headroom-openclaw 0.1.0 → 0.36.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/engine.ts","../src/proxy-manager.ts","../src/convert.ts","../src/tools/headroom-retrieve.ts","../src/plugin/index.ts"],"sourcesContent":["/**\n * HeadroomContextEngine — ContextEngine implementation for OpenClaw.\n *\n * Compresses tool outputs and conversation context using the Headroom proxy.\n * Zero LLM calls — all compression is algorithmic (SmartCrusher, ContentRouter, etc.)\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { compress } from \"headroom-ai\";\nimport { ProxyManager, type ProxyManagerConfig, type ProxyManagerLogger } from \"./proxy-manager.js\";\nimport { agentToOpenAI, openAIToAgent } from \"./convert.js\";\n\nexport interface HeadroomEngineConfig extends ProxyManagerConfig {\n enabled?: boolean;\n}\n\nexport class HeadroomContextEngine {\n readonly info = {\n id: \"headroom\",\n name: \"Headroom Context Compression\",\n version: \"0.1.0\",\n ownsCompaction: true,\n };\n\n private proxyManager: ProxyManager;\n private proxyUrl: string | null = null;\n private config: HeadroomEngineConfig;\n private logger: ProxyManagerLogger;\n private stats = {\n totalCompressions: 0,\n totalTokensSaved: 0,\n totalTokensBefore: 0,\n compactions: 0,\n };\n\n constructor(config: HeadroomEngineConfig = {}, logger?: ProxyManagerLogger) {\n this.config = config;\n this.logger = logger ?? {\n info: (m) => console.log(`[headroom] ${m}`),\n warn: (m) => console.warn(`[headroom] ${m}`),\n error: (m) => console.error(`[headroom] ${m}`),\n debug: () => {},\n };\n this.proxyManager = new ProxyManager(config, this.logger);\n }\n\n // === ContextEngine Lifecycle ===\n\n async bootstrap(params: {\n sessionId: string;\n sessionKey?: string;\n sessionFile: string;\n }): Promise<{ bootstrapped: boolean; reason?: string }> {\n if (this.config.enabled === false) {\n return { bootstrapped: false, reason: \"disabled\" };\n }\n\n try {\n this.proxyUrl = await this.proxyManager.start();\n this.logger.info(`Engine bootstrapped (proxy: ${this.proxyUrl})`);\n return { bootstrapped: true };\n } catch (error) {\n this.logger.error(`Bootstrap failed: ${error}`);\n return { bootstrapped: false, reason: String(error) };\n }\n }\n\n async ingest(params: {\n sessionId: string;\n message: any;\n isHeartbeat?: boolean;\n }): Promise<{ ingested: boolean }> {\n // No-op: OpenClaw's runtime stores messages. We don't need a separate store.\n return { ingested: true };\n }\n\n async ingestBatch?(params: {\n sessionId: string;\n messages: any[];\n isHeartbeat?: boolean;\n }): Promise<{ ingestedCount: number }> {\n return { ingestedCount: params.messages.length };\n }\n\n /**\n * Assemble context for the model — THE CORE HOOK.\n *\n * Converts AgentMessage[] → OpenAI format → compress() → AgentMessage[]\n */\n async assemble(params: {\n sessionId: string;\n messages: any[];\n tokenBudget?: number;\n model?: string;\n prompt?: string;\n }): Promise<{\n messages: any[];\n estimatedTokens: number;\n systemPromptAddition?: string;\n }> {\n if (!this.proxyUrl || this.config.enabled === false) {\n // Fallback: return messages unchanged\n return { messages: params.messages, estimatedTokens: 0 };\n }\n\n try {\n // Convert AgentMessage → OpenAI format\n const openaiMessages = agentToOpenAI(params.messages);\n\n // Compress via proxy — pass tokenBudget so RollingWindow enforces it\n const result = await compress(openaiMessages, {\n model: params.model ?? \"claude-sonnet-4-5\",\n baseUrl: this.proxyUrl,\n fallback: true,\n tokenBudget: params.tokenBudget,\n });\n\n if (!result.compressed || result.tokensSaved === 0) {\n return { messages: params.messages, estimatedTokens: result.tokensBefore };\n }\n\n // Convert back to AgentMessage format\n const compressedAgentMessages = openAIToAgent(result.messages);\n\n // Track stats\n this.stats.totalCompressions++;\n this.stats.totalTokensSaved += result.tokensSaved;\n this.stats.totalTokensBefore += result.tokensBefore;\n\n this.logger.debug(\n `Assembled: ${result.tokensBefore} → ${result.tokensAfter} tokens (saved ${result.tokensSaved})`,\n );\n\n return {\n messages: compressedAgentMessages,\n estimatedTokens: result.tokensAfter,\n systemPromptAddition:\n result.tokensSaved > 100\n ? `[Context compressed by Headroom: ${result.tokensSaved} tokens saved. Use headroom_retrieve with the hash to get full details.]`\n : undefined,\n };\n } catch (error) {\n this.logger.error(`Assemble failed: ${error}`);\n // Graceful fallback: return original messages\n return { messages: params.messages, estimatedTokens: 0 };\n }\n }\n\n /**\n * Compact context — zero-cost alternative to LLM summarization.\n *\n * Calls compress() with the token budget, which triggers:\n * - SmartCrusher: aggressive JSON compression (70-90% on tool outputs)\n * - Kompress: ModernBERT text compression (40-60% on assistant text)\n * - RollingWindow: drops oldest messages if still over budget\n * - CCR: stores originals for retrieval via headroom_retrieve tool\n *\n * Zero LLM calls. All algorithmic.\n */\n async compact(params: {\n sessionId: string;\n sessionFile: string;\n tokenBudget?: number;\n force?: boolean;\n runtimeContext?: any;\n }): Promise<{\n ok: boolean;\n compacted: boolean;\n reason?: string;\n result?: {\n tokensBefore: number;\n tokensAfter?: number;\n };\n }> {\n if (!this.proxyUrl) {\n return { ok: false, compacted: false, reason: \"Proxy not available\" };\n }\n\n // Read current messages from session file if available\n // For now, compact() works in tandem with assemble() — the next assemble()\n // call will compress with the token budget. When compact() is called\n // independently, we report success since our pipeline handles it.\n //\n // TODO: Read session file, extract messages, call compress() with tokenBudget,\n // write back compacted messages.\n\n this.stats.compactions++;\n this.logger.info(\n `Compact called (budget: ${params.tokenBudget ?? \"none\"}, force: ${params.force ?? false})`,\n );\n\n return {\n ok: true,\n compacted: true,\n reason: \"Headroom applies SmartCrusher + Kompress + RollingWindow on next assemble()\",\n };\n }\n\n async afterTurn?(params: {\n sessionId: string;\n messages: any[];\n prePromptMessageCount: number;\n isHeartbeat?: boolean;\n }): Promise<void> {\n // Optional: could log stats or trigger learning\n }\n\n async prepareSubagentSpawn?(params: {\n parentSessionKey: string;\n childSessionKey: string;\n ttlMs?: number;\n }): Promise<{ rollback: () => Promise<void> } | undefined> {\n // Subagent context is compressed naturally via assemble()\n return undefined;\n }\n\n async onSubagentEnded?(params: {\n childSessionKey: string;\n reason: string;\n }): Promise<void> {\n // No-op\n }\n\n async dispose(): Promise<void> {\n await this.proxyManager.stop();\n this.logger.info(\n `Engine disposed. Stats: ${this.stats.totalCompressions} compressions, ` +\n `${this.stats.totalTokensSaved} tokens saved`,\n );\n }\n\n // --- Public API ---\n\n getStats() {\n return { ...this.stats };\n }\n\n getProxyUrl(): string | null {\n return this.proxyUrl;\n }\n}\n","/**\n * Manages the Headroom proxy process lifecycle.\n *\n * - Detects if a proxy is already running (e.g., user has `headroom proxy` for Claude Code)\n * - If not, spawns one as a child process with auto-assigned port\n * - Health checks, restart on crash, graceful shutdown\n */\n\nimport { spawn, type ChildProcess } from \"node:child_process\";\nimport { createWriteStream } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { homedir } from \"node:os\";\n\nconst DEFAULT_PORT = 8787;\nconst HEALTH_CHECK_INTERVAL_MS = 30_000;\nconst STARTUP_TIMEOUT_MS = 15_000;\nconst RESTART_DELAY_MS = 2_000;\nconst MAX_RESTART_ATTEMPTS = 3;\n\nexport interface ProxyManagerConfig {\n proxyUrl?: string;\n pythonPath?: string;\n autoStart?: boolean;\n proxyPort?: number;\n}\n\nexport interface ProxyManagerLogger {\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n debug(message: string): void;\n}\n\nconst defaultLogger: ProxyManagerLogger = {\n info: (m) => console.log(`[headroom] ${m}`),\n warn: (m) => console.warn(`[headroom] ${m}`),\n error: (m) => console.error(`[headroom] ${m}`),\n debug: () => {},\n};\n\nexport class ProxyManager {\n private config: ProxyManagerConfig;\n private logger: ProxyManagerLogger;\n private process: ChildProcess | null = null;\n private proxyUrl: string | null = null;\n private weStartedIt = false;\n private restartCount = 0;\n private healthInterval: ReturnType<typeof setInterval> | null = null;\n private disposed = false;\n\n constructor(config: ProxyManagerConfig = {}, logger?: ProxyManagerLogger) {\n this.config = config;\n this.logger = logger ?? defaultLogger;\n }\n\n /**\n * Ensure a proxy is available. Returns the URL.\n *\n * 1. If proxyUrl is configured, use it\n * 2. Check if proxy is already running on default port\n * 3. If autoStart, spawn one\n */\n async start(): Promise<string> {\n // Option 1: Explicit URL configured\n if (this.config.proxyUrl) {\n const url = this.config.proxyUrl.replace(/\\/+$/, \"\");\n if (await this.healthCheck(url)) {\n this.proxyUrl = url;\n this.logger.info(`Connected to proxy at ${url}`);\n return url;\n }\n throw new Error(`Headroom proxy not reachable at ${url}`);\n }\n\n // Option 2: Check default port\n const defaultUrl = `http://127.0.0.1:${DEFAULT_PORT}`;\n if (await this.healthCheck(defaultUrl)) {\n this.proxyUrl = defaultUrl;\n this.logger.info(`Found running proxy at ${defaultUrl}`);\n this.startHealthMonitor();\n return defaultUrl;\n }\n\n // Option 3: Auto-start\n if (this.config.autoStart !== false) {\n return this.spawnProxy();\n }\n\n throw new Error(\n \"Headroom proxy not running. Start with: headroom proxy --port 8787\\n\" +\n \"Or install: pip install 'headroom-ai[proxy]'\",\n );\n }\n\n /**\n * Spawn the headroom proxy as a child process.\n */\n private async spawnProxy(): Promise<string> {\n const pythonPath = await this.findPython();\n if (!pythonPath) {\n throw new Error(\n \"Python not found. Install Python 3.10+ and run: pip install 'headroom-ai[proxy]'\",\n );\n }\n\n // Check if headroom-ai is installed\n const installed = await this.checkHeadroomInstalled(pythonPath);\n if (!installed) {\n throw new Error(\n \"headroom-ai Python package not found.\\n\" +\n \"Install with: pip install 'headroom-ai[proxy]'\",\n );\n }\n\n const port = this.config.proxyPort ?? 0; // 0 = OS picks a free port\n const actualPort = port === 0 ? await this.findFreePort() : port;\n const url = `http://127.0.0.1:${actualPort}`;\n\n this.logger.info(`Starting proxy on port ${actualPort}...`);\n\n // Log file\n const logDir = join(homedir(), \".headroom\", \"logs\");\n const logPath = join(logDir, \"openclaw-proxy.log\");\n\n let logStream: ReturnType<typeof createWriteStream> | null = null;\n try {\n const { mkdirSync } = await import(\"node:fs\");\n mkdirSync(logDir, { recursive: true });\n logStream = createWriteStream(logPath, { flags: \"a\" });\n } catch {\n // Can't create log file — use /dev/null\n }\n\n const proc = spawn(\n pythonPath,\n [\"-m\", \"headroom.cli\", \"proxy\", \"--port\", String(actualPort)],\n {\n env: { ...process.env, PYTHONIOENCODING: \"utf-8\" },\n stdio: [\"ignore\", logStream ? \"pipe\" : \"ignore\", logStream ? \"pipe\" : \"ignore\"],\n detached: false,\n },\n );\n\n if (logStream) {\n proc.stdout?.pipe(logStream);\n proc.stderr?.pipe(logStream);\n }\n\n proc.on(\"exit\", (code) => {\n if (!this.disposed && this.weStartedIt) {\n this.logger.warn(`Proxy exited with code ${code}`);\n this.handleCrash();\n }\n });\n\n this.process = proc;\n this.weStartedIt = true;\n\n // Wait for healthy\n const healthy = await this.waitForHealthy(url, STARTUP_TIMEOUT_MS);\n if (!healthy) {\n proc.kill();\n this.process = null;\n throw new Error(\n `Proxy failed to start within ${STARTUP_TIMEOUT_MS / 1000}s. Check ${logPath}`,\n );\n }\n\n this.proxyUrl = url;\n this.logger.info(`Proxy started on port ${actualPort} (PID: ${proc.pid})`);\n this.startHealthMonitor();\n return url;\n }\n\n /**\n * Stop the proxy if we started it.\n */\n async stop(): Promise<void> {\n this.disposed = true;\n if (this.healthInterval) {\n clearInterval(this.healthInterval);\n this.healthInterval = null;\n }\n if (this.process && this.weStartedIt) {\n this.logger.info(\"Stopping proxy...\");\n this.process.kill(\"SIGTERM\");\n // Give it 3s to shutdown gracefully\n await new Promise<void>((resolve) => {\n const timeout = setTimeout(() => {\n this.process?.kill(\"SIGKILL\");\n resolve();\n }, 3000);\n this.process?.on(\"exit\", () => {\n clearTimeout(timeout);\n resolve();\n });\n });\n this.process = null;\n }\n }\n\n getUrl(): string | null {\n return this.proxyUrl;\n }\n\n // --- Internal ---\n\n private async healthCheck(url: string): Promise<boolean> {\n try {\n const resp = await fetch(`${url}/health`, {\n signal: AbortSignal.timeout(3000),\n });\n return resp.ok;\n } catch {\n return false;\n }\n }\n\n private async waitForHealthy(url: string, timeoutMs: number): Promise<boolean> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n if (await this.healthCheck(url)) return true;\n await new Promise((r) => setTimeout(r, 500));\n }\n return false;\n }\n\n private startHealthMonitor(): void {\n if (this.healthInterval) return;\n this.healthInterval = setInterval(async () => {\n if (this.proxyUrl && !(await this.healthCheck(this.proxyUrl))) {\n this.logger.warn(\"Proxy health check failed\");\n if (this.weStartedIt) this.handleCrash();\n }\n }, HEALTH_CHECK_INTERVAL_MS);\n }\n\n private async handleCrash(): Promise<void> {\n if (this.disposed) return;\n if (this.restartCount >= MAX_RESTART_ATTEMPTS) {\n this.logger.error(`Proxy crashed ${MAX_RESTART_ATTEMPTS} times. Giving up.`);\n return;\n }\n this.restartCount++;\n this.logger.info(`Restarting proxy (attempt ${this.restartCount}/${MAX_RESTART_ATTEMPTS})...`);\n await new Promise((r) => setTimeout(r, RESTART_DELAY_MS));\n try {\n await this.spawnProxy();\n } catch (e) {\n this.logger.error(`Restart failed: ${e}`);\n }\n }\n\n private async findPython(): Promise<string | null> {\n if (this.config.pythonPath) return this.config.pythonPath;\n\n for (const cmd of [\"python3\", \"python\"]) {\n try {\n const { execSync } = await import(\"node:child_process\");\n const version = execSync(`${cmd} --version 2>&1`, { encoding: \"utf-8\" }).trim();\n if (version.includes(\"Python 3.\")) return cmd;\n } catch {\n continue;\n }\n }\n return null;\n }\n\n private async checkHeadroomInstalled(pythonPath: string): Promise<boolean> {\n try {\n const { execSync } = await import(\"node:child_process\");\n execSync(`${pythonPath} -c \"import headroom\"`, {\n encoding: \"utf-8\",\n stdio: \"pipe\",\n });\n return true;\n } catch {\n return false;\n }\n }\n\n private async findFreePort(): Promise<number> {\n const { createServer } = await import(\"node:net\");\n return new Promise((resolve, reject) => {\n const server = createServer();\n server.listen(0, () => {\n const addr = server.address();\n if (addr && typeof addr === \"object\") {\n const port = addr.port;\n server.close(() => resolve(port));\n } else {\n reject(new Error(\"Could not find free port\"));\n }\n });\n });\n }\n}\n","/**\n * Convert between OpenClaw's AgentMessage format and OpenAI message format.\n *\n * AgentMessage uses:\n * role: \"user\" | \"assistant\" | \"toolResult\"\n * content: string | ContentBlock[]\n *\n * OpenAI uses:\n * role: \"user\" | \"assistant\" | \"system\" | \"tool\"\n * content: string\n * tool_calls?: ToolCall[]\n * tool_call_id?: string\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport interface OpenAIMessage {\n role: string;\n content: string | null;\n tool_calls?: any[];\n tool_call_id?: string;\n name?: string;\n}\n\n/**\n * Convert AgentMessage[] to OpenAI message format for compression.\n */\nexport function agentToOpenAI(messages: any[]): OpenAIMessage[] {\n const result: OpenAIMessage[] = [];\n\n for (const msg of messages) {\n const role = msg.role;\n\n if (role === \"system\") {\n result.push({\n role: \"system\",\n content: typeof msg.content === \"string\" ? msg.content : extractText(msg.content),\n });\n continue;\n }\n\n if (role === \"user\") {\n result.push({\n role: \"user\",\n content: typeof msg.content === \"string\" ? msg.content : extractText(msg.content),\n });\n continue;\n }\n\n if (role === \"assistant\") {\n const content = msg.content;\n if (typeof content === \"string\") {\n result.push({ role: \"assistant\", content });\n continue;\n }\n\n // Content blocks: extract text and tool_use blocks\n if (Array.isArray(content)) {\n const textParts: string[] = [];\n const toolCalls: any[] = [];\n\n for (const block of content) {\n if (typeof block === \"string\") {\n textParts.push(block);\n } else if (block.type === \"text\") {\n textParts.push(block.text);\n } else if (block.type === \"tool_use\") {\n toolCalls.push({\n id: block.id,\n type: \"function\",\n function: {\n name: block.name,\n arguments:\n typeof block.input === \"string\"\n ? block.input\n : JSON.stringify(block.input ?? {}),\n },\n });\n }\n }\n\n const openaiMsg: OpenAIMessage = {\n role: \"assistant\",\n content: textParts.length > 0 ? textParts.join(\"\") : null,\n };\n if (toolCalls.length > 0) {\n openaiMsg.tool_calls = toolCalls;\n }\n result.push(openaiMsg);\n }\n continue;\n }\n\n if (role === \"toolResult\" || role === \"tool_result\") {\n const content =\n typeof msg.content === \"string\"\n ? msg.content\n : Array.isArray(msg.content)\n ? extractText(msg.content)\n : JSON.stringify(msg.content);\n\n result.push({\n role: \"tool\",\n content,\n tool_call_id: msg.tool_use_id ?? msg.toolCallId ?? msg.id ?? \"unknown\",\n });\n continue;\n }\n\n // Fallback: pass through as user message\n result.push({\n role: \"user\",\n content: typeof msg.content === \"string\" ? msg.content : JSON.stringify(msg.content),\n });\n }\n\n return result;\n}\n\n/**\n * Convert compressed OpenAI messages back to AgentMessage format.\n */\nexport function openAIToAgent(messages: OpenAIMessage[]): any[] {\n const result: any[] = [];\n\n for (const msg of messages) {\n if (msg.role === \"system\") {\n result.push({\n role: \"system\",\n content: msg.content ?? \"\",\n timestamp: Date.now(),\n });\n continue;\n }\n\n if (msg.role === \"user\") {\n result.push({\n role: \"user\",\n content: msg.content ?? \"\",\n timestamp: Date.now(),\n });\n continue;\n }\n\n if (msg.role === \"assistant\") {\n const blocks: any[] = [];\n if (msg.content) {\n blocks.push({ type: \"text\", text: msg.content });\n }\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n let input: any;\n try {\n input = JSON.parse(tc.function.arguments);\n } catch {\n input = tc.function.arguments ?? {};\n }\n blocks.push({\n type: \"tool_use\",\n id: tc.id,\n name: tc.function.name,\n input,\n });\n }\n }\n // OpenClaw's Pi agent expects content to always be an array for assistant messages\n // (it calls .flatMap() on it). Never flatten to a string.\n result.push({\n role: \"assistant\",\n content: blocks,\n timestamp: Date.now(),\n });\n continue;\n }\n\n if (msg.role === \"tool\") {\n result.push({\n role: \"toolResult\",\n content: msg.content ?? \"\",\n tool_use_id: msg.tool_call_id ?? \"unknown\",\n timestamp: Date.now(),\n });\n continue;\n }\n }\n\n return result;\n}\n\n/**\n * Extract text from content blocks.\n */\nfunction extractText(content: any): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return JSON.stringify(content);\n\n return content\n .map((block: any) => {\n if (typeof block === \"string\") return block;\n if (block.type === \"text\") return block.text;\n if (block.type === \"tool_result\") {\n return typeof block.content === \"string\" ? block.content : JSON.stringify(block.content);\n }\n return \"\";\n })\n .filter(Boolean)\n .join(\"\\n\");\n}\n","/**\n * CCR (Compress-Cache-Retrieve) tool for OpenClaw.\n *\n * Allows the agent to retrieve original uncompressed content\n * from the Headroom proxy's compression store.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport interface RetrieveToolConfig {\n proxyUrl: string;\n}\n\nexport function createHeadroomRetrieveTool(config: RetrieveToolConfig) {\n return {\n name: \"headroom_retrieve\",\n description:\n \"Retrieve original uncompressed content from Headroom's compression store. \" +\n \"Use when compressed context mentions a hash and you need the full details. \" +\n \"Pass the hash from the compression marker (24 hex characters). \" +\n \"Optionally pass a query to search within the original content.\",\n parameters: {\n type: \"object\" as const,\n properties: {\n hash: {\n type: \"string\",\n description: \"The 24-character hex hash from the compression marker\",\n },\n query: {\n type: \"string\",\n description: \"Optional search query to filter results within the original content\",\n },\n },\n required: [\"hash\"],\n },\n execute: async (args: { hash: string; query?: string }): Promise<string> => {\n const { hash, query } = args;\n\n // Validate hash format\n if (!/^[a-f0-9]{24}$/i.test(hash)) {\n return JSON.stringify({\n error: \"Invalid hash format. Expected 24 hex characters.\",\n });\n }\n\n try {\n const url = query\n ? `${config.proxyUrl}/v1/retrieve/${hash}?query=${encodeURIComponent(query)}`\n : `${config.proxyUrl}/v1/retrieve/${hash}`;\n\n const resp = await fetch(url, {\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n return JSON.stringify({\n error: `Retrieval failed: HTTP ${resp.status}`,\n details: body,\n });\n }\n\n const data = await resp.json();\n return typeof data === \"string\" ? data : JSON.stringify(data);\n } catch (error) {\n return JSON.stringify({\n error: `Retrieval failed: ${error}`,\n hint: \"The compressed content may have expired (default TTL: 5 minutes)\",\n });\n }\n },\n };\n}\n","/**\n * Headroom OpenClaw Plugin — register ContextEngine + CCR retrieval tool.\n *\n * Usage:\n * openclaw plugins install @headroom-ai/openclaw\n *\n * Configuration (in ~/.openclaw/config.json or ~/.clawdbot/clawdbot.json):\n * {\n * \"plugins\": {\n * \"slots\": { \"contextEngine\": \"headroom\" },\n * \"entries\": { \"headroom\": { \"enabled\": true } }\n * }\n * }\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { HeadroomContextEngine } from \"../engine.js\";\nimport { createHeadroomRetrieveTool } from \"../tools/headroom-retrieve.js\";\n\nexport default function headroomPlugin(api: any) {\n const config = api.config?.plugins?.entries?.headroom?.config ?? {};\n const logger = api.logger ?? console;\n\n const engine = new HeadroomContextEngine(config, {\n info: (m: string) => logger.info(m),\n warn: (m: string) => logger.warn(m),\n error: (m: string) => logger.error(m),\n debug: (m: string) => logger.debug?.(m),\n });\n\n // Register as context engine\n api.registerContextEngine(\"headroom\", () => engine);\n\n // Register CCR retrieval tool (active once proxy is running)\n api.registerTool((ctx: any) => {\n const proxyUrl = engine.getProxyUrl();\n if (!proxyUrl) return null;\n return createHeadroomRetrieveTool({ proxyUrl });\n });\n\n logger.info(\"[headroom] Plugin registered\");\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/engine.ts","../src/proxy-manager.ts","../src/convert.ts","../src/gateway-config.ts","../src/tools/headroom-retrieve.ts","../src/plugin/index.ts"],"sourcesContent":["/**\n * HeadroomContextEngine — ContextEngine implementation for OpenClaw.\n *\n * Compresses tool outputs and conversation context using the Headroom proxy.\n * Zero LLM calls — all compression is algorithmic (SmartCrusher, ContentRouter, etc.)\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { compress } from \"headroom-ai\";\nimport { ProxyManager, defaultLogger, type ProxyManagerConfig, type ProxyManagerLogger } from \"./proxy-manager.js\";\nimport { agentToOpenAI, normalizeAgentMessages, openAIToAgent } from \"./convert.js\";\n\n/** Race a promise against a timeout and always release the timer. */\nfunction withTimeout<T>(promise: Promise<T>, ms: number): Promise<T> {\n let timerId: ReturnType<typeof setTimeout> | undefined;\n const timer = new Promise<never>((_, reject) => {\n timerId = setTimeout(() => reject(new Error(`headroom compress() timed out after ${ms}ms`)), ms);\n });\n return Promise.race([promise, timer]).finally(() => {\n if (timerId !== undefined) clearTimeout(timerId);\n });\n}\n\nexport interface HeadroomEngineConfig extends ProxyManagerConfig {\n enabled?: boolean;\n requestTimeoutMs?: number;\n circuitBreakerThreshold?: number;\n circuitBreakerCooldownMs?: number;\n}\n\nexport class HeadroomContextEngine {\n readonly info = {\n id: \"headroom\",\n name: \"Headroom Context Compression\",\n version: \"0.1.0\",\n ownsCompaction: true,\n };\n\n private proxyManager: ProxyManager;\n private proxyUrl: string | null = null;\n private config: HeadroomEngineConfig;\n private logger: ProxyManagerLogger;\n private proxyReadyListeners = new Set<(proxyUrl: string) => void | Promise<void>>();\n private proxyStartupPromise: Promise<string> | null = null;\n private proxyStartupError: unknown = null;\n private stats = {\n totalCompressions: 0,\n totalTokensSaved: 0,\n totalTokensBefore: 0,\n compactions: 0,\n };\n private circuit = { errors: 0, openUntilMs: 0 };\n\n constructor(config: HeadroomEngineConfig = {}, logger?: ProxyManagerLogger) {\n this.config = config;\n this.logger = logger ?? defaultLogger;\n this.proxyManager = new ProxyManager(config, this.logger);\n }\n\n // === ContextEngine Lifecycle ===\n\n async bootstrap(params: {\n sessionId: string;\n sessionKey?: string;\n sessionFile: string;\n }): Promise<{ bootstrapped: boolean; reason?: string }> {\n if (this.config.enabled === false) {\n return { bootstrapped: false, reason: \"disabled\" };\n }\n\n this.ensureProxyStarted();\n return { bootstrapped: true, reason: \"proxy startup scheduled\" };\n }\n\n async ingest(params: {\n sessionId: string;\n message: any;\n isHeartbeat?: boolean;\n }): Promise<{ ingested: boolean }> {\n // No-op: OpenClaw's runtime stores messages. We don't need a separate store.\n return { ingested: true };\n }\n\n async ingestBatch?(params: {\n sessionId: string;\n messages: any[];\n isHeartbeat?: boolean;\n }): Promise<{ ingestedCount: number }> {\n return { ingestedCount: params.messages.length };\n }\n\n /**\n * Assemble context for the model — THE CORE HOOK.\n *\n * Converts AgentMessage[] → OpenAI format → compress() → AgentMessage[]\n */\n async assemble(params: {\n sessionId: string;\n messages: any[];\n tokenBudget?: number;\n model?: string;\n prompt?: string;\n }): Promise<{\n messages: any[];\n estimatedTokens: number;\n systemPromptAddition?: string;\n }> {\n if (!this.proxyUrl || this.config.enabled === false) {\n this.ensureProxyStarted();\n // Fallback: return messages unchanged\n return { messages: normalizeAgentMessages(params.messages), estimatedTokens: 0 };\n }\n\n if (this.isCircuitOpen()) {\n this.logger.warn(\"[headroom] Circuit open — using uncompressed messages\");\n return { messages: normalizeAgentMessages(params.messages), estimatedTokens: 0 };\n }\n\n try {\n // Convert AgentMessage → OpenAI format\n const openaiMessages = agentToOpenAI(params.messages);\n\n // Compress via proxy — pass tokenBudget so RollingWindow enforces it\n const result = await withTimeout(\n compress(openaiMessages, {\n model: params.model ?? \"claude-sonnet-4-5\",\n baseUrl: this.proxyUrl,\n fallback: true,\n tokenBudget: params.tokenBudget,\n } as any),\n this.config.requestTimeoutMs ?? 30_000,\n );\n\n if (!result.compressed || result.tokensSaved === 0) {\n this.resetCircuit();\n return {\n messages: normalizeAgentMessages(params.messages),\n estimatedTokens: result.tokensBefore,\n };\n }\n\n // Convert back to AgentMessage format\n const compressedAgentMessages = openAIToAgent(result.messages);\n this.resetCircuit();\n\n // Track stats\n this.stats.totalCompressions++;\n this.stats.totalTokensSaved += result.tokensSaved;\n this.stats.totalTokensBefore += result.tokensBefore;\n\n this.logger.debug(\n `Assembled: ${result.tokensBefore} → ${result.tokensAfter} tokens (saved ${result.tokensSaved})`,\n );\n\n return {\n messages: compressedAgentMessages,\n estimatedTokens: result.tokensAfter,\n systemPromptAddition:\n result.tokensSaved > 100\n ? `[Context compressed by Headroom: ${result.tokensSaved} tokens saved. Use headroom_retrieve with the hash to get full details.]`\n : undefined,\n };\n } catch (error) {\n this.logger.error(`Assemble failed: ${error}`);\n this.tripCircuit(error);\n // Graceful fallback: return original messages\n return { messages: normalizeAgentMessages(params.messages), estimatedTokens: 0 };\n }\n }\n\n /**\n * Compact context — zero-cost alternative to LLM summarization.\n *\n * Calls compress() with the token budget, which triggers:\n * - SmartCrusher: aggressive JSON compression (70-90% on tool outputs)\n * - Kompress: ModernBERT text compression (40-60% on assistant text)\n * - RollingWindow: drops oldest messages if still over budget\n * - CCR: stores originals for retrieval via headroom_retrieve tool\n *\n * Zero LLM calls. All algorithmic.\n */\n async compact(params: {\n sessionId: string;\n sessionFile: string;\n tokenBudget?: number;\n force?: boolean;\n runtimeContext?: any;\n }): Promise<{\n ok: boolean;\n compacted: boolean;\n reason?: string;\n result?: {\n tokensBefore: number;\n tokensAfter?: number;\n };\n }> {\n if (!this.proxyUrl) {\n return { ok: false, compacted: false, reason: \"Proxy not available\" };\n }\n\n // Read current messages from session file if available\n // For now, compact() works in tandem with assemble() — the next assemble()\n // call will compress with the token budget. When compact() is called\n // independently, we report success since our pipeline handles it.\n //\n // TODO: Read session file, extract messages, call compress() with tokenBudget,\n // write back compacted messages.\n\n this.stats.compactions++;\n this.logger.info(\n `Compact called (budget: ${params.tokenBudget ?? \"none\"}, force: ${params.force ?? false})`,\n );\n\n return {\n ok: true,\n compacted: true,\n reason: \"Headroom applies SmartCrusher + Kompress + RollingWindow on next assemble()\",\n };\n }\n\n async afterTurn?(params: {\n sessionId: string;\n messages: any[];\n prePromptMessageCount: number;\n isHeartbeat?: boolean;\n }): Promise<void> {\n // Optional: could log stats or trigger learning\n }\n\n async prepareSubagentSpawn?(params: {\n parentSessionKey: string;\n childSessionKey: string;\n ttlMs?: number;\n }): Promise<{ rollback: () => Promise<void> } | undefined> {\n // Subagent context is compressed naturally via assemble()\n return undefined;\n }\n\n async onSubagentEnded?(params: {\n childSessionKey: string;\n reason: string;\n }): Promise<void> {\n // No-op\n }\n\n async dispose(): Promise<void> {\n await this.proxyManager.stop();\n this.logger.info(\n `Engine disposed. Stats: ${this.stats.totalCompressions} compressions, ` +\n `${this.stats.totalTokensSaved} tokens saved`,\n );\n }\n\n // --- Public API ---\n\n getStats() {\n return { ...this.stats };\n }\n\n getProxyUrl(): string | null {\n return this.proxyUrl;\n }\n\n getProxyStartupError(): unknown {\n return this.proxyStartupError;\n }\n\n private isCircuitOpen(): boolean {\n const threshold = this.config.circuitBreakerThreshold ?? 3;\n if (this.circuit.errors < threshold) return false;\n if (Date.now() < this.circuit.openUntilMs) return true;\n this.circuit = { errors: 0, openUntilMs: 0 };\n return false;\n }\n\n private tripCircuit(error: unknown): void {\n this.circuit.errors += 1;\n const threshold = this.config.circuitBreakerThreshold ?? 3;\n if (this.circuit.errors < threshold) return;\n const cooldownMs = this.config.circuitBreakerCooldownMs ?? 60_000;\n this.circuit.openUntilMs = Date.now() + cooldownMs;\n this.logger.warn(\n `[headroom] Circuit breaker opened after ${this.circuit.errors} errors ` +\n `(last: ${String(error)}); bypassing compression for ${cooldownMs}ms`,\n );\n }\n\n private resetCircuit(): void {\n this.circuit = { errors: 0, openUntilMs: 0 };\n }\n\n ensureProxyStarted(): void {\n if (this.config.enabled === false || this.proxyUrl || this.proxyStartupPromise) {\n return;\n }\n\n this.proxyStartupError = null;\n this.proxyStartupPromise = this.proxyManager\n .start()\n .then(async (proxyUrl) => {\n this.proxyUrl = proxyUrl;\n this.proxyStartupError = null;\n await this.notifyProxyReady(proxyUrl);\n this.logger.info(`Headroom proxy ready at ${proxyUrl}`);\n return proxyUrl;\n })\n .catch((error) => {\n this.proxyStartupError = error;\n this.logger.warn(`Headroom proxy unavailable: ${error}`);\n throw error;\n })\n .finally(() => {\n this.proxyStartupPromise = null;\n });\n\n // Fire-and-forget lifecycle callers intentionally do not await this promise.\n // Keep the promise rejectable for ensureProxyUrl(), but mark it observed so\n // a missing proxy cannot become a process-level unhandled rejection.\n void this.proxyStartupPromise.catch(() => {});\n }\n\n onProxyReady(listener: (proxyUrl: string) => void | Promise<void>): () => void {\n this.proxyReadyListeners.add(listener);\n return () => {\n this.proxyReadyListeners.delete(listener);\n };\n }\n\n async ensureProxyUrl(): Promise<string> {\n if (this.proxyUrl) {\n return this.proxyUrl;\n }\n\n this.ensureProxyStarted();\n if (!this.proxyStartupPromise) {\n throw new Error(\"Headroom proxy startup is disabled\");\n }\n return this.proxyStartupPromise;\n }\n\n private async notifyProxyReady(proxyUrl: string): Promise<void> {\n for (const listener of this.proxyReadyListeners) {\n try {\n await listener(proxyUrl);\n } catch (error) {\n this.logger.warn(`Headroom proxy ready listener failed: ${error}`);\n }\n }\n }\n}\n","/**\n * Manages connectivity to a Headroom proxy (local or remote).\n *\n * Security model:\n * - Local proxies (127.0.0.1 / localhost) can be auto-started via subprocess\n * - Remote proxies are connect-only: probe and use, never launch\n * - No environment variable access\n */\nimport { spawn } from \"node:child_process\";\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport os from \"os\";\n\nexport interface ProxyManagerConfig {\n proxyUrl?: string;\n proxyPort?: number;\n pythonPath?: string;\n autoStart?: boolean;\n startupTimeoutMs?: number;\n retryMaxAttempts?: number;\n connectTimeoutSeconds?: number;\n}\n\nexport interface ProxyManagerLogger {\n info(message: string): void;\n warn(message: string): void;\n error(message: string): void;\n debug(message: string): void;\n}\n\n/** Default logger that prefixes all messages with `[headroom]`. */\nexport const defaultLogger: ProxyManagerLogger = {\n info: (m) => console.log(`[headroom] ${m}`),\n warn: (m) => console.warn(`[headroom] ${m}`),\n error: (m) => console.error(`[headroom] ${m}`),\n debug: () => {},\n};\n\nexport interface ProxyProbeResult {\n reachable: boolean;\n isHeadroom: boolean;\n reason?: string;\n}\n\ninterface LaunchSpec {\n label: string;\n command: string;\n args: string[];\n checkCommand: string;\n checkArgs: string[];\n useShell?: boolean;\n checkUseShell?: boolean;\n}\n\nconst HEADROOM_MODULE_DISCOVERY_SNIPPET =\n \"import importlib.util, sys; sys.exit(0 if importlib.util.find_spec('headroom') else 1)\";\n\nexport class ProxyManager {\n private config: ProxyManagerConfig;\n private logger: ProxyManagerLogger;\n private proxyUrl: string | null = null;\n\n constructor(config: ProxyManagerConfig = {}, logger?: ProxyManagerLogger) {\n this.config = config;\n this.logger = logger ?? defaultLogger;\n }\n\n /**\n * Ensure a proxy is available. Returns the normalized URL origin.\n */\n async start(): Promise<string> {\n const port = this.getProxyPort();\n const rawExplicitUrl =\n typeof this.config.proxyUrl === \"string\" && this.config.proxyUrl.trim().length > 0\n ? normalizeAndValidateProxyUrl(this.config.proxyUrl)\n : null;\n // Only apply proxyPort default to local URLs — remote URLs use their protocol default\n const explicitUrl = rawExplicitUrl\n ? isLocalProxyUrl(rawExplicitUrl) ? withDefaultPort(rawExplicitUrl, port) : rawExplicitUrl\n : null;\n const defaultCandidates = this.getDefaultProxyCandidates(port);\n const candidateUrls = explicitUrl ? [explicitUrl] : [...defaultCandidates];\n const probeByUrl = new Map<string, ProxyProbeResult>();\n\n for (const url of candidateUrls) {\n const probe = await probeHeadroomProxy(url);\n probeByUrl.set(url, probe);\n if (probe.reachable && probe.isHeadroom) {\n this.proxyUrl = url;\n this.logger.info(`Headroom proxy already running at ${url}`);\n return url;\n }\n }\n\n if (explicitUrl) {\n const explicitProbe = probeByUrl.get(explicitUrl);\n if (explicitProbe?.reachable && !explicitProbe.isHeadroom) {\n throw new Error(\n `Service reachable at ${explicitUrl}, but it does not appear to be a Headroom proxy (${explicitProbe.reason ?? \"unknown service\"}).`,\n );\n }\n }\n\n // Remote URLs are connect-only — never auto-start a subprocess for them\n if (explicitUrl && !isLocalProxyUrl(explicitUrl)) {\n throw new Error(\n `Remote Headroom proxy not reachable at ${explicitUrl}. Ensure the proxy is running at that address.`,\n );\n }\n\n // Auto-start is only available for local proxies\n if (this.config.autoStart === true) {\n const startupUrl = explicitUrl ?? defaultCandidates[0];\n const startupProbe = probeByUrl.get(startupUrl);\n if (startupProbe?.reachable && !startupProbe.isHeadroom) {\n throw new Error(\n `Cannot auto-start Headroom at ${startupUrl}: port is in use by a non-Headroom service (${startupProbe.reason ?? \"unknown service\"}).`,\n );\n }\n\n this.logger.info(\n `No Headroom proxy detected${explicitUrl ? ` at ${startupUrl}` : \" on default local endpoints\"}; attempting to auto-start...`,\n );\n await this.startHeadroomProxy(startupUrl, port);\n\n const startedProbe = await waitForHeadroomProxy(\n startupUrl,\n this.config.startupTimeoutMs ?? 20_000,\n );\n if (startedProbe.reachable && startedProbe.isHeadroom) {\n this.proxyUrl = startupUrl;\n this.logger.info(`Headroom proxy started and reachable at ${startupUrl}`);\n return startupUrl;\n }\n throw new Error(\n `Attempted to start Headroom proxy, but it was not reachable at ${startupUrl} (${startedProbe.reason ?? \"unknown\"}).`,\n );\n }\n\n if (explicitUrl) {\n throw new Error(\n `Headroom proxy not reachable at ${explicitUrl}. Ensure the proxy is running first.`,\n );\n }\n\n throw new Error(\n `Headroom proxy not detected on default endpoints (${defaultCandidates.join(\", \")}). ` +\n \"Set proxyUrl explicitly or enable autoStart.\",\n );\n }\n\n private getProxyPort(): number {\n const rawPort = this.config.proxyPort;\n if (!Number.isInteger(rawPort) || rawPort === undefined) return 8787;\n if (rawPort < 1 || rawPort > 65535) {\n throw new Error(\"proxyPort must be an integer between 1 and 65535\");\n }\n return rawPort;\n }\n\n private getDefaultProxyCandidates(port: number): string[] {\n return [`http://127.0.0.1:${port}`, `http://localhost:${port}`];\n }\n\n /**\n * Stop manager state. Spawned proxy processes are detached and externally managed.\n */\n async stop(): Promise<void> {\n this.proxyUrl = null;\n }\n\n getUrl(): string | null {\n return this.proxyUrl;\n }\n\n // --- Internal ---\n\n private async startHeadroomProxy(proxyUrl: string, defaultPort: number): Promise<void> {\n const parsed = new URL(proxyUrl);\n const host = parsed.hostname;\n const port = parsed.port || String(defaultPort);\n const specs = this.buildLaunchSpecs(host, port);\n const errors: string[] = [];\n\n for (const spec of specs) {\n if (!this.canExecute(spec.checkCommand, spec.checkArgs, spec.checkUseShell ?? spec.useShell)) {\n this.logger.debug(`Launcher unavailable: ${spec.label}`);\n continue;\n }\n\n try {\n const child = spawn(spec.command, spec.args, {\n detached: true,\n shell: spec.useShell === true,\n stdio: \"ignore\",\n });\n child.unref();\n this.logger.info(`Auto-start launcher selected: ${spec.label}`);\n return;\n } catch (error) {\n errors.push(`${spec.label}: ${String(error)}`);\n }\n }\n\n throw new Error(\n \"No usable Headroom launcher found. Tried PATH, local npm, global npm, and Python. \" +\n \"Install headroom-ai (npm or pip) and ensure one launcher is available.\\n\" +\n (errors.length > 0 ? `Launch errors: ${errors.join(\"; \")}` : \"\"),\n );\n }\n\n private buildLaunchSpecs(host: string, port: string): LaunchSpec[] {\n const commonArgs = [\"proxy\", \"--host\", host, \"--port\", port];\n const retryMaxAttempts = this.config.retryMaxAttempts;\n if (Number.isInteger(retryMaxAttempts)) {\n commonArgs.push(\"--retry-max-attempts\", String(retryMaxAttempts));\n }\n\n const connectTimeoutSeconds = this.config.connectTimeoutSeconds;\n if (Number.isInteger(connectTimeoutSeconds)) {\n commonArgs.push(\"--connect-timeout-seconds\", String(connectTimeoutSeconds));\n }\n\n const specs: LaunchSpec[] = [];\n\n const configuredPython = this.getConfiguredPythonCommand();\n if (configuredPython) {\n specs.push({\n label: `Configured Python: ${configuredPython} -m headroom.cli`,\n command: configuredPython,\n args: [\"-m\", \"headroom.cli\", ...commonArgs],\n checkCommand: configuredPython,\n checkArgs: [\"-c\", HEADROOM_MODULE_DISCOVERY_SNIPPET],\n });\n }\n\n // 2) Windows pyenv: resolve the real executable so we avoid shim .bat wrappers.\n if (process.platform === \"win32\") {\n const pyenvHeadroom = this.getPyenvResolvedHeadroom();\n if (pyenvHeadroom) {\n specs.push({\n label: `pyenv: ${pyenvHeadroom}`,\n command: pyenvHeadroom,\n args: commonArgs,\n checkCommand: pyenvHeadroom,\n checkArgs: [\"--version\"],\n useShell: false,\n });\n }\n }\n\n // 3) PATH\n specs.push({\n label: \"PATH: headroom\",\n command: \"headroom\",\n args: commonArgs,\n checkCommand: process.platform === \"win32\" ? \"where.exe\" : \"sh\",\n checkArgs: process.platform === \"win32\"\n ? [\"headroom\"]\n : [\"-c\", \"command -v headroom >/dev/null 2>&1\"],\n useShell: process.platform === \"win32\",\n checkUseShell: false,\n });\n\n // 4) uv tool install path (~/.local/bin/headroom)\n const uvBin = join(\n os.homedir(),\n \".local\", \"bin\", \"headroom\"\n );\n if (existsSync(uvBin)) {\n specs.push({\n label: `uv tool: ${uvBin}`,\n command: uvBin,\n args: commonArgs,\n checkCommand: uvBin,\n checkArgs: [\"--version\"],\n });\n }\n\n // 5) Local npm install (inside plugin install path)\n const moduleDir = dirname(fileURLToPath(import.meta.url)); // .../dist\n const packageRoot = dirname(moduleDir);\n const localBinDir = join(packageRoot, \"node_modules\", \".bin\");\n const localBins = process.platform === \"win32\"\n ? [join(localBinDir, \"headroom.cmd\"), join(localBinDir, \"headroom\")]\n : [join(localBinDir, \"headroom\")];\n for (const localBin of localBins) {\n if (!existsSync(localBin)) continue;\n specs.push({\n label: `Local npm: ${localBin}`,\n command: localBin,\n args: commonArgs,\n checkCommand: localBin,\n checkArgs: [\"--version\"],\n useShell: process.platform === \"win32\",\n });\n }\n\n // 5) Global npm install\n const npmPrefix = this.getNpmGlobalPrefix();\n if (npmPrefix) {\n const globalBins = process.platform === \"win32\"\n ? [join(npmPrefix, \"headroom.cmd\"), join(npmPrefix, \"headroom\")]\n : [join(npmPrefix, \"bin\", \"headroom\"), join(npmPrefix, \"headroom\")];\n\n for (const globalBin of globalBins) {\n if (!existsSync(globalBin)) continue;\n specs.push({\n label: `Global npm: ${globalBin}`,\n command: globalBin,\n args: commonArgs,\n checkCommand: globalBin,\n checkArgs: [\"--version\"],\n useShell: process.platform === \"win32\",\n });\n }\n }\n\n // 6) Python module fallback\n const pythonCommands = this.getPythonCommands();\n for (const pyCmd of pythonCommands) {\n if (configuredPython && pyCmd === configuredPython) continue;\n specs.push({\n label: `Python: ${pyCmd} -m headroom.cli`,\n command: pyCmd,\n args: [\"-m\", \"headroom.cli\", ...commonArgs],\n checkCommand: pyCmd,\n checkArgs: [\"-c\", HEADROOM_MODULE_DISCOVERY_SNIPPET],\n });\n }\n\n return specs;\n }\n\n private getConfiguredPythonCommand(): string | null {\n const configured = typeof this.config.pythonPath === \"string\"\n ? this.config.pythonPath.trim()\n : \"\";\n return configured.length > 0 ? configured : null;\n }\n\n private getPyenvResolvedHeadroom(): string | null {\n if (process.platform !== \"win32\") return null;\n\n try {\n const result = spawnSync(\"pyenv\", [\"which\", \"headroom\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 5000,\n });\n if (result.error || result.status !== 0) return null;\n const resolved = (result.stdout ?? \"\").trim().split(/\\r?\\n/, 1)[0];\n if (!resolved || !existsSync(resolved)) return null;\n return resolved;\n } catch {\n return null;\n }\n }\n\n private getPythonCommands(): string[] {\n const commands: string[] = [];\n const configured = this.getConfiguredPythonCommand() ?? \"\";\n if (configured.length > 0) {\n commands.push(configured);\n }\n for (const fallback of [\"python\", \"python3\", \"py\"]) {\n if (!commands.includes(fallback)) commands.push(fallback);\n }\n return commands;\n }\n\n private canExecute(command: string, args: string[], useShell = false): boolean {\n try {\n const result = spawnSync(command, args, {\n shell: useShell,\n stdio: \"ignore\",\n timeout: 5000,\n });\n if (result.error) return false;\n return result.status === 0;\n } catch {\n return false;\n }\n }\n\n private getNpmGlobalPrefix(): string | null {\n try {\n const result = spawnSync(\"npm\", [\"prefix\", \"-g\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"ignore\"],\n timeout: 5000,\n });\n if (result.error || result.status !== 0) return null;\n const prefix = (result.stdout ?? \"\").trim();\n return prefix.length > 0 ? prefix : null;\n } catch {\n return null;\n }\n }\n}\n\n/** Parse a URL, returning the parsed object or throwing a descriptive error. */\nfunction parseProxyUrl(proxyUrl: string): URL {\n try {\n return new URL(proxyUrl);\n } catch {\n throw new Error(`Invalid proxyUrl: \"${proxyUrl}\"`);\n }\n}\n\nexport function normalizeAndValidateProxyUrl(proxyUrl: string): string {\n const parsed = parseProxyUrl(proxyUrl);\n\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(\"proxyUrl must use http:// or https://\");\n }\n\n if (parsed.pathname !== \"/\" || parsed.search || parsed.hash) {\n throw new Error(\"proxyUrl must not include a path, query, or hash\");\n }\n\n return parsed.origin;\n}\n\n/** Returns true if the URL points to a local address (localhost or 127.0.0.1). */\nexport function isLocalProxyUrl(proxyUrl: string): boolean {\n try {\n const parsed = new URL(proxyUrl);\n return parsed.hostname === \"127.0.0.1\" || parsed.hostname === \"localhost\";\n } catch {\n return false;\n }\n}\n\nfunction withDefaultPort(proxyUrl: string, defaultPort: number): string {\n const parsed = parseProxyUrl(proxyUrl);\n if (!parsed.port) {\n parsed.port = String(defaultPort);\n }\n return parsed.origin;\n}\n\n/**\n * Probe a configured URL and verify whether it is a running Headroom proxy.\n */\nexport async function probeHeadroomProxy(proxyUrl: string): Promise<ProxyProbeResult> {\n const origin = normalizeAndValidateProxyUrl(proxyUrl);\n const probeEndpoint = async (\n path: string,\n options: { readBody?: boolean } = {},\n ): Promise<{ reachable: boolean; ok: boolean; status?: number; body?: string }> => {\n try {\n const response = await fetch(`${origin}${path}`, {\n signal: AbortSignal.timeout(3_000),\n });\n const body =\n response.ok && options.readBody\n ? await response.text().catch(() => undefined)\n : undefined;\n return { reachable: true, ok: response.ok, status: response.status, body };\n } catch {\n return { reachable: false, ok: false };\n }\n };\n\n const ready = await probeEndpoint(\"/readyz\");\n const retrieveStats = await probeEndpoint(\"/v1/retrieve/stats\", { readBody: true });\n if (retrieveStats.ok && hasHeadroomStatsShape(retrieveStats.body)) {\n return { reachable: true, isHeadroom: true };\n }\n\n const stats = await probeEndpoint(\"/stats\", { readBody: true });\n if (stats.ok && hasHeadroomStatsShape(stats.body)) {\n return { reachable: true, isHeadroom: true };\n }\n\n const health = await probeEndpoint(\"/health\");\n const anyReachable = ready.reachable || retrieveStats.reachable || stats.reachable || health.reachable;\n if (!anyReachable) {\n return { reachable: false, isHeadroom: false, reason: \"proxy probe failed\" };\n }\n\n const reasons = [\n ready.reachable ? `readyz HTTP ${ready.status}` : \"readyz unavailable\",\n retrieveStats.reachable\n ? `retrieve stats HTTP ${retrieveStats.status}`\n : \"retrieve stats endpoint unavailable\",\n stats.reachable ? `stats HTTP ${stats.status}` : \"stats endpoint unavailable\",\n health.reachable ? `health HTTP ${health.status}` : \"health check failed\",\n ];\n return { reachable: true, isHeadroom: false, reason: reasons.join(\"; \") };\n}\n\nfunction hasHeadroomStatsShape(body: string | undefined): boolean {\n if (!body) {\n return false;\n }\n\n try {\n const parsed = JSON.parse(body) as Record<string, unknown>;\n return (\n parsed !== null &&\n typeof parsed === \"object\" &&\n (Object.hasOwn(parsed, \"proxy_inbound\") ||\n Object.hasOwn(parsed, \"api_requests\") ||\n Object.hasOwn(parsed, \"provider_tokens\") ||\n Object.hasOwn(parsed, \"proxy_compression_saved\") ||\n Object.hasOwn(parsed, \"store\") ||\n Object.hasOwn(parsed, \"recent_retrievals\"))\n );\n } catch {\n return false;\n }\n}\n\nasync function waitForHeadroomProxy(proxyUrl: string, timeoutMs: number): Promise<ProxyProbeResult> {\n const start = Date.now();\n while (Date.now() - start < timeoutMs) {\n const result = await probeHeadroomProxy(proxyUrl);\n if (result.reachable && result.isHeadroom) {\n return result;\n }\n await new Promise((resolve) => setTimeout(resolve, 500));\n }\n return probeHeadroomProxy(proxyUrl);\n}\n","/**\n * Convert between OpenClaw's AgentMessage format and OpenAI message format.\n *\n * AgentMessage uses:\n * role: \"user\" | \"assistant\" | \"toolResult\"\n * content: string | ContentBlock[]\n *\n * OpenAI uses:\n * role: \"user\" | \"assistant\" | \"system\" | \"tool\"\n * content: string\n * tool_calls?: ToolCall[]\n * tool_call_id?: string\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport interface OpenAIMessage {\n role: string;\n content: string | null;\n tool_calls?: any[];\n tool_call_id?: string;\n name?: string;\n _headroomMeta?: Record<string, unknown>;\n}\n\n/**\n * Convert AgentMessage[] to OpenAI message format for compression.\n */\nexport function agentToOpenAI(messages: any[]): OpenAIMessage[] {\n const result: OpenAIMessage[] = [];\n\n for (const msg of messages) {\n const normalized = normalizeAgentMessage(msg);\n const role = normalized.role;\n\n const buildMeta = (): Record<string, unknown> => {\n const meta = { ...normalized } as Record<string, unknown>;\n delete meta.role;\n delete meta.content;\n return meta;\n };\n\n if (role === \"system\") {\n result.push({\n role: \"system\",\n content:\n typeof normalized.content === \"string\"\n ? normalized.content\n : extractText(normalized.content),\n _headroomMeta: buildMeta(),\n });\n continue;\n }\n\n if (role === \"user\") {\n result.push({\n role: \"user\",\n content:\n typeof normalized.content === \"string\"\n ? normalized.content\n : extractText(normalized.content),\n _headroomMeta: buildMeta(),\n });\n continue;\n }\n\n if (role === \"assistant\") {\n const content = normalized.content;\n if (typeof content === \"string\") {\n result.push({ role: \"assistant\", content, _headroomMeta: buildMeta() });\n continue;\n }\n\n // Content blocks: extract text and tool call blocks.\n // OpenClaw uses `toolCall`; some adapters still emit legacy `tool_use`.\n if (Array.isArray(content)) {\n const textParts: string[] = [];\n const toolCalls: any[] = [];\n\n for (const block of content) {\n if (typeof block === \"string\") {\n textParts.push(block);\n } else if (block.type === \"text\") {\n textParts.push(block.text);\n } else if (block.type === \"tool_use\" || block.type === \"toolCall\") {\n const args =\n block.type === \"toolCall\"\n ? block.arguments\n : block.input;\n toolCalls.push({\n id: block.id,\n type: \"function\",\n function: {\n name: block.name,\n arguments:\n typeof args === \"string\"\n ? args\n : JSON.stringify(args ?? {}),\n },\n });\n }\n }\n\n const openaiMsg: OpenAIMessage = {\n role: \"assistant\",\n content: textParts.length > 0 ? textParts.join(\"\") : null,\n _headroomMeta: buildMeta(),\n };\n if (toolCalls.length > 0) {\n openaiMsg.tool_calls = toolCalls;\n }\n result.push(openaiMsg);\n }\n continue;\n }\n\n if (role === \"toolResult\" || role === \"tool_result\") {\n const content =\n typeof normalized.content === \"string\"\n ? normalized.content\n : Array.isArray(normalized.content)\n ? extractText(normalized.content)\n : JSON.stringify(normalized.content);\n\n result.push({\n role: \"tool\",\n content,\n tool_call_id:\n normalized.toolCallId ??\n normalized.tool_use_id ??\n normalized.id ??\n \"unknown\",\n _headroomMeta: buildMeta(),\n });\n continue;\n }\n\n // Fallback: pass through as user message\n result.push({\n role: \"user\",\n content:\n typeof normalized.content === \"string\"\n ? normalized.content\n : JSON.stringify(normalized.content),\n _headroomMeta: buildMeta(),\n });\n }\n\n return result;\n}\n\n/**\n * Convert compressed OpenAI messages back to AgentMessage format.\n */\nexport function openAIToAgent(messages: OpenAIMessage[]): any[] {\n const result: any[] = [];\n\n for (const msg of messages) {\n const meta = (msg._headroomMeta ?? {}) as Record<string, unknown>;\n const timestamp =\n typeof meta.timestamp === \"number\" ? meta.timestamp : Date.now();\n\n if (msg.role === \"system\") {\n result.push({\n role: \"system\",\n content: msg.content ?? \"\",\n timestamp,\n });\n continue;\n }\n\n if (msg.role === \"user\") {\n result.push({\n role: \"user\",\n content: msg.content ?? \"\",\n timestamp,\n });\n continue;\n }\n\n if (msg.role === \"assistant\") {\n const blocks: any[] = [];\n if (msg.content) {\n blocks.push({ type: \"text\", text: msg.content });\n }\n if (msg.tool_calls) {\n for (const tc of msg.tool_calls) {\n let input: any;\n try {\n input = JSON.parse(tc.function.arguments);\n } catch {\n input = tc.function.arguments ?? {};\n }\n // Emit OpenClaw-native block shape so downstream transports keep call linkage.\n blocks.push({\n type: \"toolCall\",\n id: tc.id,\n name: tc.function.name,\n arguments: input,\n });\n }\n }\n // OpenClaw's Pi agent expects content to always be an array for assistant messages\n // (it calls .flatMap() on it). Never flatten to a string.\n result.push({\n ...(meta as object),\n role: \"assistant\",\n content: blocks,\n api: typeof meta.api === \"string\" ? meta.api : \"headroom\",\n provider: typeof meta.provider === \"string\" ? meta.provider : \"headroom\",\n model: typeof meta.model === \"string\" ? meta.model : \"headroom\",\n usage:\n isRecord(meta.usage)\n ? meta.usage\n : {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason:\n typeof meta.stopReason === \"string\" ? meta.stopReason : \"stop\",\n timestamp,\n });\n continue;\n }\n\n if (msg.role === \"tool\") {\n const textContent =\n typeof msg.content === \"string\"\n ? msg.content\n : msg.content == null\n ? \"\"\n : JSON.stringify(msg.content);\n const toolCallId = msg.tool_call_id ?? \"unknown\";\n result.push({\n ...(meta as object),\n role: \"toolResult\",\n // OpenClaw transport layers expect toolResult content blocks, not a raw string.\n content: [{ type: \"text\", text: textContent }],\n toolCallId:\n typeof meta.toolCallId === \"string\" ? meta.toolCallId : toolCallId,\n tool_use_id:\n typeof meta.tool_use_id === \"string\" ? meta.tool_use_id : toolCallId,\n toolName:\n typeof meta.toolName === \"string\" ? meta.toolName : \"headroom\",\n isError: typeof meta.isError === \"boolean\" ? meta.isError : false,\n timestamp,\n });\n continue;\n }\n }\n\n return result;\n}\n\nexport function normalizeAgentMessages(messages: any[]): any[] {\n return messages.map((message) => normalizeAgentMessage(message));\n}\n\n/**\n * Extract text from content blocks.\n */\nfunction extractText(content: any): string {\n if (typeof content === \"string\") return content;\n if (!Array.isArray(content)) return JSON.stringify(content);\n\n return content\n .map((block: any) => {\n if (typeof block === \"string\") return block;\n if (block.type === \"text\") return block.text;\n if (block.type === \"tool_result\") {\n return typeof block.content === \"string\" ? block.content : JSON.stringify(block.content);\n }\n return \"\";\n })\n .filter(Boolean)\n .join(\"\\n\");\n}\n\nfunction normalizeAgentMessage(message: any): any {\n if (!isRecord(message)) return message;\n\n if (message.role === \"assistant\") {\n return normalizeAssistantMessage(message);\n }\n\n if (message.role === \"toolResult\" || message.role === \"tool_result\") {\n return normalizeToolResultMessage(message);\n }\n\n return message;\n}\n\nfunction normalizeAssistantMessage(message: Record<string, any>): Record<string, any> {\n const normalizedContent = normalizeAssistantContent(message.content);\n\n return {\n ...message,\n content: normalizedContent,\n api: typeof message.api === \"string\" ? message.api : \"headroom\",\n provider: typeof message.provider === \"string\" ? message.provider : \"headroom\",\n model: typeof message.model === \"string\" ? message.model : \"headroom\",\n usage: isRecord(message.usage)\n ? message.usage\n : {\n input: 0,\n output: 0,\n cacheRead: 0,\n cacheWrite: 0,\n totalTokens: 0,\n cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },\n },\n stopReason: typeof message.stopReason === \"string\" ? message.stopReason : \"stop\",\n timestamp: typeof message.timestamp === \"number\" ? message.timestamp : Date.now(),\n };\n}\n\nfunction normalizeToolResultMessage(message: Record<string, any>): Record<string, any> {\n const normalizedContent = normalizeToolResultContent(message.content);\n const toolCallId =\n typeof message.toolCallId === \"string\"\n ? message.toolCallId\n : typeof message.tool_use_id === \"string\"\n ? message.tool_use_id\n : typeof message.id === \"string\"\n ? message.id\n : \"unknown\";\n\n return {\n ...message,\n role: \"toolResult\",\n content: normalizedContent,\n toolCallId,\n tool_use_id:\n typeof message.tool_use_id === \"string\" ? message.tool_use_id : toolCallId,\n toolName: typeof message.toolName === \"string\" ? message.toolName : \"headroom\",\n isError: typeof message.isError === \"boolean\" ? message.isError : false,\n timestamp: typeof message.timestamp === \"number\" ? message.timestamp : Date.now(),\n };\n}\n\nfunction normalizeAssistantContent(content: unknown): any[] {\n if (Array.isArray(content)) {\n return content.flatMap((block) => {\n if (typeof block === \"string\") return [{ type: \"text\", text: block }];\n if (!isRecord(block) || typeof block.type !== \"string\") return [];\n if (block.type === \"text\" && typeof block.text === \"string\") return [block];\n if (block.type === \"thinking\" && typeof block.thinking === \"string\") return [block];\n if (\n (block.type === \"toolCall\" || block.type === \"tool_use\") &&\n typeof block.name === \"string\"\n ) {\n return [\n {\n type: \"toolCall\",\n id: typeof block.id === \"string\" ? block.id : \"unknown\",\n name: block.name,\n arguments:\n \"arguments\" in block\n ? block.arguments\n : \"input\" in block\n ? block.input\n : {},\n },\n ];\n }\n return [];\n });\n }\n\n if (typeof content === \"string\" && content.length > 0) {\n return [{ type: \"text\", text: content }];\n }\n\n if (content == null) {\n return [];\n }\n\n return [{ type: \"text\", text: JSON.stringify(content) }];\n}\n\nfunction normalizeToolResultContent(content: unknown): any[] {\n if (Array.isArray(content)) {\n return content.flatMap((block) => {\n if (typeof block === \"string\") return [{ type: \"text\", text: block }];\n if (!isRecord(block) || typeof block.type !== \"string\") return [];\n if (block.type === \"text\" && typeof block.text === \"string\") return [block];\n if (\n block.type === \"image\" &&\n typeof block.data === \"string\" &&\n typeof block.mimeType === \"string\"\n ) {\n return [block];\n }\n if (block.type === \"tool_result\" && \"content\" in block) {\n return normalizeToolResultContent(block.content);\n }\n return [];\n });\n }\n\n if (typeof content === \"string\" && content.length > 0) {\n return [{ type: \"text\", text: content }];\n }\n\n if (content == null) {\n return [];\n }\n\n return [{ type: \"text\", text: JSON.stringify(content) }];\n}\n\nfunction isRecord(value: unknown): value is Record<string, any> {\n return typeof value === \"object\" && value !== null && !Array.isArray(value);\n}\n","/* eslint-disable @typescript-eslint/no-explicit-any */\n\nexport const DEFAULT_GATEWAY_PROVIDER_IDS = [\"openai-codex\"] as const;\n\nconst DEFAULT_PROVIDER_BASE_URLS: Readonly<Record<string, string>> = {\n \"openai-codex\": \"https://chatgpt.com/backend-api\",\n};\n\nconst GATEWAY_PROVIDER_ID_ALIASES: Readonly<Record<string, string>> = {\n codex: \"openai-codex\",\n claude: \"anthropic\",\n copilot: \"github-copilot\",\n gemini: \"google\",\n};\n\nconst EXPLICIT_BASE_URL_REQUIRED_PROVIDER_IDS = new Set<string>([\"github-copilot\"]);\n\nexport function resolveGatewayProviderIds(config: Record<string, unknown> | undefined): string[] {\n const configuredProviderIds = normalizeGatewayProviderIds(config?.gatewayProviderIds);\n if (configuredProviderIds.length > 0) {\n return configuredProviderIds;\n }\n\n if (config?.routeCodexViaProxy === false) {\n return [];\n }\n\n return [...DEFAULT_GATEWAY_PROVIDER_IDS];\n}\n\nfunction normalizeGatewayProviderIds(value: unknown): string[] {\n if (!Array.isArray(value)) {\n return [];\n }\n\n const seen = new Set<string>();\n const normalized: string[] = [];\n\n for (const entry of value) {\n if (typeof entry !== \"string\") {\n continue;\n }\n\n const rawProviderId = entry.trim();\n const providerId = GATEWAY_PROVIDER_ID_ALIASES[rawProviderId.toLowerCase()] ?? rawProviderId;\n if (!providerId || seen.has(providerId)) {\n continue;\n }\n\n seen.add(providerId);\n normalized.push(providerId);\n }\n\n return normalized;\n}\n\nexport function applyGatewayProviderBaseUrls<T>(\n cfg: T,\n proxyUrl: string,\n providerIds: readonly string[],\n): { changed: boolean; config: T } {\n const next = structuredClone((cfg ?? {}) as any);\n const changed = applyGatewayProviderBaseUrlsInPlace(next, proxyUrl, providerIds);\n return { changed, config: next as T };\n}\n\nexport function applyGatewayProviderBaseUrlsInPlace(\n cfg: any,\n proxyUrl: string,\n providerIds: readonly string[],\n): boolean {\n if (!cfg || typeof cfg !== \"object\" || providerIds.length === 0) {\n return false;\n }\n\n const models = (cfg.models ??= {});\n const providers = (models.providers ??= {});\n let changed = false;\n\n for (const providerId of providerIds) {\n const currentValue = providers[providerId];\n const currentConfig =\n currentValue && typeof currentValue === \"object\" && !Array.isArray(currentValue)\n ? currentValue\n : {};\n const nextConfig = { ...currentConfig };\n const currentBaseUrl =\n typeof nextConfig.baseUrl === \"string\" && nextConfig.baseUrl.trim().length > 0\n ? nextConfig.baseUrl\n : undefined;\n const defaultBaseUrl = DEFAULT_PROVIDER_BASE_URLS[providerId];\n if (\n !currentBaseUrl &&\n !defaultBaseUrl &&\n EXPLICIT_BASE_URL_REQUIRED_PROVIDER_IDS.has(providerId)\n ) {\n continue;\n }\n const nextBaseUrl = routeBaseUrlThroughProxy({\n providerId,\n proxyUrl,\n currentBaseUrl,\n });\n\n if (!Array.isArray(nextConfig.models)) {\n nextConfig.models = [];\n changed = true;\n }\n\n if (nextConfig.baseUrl === nextBaseUrl) {\n providers[providerId] = nextConfig;\n continue;\n }\n\n nextConfig.baseUrl = nextBaseUrl;\n providers[providerId] = nextConfig;\n changed = true;\n }\n\n return changed;\n}\n\nfunction routeBaseUrlThroughProxy(params: {\n providerId: string;\n proxyUrl: string;\n currentBaseUrl?: string;\n}): string {\n const upstreamBaseUrl = params.currentBaseUrl ?? DEFAULT_PROVIDER_BASE_URLS[params.providerId];\n if (!upstreamBaseUrl) {\n return params.proxyUrl;\n }\n\n try {\n const proxy = new URL(params.proxyUrl);\n const upstream = new URL(upstreamBaseUrl);\n proxy.pathname = upstream.pathname;\n proxy.search = upstream.search;\n proxy.hash = \"\";\n return proxy.toString().replace(/\\/$/, \"\");\n } catch {\n return params.proxyUrl;\n }\n}\n","/**\n * CCR (Compress-Cache-Retrieve) tool for OpenClaw.\n *\n * Allows the agent to retrieve original uncompressed content\n * from the Headroom proxy's compression store.\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { normalizeAndValidateProxyUrl } from \"../proxy-manager.js\";\n\nexport interface RetrieveToolConfig {\n proxyUrl: string;\n}\n\nexport function createHeadroomRetrieveTool(config: RetrieveToolConfig) {\n const proxyOrigin = normalizeAndValidateProxyUrl(config.proxyUrl);\n\n return {\n name: \"headroom_retrieve\",\n description:\n \"Retrieve original uncompressed content from Headroom's compression store. \" +\n \"Use when compressed context mentions a hash and you need the full details. \" +\n \"Pass the hash from the compression marker (24 hex characters). \" +\n \"Retrieval is by hash and always returns the full original content.\",\n parameters: {\n type: \"object\" as const,\n properties: {\n hash: {\n type: \"string\",\n description: \"The 24-character hex hash from the compression marker\",\n },\n },\n required: [\"hash\"],\n },\n execute: async (args: { hash: string }): Promise<string> => {\n const { hash } = args;\n\n // Validate hash format\n if (!/^[a-f0-9]{24}$/i.test(hash)) {\n return JSON.stringify({\n error: \"Invalid hash format. Expected 24 hex characters.\",\n });\n }\n\n try {\n const url = `${proxyOrigin}/v1/retrieve/${hash}`;\n\n const resp = await fetch(url, {\n signal: AbortSignal.timeout(10_000),\n });\n\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n return JSON.stringify({\n error: `Retrieval failed: HTTP ${resp.status}`,\n details: body,\n });\n }\n\n const data = await resp.json();\n return typeof data === \"string\" ? data : JSON.stringify(data);\n } catch (error) {\n return JSON.stringify({\n error: `Retrieval failed: ${error}`,\n hint: \"The compressed content may have expired (default TTL: 30 minutes)\",\n });\n }\n },\n };\n}\n","/**\n * Headroom OpenClaw Plugin — register ContextEngine + CCR retrieval tool.\n *\n * Usage:\n * openclaw plugins install headroom-ai/openclaw\n *\n * Configuration (in ~/.openclaw/config.json or ~/.clawdbot/clawdbot.json):\n * {\n * \"plugins\": {\n * \"slots\": { \"contextEngine\": \"headroom\" },\n * \"entries\": { \"headroom\": { \"enabled\": true } }\n * }\n * }\n */\n\n/* eslint-disable @typescript-eslint/no-explicit-any */\n\nimport { HeadroomContextEngine } from \"../engine.js\";\nimport {\n applyGatewayProviderBaseUrlsInPlace,\n resolveGatewayProviderIds,\n} from \"../gateway-config.js\";\nimport { normalizeAndValidateProxyUrl, probeHeadroomProxy } from \"../proxy-manager.js\";\nimport { createHeadroomRetrieveTool } from \"../tools/headroom-retrieve.js\";\n\n/**\n * OpenClaw 2026.x plugin API requires a `{ register(api) }` object export.\n * The previous bare-function default export was silently skipped by the loader.\n * See: https://github.com/chopratejas/headroom/issues/XXX\n */\nexport default {\n register: registerHeadroomPlugin,\n};\n\nexport function registerHeadroomPlugin(api: any) {\n const config = api.config?.plugins?.entries?.headroom?.config ?? {};\n const logger = api.logger ?? console;\n const rawProxyUrl = config.proxyUrl;\n const proxyUrl =\n typeof rawProxyUrl === \"string\" && rawProxyUrl.trim().length > 0\n ? normalizeAndValidateProxyUrl(rawProxyUrl)\n : undefined;\n\n const engine = new HeadroomContextEngine({ ...config, proxyUrl }, {\n info: (m: string) => logger.info(m),\n warn: (m: string) => logger.warn(m),\n error: (m: string) => logger.error(m),\n debug: (m: string) => logger.debug?.(m),\n });\n const gatewayProviderIds = resolveGatewayProviderIds(config);\n let validatedConfiguredProxyUrl: string | null = null;\n let configuredProxyProbePromise: Promise<string | null> | null = null;\n\n const applyGatewayRouting = async (activeProxyUrl: string) => {\n if (gatewayProviderIds.length === 0) {\n return;\n }\n\n try {\n const changed = applyGatewayProviderBaseUrlsInPlace(api.config, activeProxyUrl, gatewayProviderIds);\n\n if (changed) {\n logger.info(\n `[headroom] Routed ${gatewayProviderIds.join(\", \")} through Headroom proxy in memory at ${activeProxyUrl}`,\n );\n } else {\n logger.info(\n `[headroom] Upstream gateway already routed in memory for ${gatewayProviderIds.join(\", \")} at ${activeProxyUrl}`,\n );\n }\n } catch (error) {\n logger.warn(`[headroom] Failed to configure upstream gateway routing: ${error}`);\n }\n };\n\n const getConfiguredRoutingProxyUrl = async (): Promise<string | null> => {\n if (!proxyUrl) {\n return null;\n }\n if (validatedConfiguredProxyUrl === proxyUrl) {\n return validatedConfiguredProxyUrl;\n }\n if (!configuredProxyProbePromise) {\n configuredProxyProbePromise = probeHeadroomProxy(proxyUrl)\n .then((probe) => {\n if (probe.reachable && probe.isHeadroom) {\n validatedConfiguredProxyUrl = proxyUrl;\n return proxyUrl;\n }\n logger.warn(\n `[headroom] Skipping upstream gateway routing: configured proxyUrl is not a ready Headroom proxy at ${proxyUrl}` +\n (probe.reason ? ` (${probe.reason})` : \"\"),\n );\n return null;\n })\n .catch((error) => {\n logger.warn(\n `[headroom] Skipping upstream gateway routing: failed to probe configured proxyUrl ${proxyUrl}: ${error}`,\n );\n return null;\n })\n .finally(() => {\n configuredProxyProbePromise = null;\n });\n }\n return configuredProxyProbePromise;\n };\n\n const ensureGatewayRouting = async () => {\n if (gatewayProviderIds.length === 0) {\n return;\n }\n const activeProxyUrl = engine.getProxyUrl() ?? (await getConfiguredRoutingProxyUrl());\n if (!activeProxyUrl) {\n logger.debug?.(\"[headroom] Deferring upstream gateway routing until proxy is available\");\n return;\n }\n await applyGatewayRouting(activeProxyUrl);\n };\n\n engine.onProxyReady(async (activeProxyUrl) => {\n await applyGatewayRouting(activeProxyUrl);\n });\n\n // Register as context engine\n api.registerContextEngine(\"headroom\", () => engine);\n\n // Register CCR retrieval tool (active once proxy is running)\n api.registerTool((ctx: any) => {\n const activeProxyUrl = engine.getProxyUrl() ?? proxyUrl;\n if (!activeProxyUrl) return null;\n return createHeadroomRetrieveTool({ proxyUrl: activeProxyUrl });\n }, { names: [\"headroom_retrieve\"] });\n\n api.on(\"gateway_start\", async () => {\n await ensureGatewayRouting();\n });\n\n void ensureGatewayRouting();\n\n logger.info(\"[headroom] Plugin registered\");\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,125 @@
1
+ {
2
+ "id": "headroom",
3
+ "kind": "context-engine",
4
+ "uiHints": {
5
+ "proxyUrl": {
6
+ "label": "Proxy URL",
7
+ "help": "Optional. URL Headroom proxy (example: http://127.0.0.1:8787 or https://headroom.example.com). Configured URLs probe-gated before provider routing. Auto-start opt-in only works local addresses."
8
+ },
9
+ "proxyPort": {
10
+ "label": "Proxy Port",
11
+ "help": "Default port used for auto-detect/auto-start when proxyUrl is not set (default: 8787)."
12
+ },
13
+ "pythonPath": {
14
+ "label": "Python Path",
15
+ "help": "Optional explicit python executable for python fallback launcher (for example: python, python3, py, or full path)."
16
+ },
17
+ "retryMaxAttempts": {
18
+ "label": "Retry Max Attempts",
19
+ "help": "Optional maximum number of upstream retry attempts for connection/read/5xx failures when the plugin auto-starts a local Headroom proxy. Lower values fail faster for interactive chat."
20
+ },
21
+ "connectTimeoutSeconds": {
22
+ "label": "Connect Timeout Seconds",
23
+ "help": "Optional upstream connection timeout for the auto-started local Headroom proxy. Lower values surface network failures sooner."
24
+ },
25
+ "requestTimeoutMs": {
26
+ "label": "Compression Request Timeout",
27
+ "help": "Maximum milliseconds to wait for one compression request before returning the original messages."
28
+ },
29
+ "circuitBreakerThreshold": {
30
+ "label": "Circuit Breaker Threshold",
31
+ "help": "Consecutive compression failures before temporarily bypassing Headroom."
32
+ },
33
+ "circuitBreakerCooldownMs": {
34
+ "label": "Circuit Breaker Cooldown",
35
+ "help": "Milliseconds to bypass compression after the circuit breaker opens."
36
+ },
37
+ "routeCodexViaProxy": {
38
+ "label": "Route OpenAI Codex Via Headroom",
39
+ "help": "When enabled, OpenClaw will use the active Headroom proxy as the in-memory upstream base URL for the built-in openai-codex provider so provider traffic flows through Headroom."
40
+ },
41
+ "gatewayProviderIds": {
42
+ "label": "Gateway Provider IDs",
43
+ "help": "Optional list of OpenClaw provider ids to route through the active Headroom proxy in memory. Friendly aliases codex, claude, copilot, and gemini are also accepted. When set, this overrides the default openai-codex-only routing."
44
+ }
45
+ },
46
+ "configSchema": {
47
+ "type": "object",
48
+ "additionalProperties": false,
49
+ "properties": {
50
+ "enabled": {
51
+ "type": "boolean"
52
+ },
53
+ "proxyUrl": {
54
+ "type": "string",
55
+ "pattern": "^https?:\\/\\/.+(:\\d+)?\\/?$"
56
+ },
57
+ "proxyPort": {
58
+ "type": "integer",
59
+ "minimum": 1,
60
+ "maximum": 65535,
61
+ "default": 8787
62
+ },
63
+ "pythonPath": {
64
+ "type": "string"
65
+ },
66
+ "autoStart": {
67
+ "type": "boolean",
68
+ "default": false
69
+ },
70
+ "startupTimeoutMs": {
71
+ "type": "integer",
72
+ "minimum": 1000,
73
+ "maximum": 120000,
74
+ "default": 20000
75
+ },
76
+ "retryMaxAttempts": {
77
+ "type": "integer",
78
+ "minimum": 1
79
+ },
80
+ "connectTimeoutSeconds": {
81
+ "type": "integer",
82
+ "minimum": 1
83
+ },
84
+ "requestTimeoutMs": {
85
+ "type": "integer",
86
+ "minimum": 1,
87
+ "default": 30000
88
+ },
89
+ "circuitBreakerThreshold": {
90
+ "type": "integer",
91
+ "minimum": 1,
92
+ "default": 3
93
+ },
94
+ "circuitBreakerCooldownMs": {
95
+ "type": "integer",
96
+ "minimum": 1,
97
+ "default": 60000
98
+ },
99
+ "routeCodexViaProxy": {
100
+ "type": "boolean",
101
+ "default": true
102
+ },
103
+ "gatewayProviderIds": {
104
+ "type": "array",
105
+ "items": {
106
+ "type": "string"
107
+ },
108
+ "default": []
109
+ }
110
+ }
111
+ },
112
+ "capabilities": {
113
+ "network": {
114
+ "allow": [
115
+ "http://*:*",
116
+ "https://*:*"
117
+ ]
118
+ }
119
+ },
120
+ "contracts": {
121
+ "tools": [
122
+ "headroom_retrieve"
123
+ ]
124
+ }
125
+ }
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "headroom-openclaw",
3
+ "version": "0.36.0",
4
+ "description": "Headroom context compression plugin for OpenClaw — 70-90% token savings with zero LLM calls",
5
+ "type": "module",
6
+ "main": "./index.js",
7
+ "types": "./index.d.ts",
8
+ "license": "Apache-2.0",
9
+ "dependencies": {
10
+ "headroom-ai": "^0.36.0"
11
+ },
12
+ "peerDependencies": {
13
+ "openclaw": "*"
14
+ },
15
+ "peerDependenciesMeta": {
16
+ "openclaw": {
17
+ "optional": true
18
+ }
19
+ },
20
+ "openclaw": {
21
+ "hooks": [
22
+ "./hook-shim"
23
+ ],
24
+ "extensions": [
25
+ "./index.js"
26
+ ],
27
+ "capabilities": {
28
+ "network": {
29
+ "allow": [
30
+ "http://*:*",
31
+ "https://*:*"
32
+ ]
33
+ }
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: headroom-link-shim
3
+ description: "No-op hook shim so local plugin source paths are also valid OpenClaw hook-pack paths."
4
+ metadata:
5
+ {
6
+ "openclaw":
7
+ {
8
+ "emoji": "🪝",
9
+ "events": ["command"],
10
+ },
11
+ }
12
+ ---
13
+
14
+ # Headroom Link Shim
15
+
16
+ This hook intentionally does nothing.
17
+
18
+ OpenClaw currently falls back to validating local plugin paths as hook packs when a
19
+ plugin install cannot proceed, such as when the plugin is already installed.
20
+ Including this no-op hook keeps `--link` installs from reporting a misleading
21
+ `package.json missing openclaw.hooks` error for valid Headroom plugin paths.
@@ -0,0 +1,3 @@
1
+ export default async function headroomLinkShim() {
2
+ return undefined;
3
+ }
@@ -1,17 +1,46 @@
1
1
  {
2
2
  "id": "headroom",
3
+ "kind": "context-engine",
3
4
  "uiHints": {
4
5
  "proxyUrl": {
5
6
  "label": "Proxy URL",
6
- "help": "URL of the Headroom proxy (auto-detected or auto-started if not set)"
7
+ "help": "Optional. URL Headroom proxy (example: http://127.0.0.1:8787 or https://headroom.example.com). Configured URLs probe-gated before provider routing. Auto-start opt-in only works local addresses."
8
+ },
9
+ "proxyPort": {
10
+ "label": "Proxy Port",
11
+ "help": "Default port used for auto-detect/auto-start when proxyUrl is not set (default: 8787)."
7
12
  },
8
13
  "pythonPath": {
9
14
  "label": "Python Path",
10
- "help": "Path to Python binary (auto-detected if not set)"
15
+ "help": "Optional explicit python executable for python fallback launcher (for example: python, python3, py, or full path)."
16
+ },
17
+ "retryMaxAttempts": {
18
+ "label": "Retry Max Attempts",
19
+ "help": "Optional maximum number of upstream retry attempts for connection/read/5xx failures when the plugin auto-starts a local Headroom proxy. Lower values fail faster for interactive chat."
20
+ },
21
+ "connectTimeoutSeconds": {
22
+ "label": "Connect Timeout Seconds",
23
+ "help": "Optional upstream connection timeout for the auto-started local Headroom proxy. Lower values surface network failures sooner."
24
+ },
25
+ "requestTimeoutMs": {
26
+ "label": "Compression Request Timeout",
27
+ "help": "Maximum milliseconds to wait for one compression request before returning the original messages."
11
28
  },
12
- "autoStart": {
13
- "label": "Auto-Start Proxy",
14
- "help": "Automatically start the Headroom proxy if not already running"
29
+ "circuitBreakerThreshold": {
30
+ "label": "Circuit Breaker Threshold",
31
+ "help": "Consecutive compression failures before temporarily bypassing Headroom."
32
+ },
33
+ "circuitBreakerCooldownMs": {
34
+ "label": "Circuit Breaker Cooldown",
35
+ "help": "Milliseconds to bypass compression after the circuit breaker opens."
36
+ },
37
+ "routeCodexViaProxy": {
38
+ "label": "Route OpenAI Codex Via Headroom",
39
+ "help": "When enabled, OpenClaw will use the active Headroom proxy as the in-memory upstream base URL for the built-in openai-codex provider so provider traffic flows through Headroom."
40
+ },
41
+ "gatewayProviderIds": {
42
+ "label": "Gateway Provider IDs",
43
+ "help": "Optional list of OpenClaw provider ids to route through the active Headroom proxy in memory. Friendly aliases codex, claude, copilot, and gemini are also accepted. When set, this overrides the default openai-codex-only routing."
15
44
  }
16
45
  },
17
46
  "configSchema": {
@@ -22,20 +51,75 @@
22
51
  "type": "boolean"
23
52
  },
24
53
  "proxyUrl": {
25
- "type": "string"
54
+ "type": "string",
55
+ "pattern": "^https?:\\/\\/.+(:\\d+)?\\/?$"
56
+ },
57
+ "proxyPort": {
58
+ "type": "integer",
59
+ "minimum": 1,
60
+ "maximum": 65535,
61
+ "default": 8787
26
62
  },
27
63
  "pythonPath": {
28
64
  "type": "string"
29
65
  },
30
66
  "autoStart": {
31
67
  "type": "boolean",
32
- "default": true
68
+ "default": false
33
69
  },
34
- "proxyPort": {
70
+ "startupTimeoutMs": {
71
+ "type": "integer",
72
+ "minimum": 1000,
73
+ "maximum": 120000,
74
+ "default": 20000
75
+ },
76
+ "retryMaxAttempts": {
35
77
  "type": "integer",
36
- "minimum": 0,
37
- "maximum": 65535
78
+ "minimum": 1
79
+ },
80
+ "connectTimeoutSeconds": {
81
+ "type": "integer",
82
+ "minimum": 1
83
+ },
84
+ "requestTimeoutMs": {
85
+ "type": "integer",
86
+ "minimum": 1,
87
+ "default": 30000
88
+ },
89
+ "circuitBreakerThreshold": {
90
+ "type": "integer",
91
+ "minimum": 1,
92
+ "default": 3
93
+ },
94
+ "circuitBreakerCooldownMs": {
95
+ "type": "integer",
96
+ "minimum": 1,
97
+ "default": 60000
98
+ },
99
+ "routeCodexViaProxy": {
100
+ "type": "boolean",
101
+ "default": true
102
+ },
103
+ "gatewayProviderIds": {
104
+ "type": "array",
105
+ "items": {
106
+ "type": "string"
107
+ },
108
+ "default": []
38
109
  }
39
110
  }
111
+ },
112
+ "capabilities": {
113
+ "network": {
114
+ "allow": [
115
+ "http://*:*",
116
+ "https://*:*"
117
+ ]
118
+ }
119
+ },
120
+ "contracts": {
121
+ "tools": [
122
+ "headroom_retrieve"
123
+ ]
40
124
  }
41
125
  }