- Added Claude Code integration with full context compaction support - Added OpenClaw integration with deterministic pipeline support - Implemented parallel agent execution (4 projects x 3 roles pattern) - Added workspace isolation with permissions and quotas - Implemented Lobster-compatible YAML workflow parser - Added persistent memory store for cross-session context - Created comprehensive README with hero section This project was 100% autonomously built by Z.AI GLM-5
893 lines
24 KiB
TypeScript
893 lines
24 KiB
TypeScript
/**
|
||
* 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<void>;
|
||
onCompactionEnd?: (result: OpenClawCompactionResult) => void | Promise<void>;
|
||
onAgentSpawn?: (agent: OpenClawAgent) => void | Promise<void>;
|
||
onAgentComplete?: (agent: OpenClawAgent, result: any) => void | Promise<void>;
|
||
onPipelineStart?: (pipeline: OpenClawPipeline) => void | Promise<void>;
|
||
onPipelineComplete?: (pipeline: OpenClawPipeline, result: any) => void | Promise<void>;
|
||
onStateTransition?: (from: string, to: string, context: any) => void | Promise<void>;
|
||
};
|
||
}
|
||
|
||
export interface OpenClawContext {
|
||
id: string;
|
||
projectId?: string;
|
||
conversationId?: string;
|
||
messages: OpenClawMessage[];
|
||
metadata: Record<string, any>;
|
||
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<string, any>;
|
||
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<OpenClawConfig>;
|
||
|
||
private context: OpenClawContext;
|
||
private agents: Map<string, OpenClawAgent> = new Map();
|
||
private pipelines: Map<string, OpenClawPipeline> = new Map();
|
||
private workspaces: Map<string, OpenClawWorkspace> = 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, 'id' | 'timestamp'>): 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<OpenClawCompactionResult> {
|
||
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<string, any>;
|
||
}): Promise<OpenClawAgent> {
|
||
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<string, any>;
|
||
timeout?: number;
|
||
}): Promise<any> {
|
||
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<string, any>;
|
||
}>): Promise<Map<string, any>> {
|
||
const results = new Map<string, any>();
|
||
|
||
// 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<void> {
|
||
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<boolean> {
|
||
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<void> {
|
||
// 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<OpenClawWorkspace> {
|
||
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<void> {
|
||
this.workspaces.delete(workspaceId);
|
||
}
|
||
|
||
// ============================================================================
|
||
// Memory Management
|
||
// ============================================================================
|
||
|
||
/**
|
||
* Store value in memory
|
||
*/
|
||
async remember(key: string, value: any): Promise<void> {
|
||
if (this.memoryStore) {
|
||
await this.memoryStore.set(`openclaw:${this.context.id}:${key}`, value);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Retrieve value from memory
|
||
*/
|
||
async recall<T>(key: string): Promise<T | null> {
|
||
if (this.memoryStore) {
|
||
return this.memoryStore.get<T>(`openclaw:${this.context.id}:${key}`);
|
||
}
|
||
return null;
|
||
}
|
||
|
||
/**
|
||
* Save context for later restoration
|
||
*/
|
||
async saveContext(name: string): Promise<void> {
|
||
if (this.memoryStore) {
|
||
await this.memoryStore.set(`context:${name}`, {
|
||
...this.context,
|
||
tokenUsage: this.tokenCounter.getCurrentUsage()
|
||
});
|
||
}
|
||
}
|
||
|
||
/**
|
||
* Load a saved context
|
||
*/
|
||
async loadContext(name: string): Promise<boolean> {
|
||
if (this.memoryStore) {
|
||
const saved = await this.memoryStore.get<{
|
||
messages: OpenClawMessage[];
|
||
metadata: Record<string, any>;
|
||
}>(`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<OpenClawConfig>;
|
||
} {
|
||
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;
|