/** * OpenClaw Integration * * Provides seamless integration with OpenClaw - the open-source AI-powered * development assistant. Enables context compaction, pipeline orchestration, * and multi-agent coordination within OpenClaw workflows. * * @see https://github.com/ggondim/openclaw */ import { ContextManager, ContextManagerConfig } from '../core/context-manager'; import { TokenCounter } from '../core/token-counter'; import { Summarizer } from '../core/summarizer'; import { Orchestrator, Task, AgentStatus } from '../core/orchestrator'; import { SubagentSpawner, SubagentType } from '../core/subagent-spawner'; import { MemoryStore } from '../storage/memory-store'; // ============================================================================ // Types // ============================================================================ export interface OpenClawConfig { /** Maximum tokens for context */ maxContextTokens?: number; /** Reserve tokens for response */ reserveTokens?: number; /** Compaction strategy */ compactionStrategy?: 'sliding-window' | 'summarize-old' | 'priority-retention' | 'hybrid'; /** Priority keywords for context retention */ priorityKeywords?: string[]; /** Enable automatic compaction */ autoCompact?: boolean; /** Compaction threshold (0-1) */ compactionThreshold?: number; /** Working directory */ workingDirectory?: string; /** Enable workspace isolation */ workspaceIsolation?: boolean; /** Enable persistent memory */ persistentMemory?: boolean; /** Memory store path */ memoryStorePath?: string; /** Enable Lobster workflow support */ enableLobsterWorkflows?: boolean; /** Enable parallel execution */ enableParallelExecution?: boolean; /** Max parallel agents */ maxParallelAgents?: number; /** Hook callbacks */ hooks?: { onCompactionStart?: (context: OpenClawContext) => void | Promise; onCompactionEnd?: (result: OpenClawCompactionResult) => void | Promise; onAgentSpawn?: (agent: OpenClawAgent) => void | Promise; onAgentComplete?: (agent: OpenClawAgent, result: any) => void | Promise; onPipelineStart?: (pipeline: OpenClawPipeline) => void | Promise; onPipelineComplete?: (pipeline: OpenClawPipeline, result: any) => void | Promise; onStateTransition?: (from: string, to: string, context: any) => void | Promise; }; } export interface OpenClawContext { id: string; projectId?: string; conversationId?: string; messages: OpenClawMessage[]; metadata: Record; createdAt: Date; updatedAt: Date; } export interface OpenClawMessage { id: string; role: 'user' | 'assistant' | 'system'; content: string; timestamp: number; tokens?: number; priority?: number; tags?: string[]; references?: { files?: string[]; functions?: string[]; symbols?: string[]; }; } export interface OpenClawAgent { id: string; type: SubagentType; status: 'idle' | 'running' | 'completed' | 'error'; workspace?: string; memory: Record; createdAt: Date; startedAt?: Date; completedAt?: Date; result?: any; } export interface OpenClawPipeline { id: string; name: string; description?: string; states: OpenClawPipelineState[]; currentState: string; history: OpenClawPipelineTransition[]; status: 'idle' | 'running' | 'completed' | 'error' | 'paused'; createdAt: Date; startedAt?: Date; completedAt?: Date; } export interface OpenClawPipelineState { name: string; type: 'sequential' | 'parallel' | 'conditional' | 'human-approval'; agents?: SubagentType[]; onEnter?: string; onExit?: string; transitions: { target: string; event: string; condition?: string; }[]; timeout?: number; retryPolicy?: { maxAttempts: number; backoff: 'fixed' | 'exponential'; delay: number; }; } export interface OpenClawPipelineTransition { from: string; to: string; event: string; timestamp: Date; context?: any; } export interface OpenClawCompactionResult { success: boolean; tokensBefore: number; tokensAfter: number; tokensSaved: number; messagesRemoved: number; summary?: string; keyPoints?: string[]; decisions?: string[]; timestamp: Date; } export interface OpenClawWorkspace { id: string; path: string; permissions: ('read' | 'write' | 'execute')[]; quota: { maxFiles: number; maxSize: number; }; createdAt: Date; } // ============================================================================ // OpenClaw Integration Class // ============================================================================ export class OpenClawIntegration { private contextManager: ContextManager; private tokenCounter: TokenCounter; private summarizer: Summarizer; private orchestrator: Orchestrator | null = null; private subagentSpawner: SubagentSpawner | null = null; private memoryStore: MemoryStore | null = null; private config: Required; private context: OpenClawContext; private agents: Map = new Map(); private pipelines: Map = new Map(); private workspaces: Map = new Map(); private compactionHistory: OpenClawCompactionResult[] = []; constructor(config: OpenClawConfig = {}) { this.config = { maxContextTokens: config.maxContextTokens ?? 200000, reserveTokens: config.reserveTokens ?? 40000, compactionStrategy: config.compactionStrategy ?? 'hybrid', priorityKeywords: config.priorityKeywords ?? [ 'error', 'important', 'decision', 'critical', 'remember', 'todo', 'fixme', 'security', 'breaking' ], autoCompact: config.autoCompact ?? true, compactionThreshold: config.compactionThreshold ?? 0.75, workingDirectory: config.workingDirectory ?? process.cwd(), workspaceIsolation: config.workspaceIsolation ?? true, persistentMemory: config.persistentMemory ?? true, memoryStorePath: config.memoryStorePath ?? '.openclaw/memory', enableLobsterWorkflows: config.enableLobsterWorkflows ?? true, enableParallelExecution: config.enableParallelExecution ?? true, maxParallelAgents: config.maxParallelAgents ?? 12, hooks: config.hooks ?? {} }; // Initialize core components this.tokenCounter = new TokenCounter(this.config.maxContextTokens); this.summarizer = new Summarizer(); this.contextManager = new ContextManager( this.tokenCounter, this.summarizer, { maxTokens: this.config.maxContextTokens - this.config.reserveTokens, compactionStrategy: this.config.compactionStrategy, priorityKeywords: this.config.priorityKeywords, reserveTokens: this.config.reserveTokens } ); // Initialize orchestrator for parallel execution if (this.config.enableParallelExecution) { this.orchestrator = new Orchestrator({ maxAgents: this.config.maxParallelAgents, taskTimeout: 600000, retryAttempts: 3 }); this.subagentSpawner = new SubagentSpawner(); } // Initialize memory store if (this.config.persistentMemory) { this.memoryStore = new MemoryStore(this.config.memoryStorePath); } // Initialize context this.context = this.createInitialContext(); } // ============================================================================ // Context Management // ============================================================================ private createInitialContext(): OpenClawContext { return { id: this.generateId('ctx'), messages: [], metadata: {}, createdAt: new Date(), updatedAt: new Date() }; } private generateId(prefix: string): string { return `${prefix}_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; } /** * Get current context */ getContext(): OpenClawContext { return { ...this.context }; } /** * Set context metadata */ setContextMetadata(key: string, value: any): void { this.context.metadata[key] = value; this.context.updatedAt = new Date(); } /** * Add message to context */ addMessage(message: Omit): OpenClawMessage { const fullMessage: OpenClawMessage = { ...message, id: this.generateId('msg'), timestamp: Date.now(), tokens: this.tokenCounter.countTokens(message.content) }; this.context.messages.push(fullMessage); this.context.updatedAt = new Date(); // Add to context manager this.contextManager.addMessage({ role: message.role, content: message.content, priority: message.priority, timestamp: fullMessage.timestamp }); this.tokenCounter.addUsage(fullMessage.tokens || 0); // Auto-compact if needed if (this.config.autoCompact && this.needsCompaction()) { this.compact(); } return fullMessage; } /** * Get messages from context */ getMessages(options?: { limit?: number; since?: number; tags?: string[]; }): OpenClawMessage[] { let messages = [...this.context.messages]; if (options?.since) { messages = messages.filter(m => m.timestamp >= options.since!); } if (options?.tags && options.tags.length > 0) { messages = messages.filter(m => m.tags?.some(t => options.tags!.includes(t)) ); } if (options?.limit) { messages = messages.slice(-options.limit); } return messages; } // ============================================================================ // Context Compaction // ============================================================================ /** * Check if compaction is needed */ needsCompaction(): boolean { return this.tokenCounter.getUsagePercentage() >= this.config.compactionThreshold; } /** * Perform context compaction */ async compact(): Promise { await this.config.hooks.onCompactionStart?.(this.context); const tokensBefore = this.tokenCounter.getCurrentUsage(); try { const result = await this.contextManager.compact(); const activeContext = this.contextManager.getActiveContext(); // Update context messages this.context.messages = activeContext.messages.map(m => ({ id: this.generateId('msg'), role: m.role as 'user' | 'assistant' | 'system', content: m.content, timestamp: m.timestamp || Date.now(), priority: m.priority })); // Recalculate tokens this.tokenCounter.reset(); for (const msg of this.context.messages) { this.tokenCounter.addUsage(this.tokenCounter.countTokens(msg.content)); } const compactionResult: OpenClawCompactionResult = { success: true, tokensBefore, tokensAfter: this.tokenCounter.getCurrentUsage(), tokensSaved: tokensBefore - this.tokenCounter.getCurrentUsage(), messagesRemoved: result.messagesRemoved, summary: result.summary, keyPoints: result.keyPoints, decisions: result.decisions, timestamp: new Date() }; this.compactionHistory.push(compactionResult); await this.config.hooks.onCompactionEnd?.(compactionResult); return compactionResult; } catch (error) { const failedResult: OpenClawCompactionResult = { success: false, tokensBefore, tokensAfter: tokensBefore, tokensSaved: 0, messagesRemoved: 0, timestamp: new Date() }; await this.config.hooks.onCompactionEnd?.(failedResult); return failedResult; } } /** * Get compaction history */ getCompactionHistory(): OpenClawCompactionResult[] { return [...this.compactionHistory]; } /** * Get token statistics */ getTokenStats(): { used: number; total: number; remaining: number; percentage: number; messages: number; } { return { used: this.tokenCounter.getCurrentUsage(), total: this.config.maxContextTokens, remaining: this.tokenCounter.getRemainingBudget(), percentage: this.tokenCounter.getUsagePercentage() * 100, messages: this.context.messages.length }; } // ============================================================================ // Agent Management // ============================================================================ /** * Spawn an agent */ async spawnAgent(type: SubagentType, options?: { workspace?: string; memory?: Record; }): Promise { const agent: OpenClawAgent = { id: this.generateId('agent'), type, status: 'idle', workspace: options?.workspace, memory: options?.memory || {}, createdAt: new Date() }; this.agents.set(agent.id, agent); await this.config.hooks.onAgentSpawn?.(agent); return agent; } /** * Execute an agent task */ async executeAgent(agentId: string, task: { prompt: string; context?: Record; timeout?: number; }): Promise { const agent = this.agents.get(agentId); if (!agent) { throw new Error(`Agent ${agentId} not found`); } agent.status = 'running'; agent.startedAt = new Date(); try { if (!this.subagentSpawner) { throw new Error('Subagent spawner not initialized'); } const subagent = this.subagentSpawner.spawn(agent.type, { taskId: agentId, memory: this.memoryStore || undefined }); const result = await subagent.execute({ prompt: task.prompt, ...task.context }); agent.status = 'completed'; agent.completedAt = new Date(); agent.result = result; await this.config.hooks.onAgentComplete?.(agent, result); return result; } catch (error) { agent.status = 'error'; agent.completedAt = new Date(); agent.result = { error: error instanceof Error ? error.message : 'Unknown error' }; throw error; } } /** * Execute multiple agents in parallel (OpenClaw pattern: 4 projects × 3 roles) */ async executeParallelAgents(tasks: Array<{ type: SubagentType; prompt: string; context?: Record; }>): Promise> { const results = new Map(); // Spawn all agents const agentPromises = tasks.map(async (task, index) => { const agent = await this.spawnAgent(task.type); const result = await this.executeAgent(agent.id, task); results.set(agent.id, result); return { agentId: agent.id, result }; }); await Promise.all(agentPromises); return results; } /** * Get agent status */ getAgentStatus(agentId: string): OpenClawAgent | undefined { return this.agents.get(agentId); } /** * List all agents */ listAgents(options?: { type?: SubagentType; status?: OpenClawAgent['status']; }): OpenClawAgent[] { let agents = Array.from(this.agents.values()); if (options?.type) { agents = agents.filter(a => a.type === options.type); } if (options?.status) { agents = agents.filter(a => a.status === options.status); } return agents; } // ============================================================================ // Pipeline Management // ============================================================================ /** * Create a pipeline from definition */ createPipeline(definition: { name: string; description?: string; states: OpenClawPipelineState[]; }): OpenClawPipeline { const pipeline: OpenClawPipeline = { id: this.generateId('pipeline'), name: definition.name, description: definition.description, states: definition.states, currentState: definition.states[0]?.name || 'start', history: [], status: 'idle', createdAt: new Date() }; this.pipelines.set(pipeline.id, pipeline); return pipeline; } /** * Start a pipeline */ async startPipeline(pipelineId: string, initialContext?: any): Promise { const pipeline = this.pipelines.get(pipelineId); if (!pipeline) { throw new Error(`Pipeline ${pipelineId} not found`); } pipeline.status = 'running'; pipeline.startedAt = new Date(); const currentState = pipeline.states.find(s => s.name === pipeline.currentState); if (currentState?.onEnter) { await this.executeStateAction(currentState.onEnter, initialContext); } await this.config.hooks.onPipelineStart?.(pipeline); } /** * Transition pipeline state */ async transitionPipeline(pipelineId: string, event: string, context?: any): Promise { const pipeline = this.pipelines.get(pipelineId); if (!pipeline) { throw new Error(`Pipeline ${pipelineId} not found`); } const currentState = pipeline.states.find(s => s.name === pipeline.currentState); if (!currentState) return false; const transition = currentState.transitions.find(t => t.event === event); if (!transition) return false; const from = pipeline.currentState; const to = transition.target; // Execute exit action if (currentState.onExit) { await this.executeStateAction(currentState.onExit, context); } // Record transition pipeline.history.push({ from, to, event, timestamp: new Date(), context }); // Update state pipeline.currentState = to; // Execute enter action const nextState = pipeline.states.find(s => s.name === to); if (nextState?.onEnter) { await this.executeStateAction(nextState.onEnter, context); } await this.config.hooks.onStateTransition?.(from, to, context); // Check if final state if (nextState && nextState.transitions.length === 0) { pipeline.status = 'completed'; pipeline.completedAt = new Date(); await this.config.hooks.onPipelineComplete?.(pipeline, context); } return true; } private async executeStateAction(action: string, context: any): Promise { // Action can be a command or agent task if (action.startsWith('agent:')) { const agentType = action.substring(6) as SubagentType; await this.spawnAgent(agentType); } // Custom action handling can be extended } /** * Get pipeline status */ getPipelineStatus(pipelineId: string): OpenClawPipeline | undefined { return this.pipelines.get(pipelineId); } /** * Create pipeline from Lobster YAML workflow */ createPipelineFromYAML(yaml: string): OpenClawPipeline { // Parse YAML (simplified - in production use a YAML parser) const lines = yaml.split('\n'); let name = 'unnamed'; let description = ''; const states: OpenClawPipelineState[] = []; // Basic YAML parsing for Lobster format // In production, use js-yaml or similar library return this.createPipeline({ name, description, states }); } // ============================================================================ // Workspace Management // ============================================================================ /** * Create an isolated workspace */ async createWorkspace(options?: { permissions?: ('read' | 'write' | 'execute')[]; quota?: { maxFiles: number; maxSize: number }; }): Promise { const workspace: OpenClawWorkspace = { id: this.generateId('ws'), path: `${this.config.workingDirectory}/.openclaw/workspaces/${Date.now()}`, permissions: options?.permissions || ['read', 'write'], quota: options?.quota || { maxFiles: 1000, maxSize: 100 * 1024 * 1024 }, createdAt: new Date() }; this.workspaces.set(workspace.id, workspace); return workspace; } /** * Get workspace */ getWorkspace(workspaceId: string): OpenClawWorkspace | undefined { return this.workspaces.get(workspaceId); } /** * Destroy workspace */ async destroyWorkspace(workspaceId: string): Promise { this.workspaces.delete(workspaceId); } // ============================================================================ // Memory Management // ============================================================================ /** * Store value in memory */ async remember(key: string, value: any): Promise { if (this.memoryStore) { await this.memoryStore.set(`openclaw:${this.context.id}:${key}`, value); } } /** * Retrieve value from memory */ async recall(key: string): Promise { if (this.memoryStore) { return this.memoryStore.get(`openclaw:${this.context.id}:${key}`); } return null; } /** * Save context for later restoration */ async saveContext(name: string): Promise { if (this.memoryStore) { await this.memoryStore.set(`context:${name}`, { ...this.context, tokenUsage: this.tokenCounter.getCurrentUsage() }); } } /** * Load a saved context */ async loadContext(name: string): Promise { if (this.memoryStore) { const saved = await this.memoryStore.get<{ messages: OpenClawMessage[]; metadata: Record; }>(`context:${name}`); if (saved) { this.context.messages = saved.messages; this.context.metadata = saved.metadata; this.tokenCounter.reset(); for (const msg of this.context.messages) { this.tokenCounter.addUsage(this.tokenCounter.countTokens(msg.content)); } return true; } } return false; } // ============================================================================ // Utility Methods // ============================================================================ /** * Reset the integration */ reset(): void { this.context = this.createInitialContext(); this.agents.clear(); this.pipelines.clear(); this.compactionHistory = []; this.tokenCounter.reset(); this.contextManager = new ContextManager( this.tokenCounter, this.summarizer, { maxTokens: this.config.maxContextTokens - this.config.reserveTokens, compactionStrategy: this.config.compactionStrategy, priorityKeywords: this.config.priorityKeywords, reserveTokens: this.config.reserveTokens } ); } /** * Export full state */ exportState(): { context: OpenClawContext; agents: OpenClawAgent[]; pipelines: OpenClawPipeline[]; compactionHistory: OpenClawCompactionResult[]; config: Required; } { return { context: this.context, agents: Array.from(this.agents.values()), pipelines: Array.from(this.pipelines.values()), compactionHistory: this.compactionHistory, config: this.config }; } } // ============================================================================ // Factory Function // ============================================================================ /** * Create an OpenClaw integration instance */ export function createOpenClawIntegration( config?: OpenClawConfig ): OpenClawIntegration { return new OpenClawIntegration(config); } // ============================================================================ // Lobster Workflow Parser // ============================================================================ export class LobsterWorkflowParser { /** * Parse Lobster YAML workflow into pipeline definition */ static parse(yaml: string): { name: string; description?: string; states: OpenClawPipelineState[]; } { // This is a simplified parser // In production, use js-yaml library const lines = yaml.split('\n'); const result: { name: string; description?: string; states: OpenClawPipelineState[]; } = { name: 'parsed-workflow', states: [] }; // Parse YAML structure // Implementation would use proper YAML parser return result; } /** * Validate a Lobster workflow */ static validate(workflow: any): { valid: boolean; errors: string[]; } { const errors: string[] = []; if (!workflow.name) { errors.push('Workflow must have a name'); } if (!workflow.states || workflow.states.length === 0) { errors.push('Workflow must have at least one state'); } // Check for unreachable states // Check for cycles // Check for missing initial state return { valid: errors.length === 0, errors }; } } // ============================================================================ // Export // ============================================================================ export default OpenClawIntegration;