feat: Add complete Agentic Compaction & Pipeline System
- Context Compaction System with token counting and summarization - Deterministic State Machine for flow control (no LLM decisions) - Parallel Execution Engine (up to 12 concurrent sessions) - Event-Driven Coordination via Event Bus - Agent Workspace Isolation (tools, memory, identity, files) - YAML Workflow Integration (OpenClaw/Lobster compatible) - Claude Code integration layer - Complete demo UI with real-time visualization - Comprehensive documentation and README Components: - agent-system/: Context management, token counting, subagent spawning - pipeline-system/: State machine, parallel executor, event bus, workflows - skills/: AI capabilities (LLM, ASR, TTS, VLM, image generation, etc.) - src/app/: Next.js demo application Total: ~100KB of production-ready TypeScript code
This commit is contained in:
642
pipeline-system/workspace/agent-workspace.ts
Normal file
642
pipeline-system/workspace/agent-workspace.ts
Normal file
@@ -0,0 +1,642 @@
|
||||
/**
|
||||
* Agent Workspace Isolation
|
||||
*
|
||||
* Each agent gets its own tools, memory, identity, and workspace.
|
||||
* Provides isolation and resource management for parallel agents.
|
||||
*/
|
||||
|
||||
import { randomUUID } from 'crypto';
|
||||
import { EventEmitter } from 'events';
|
||||
import { mkdirSync, rmSync, existsSync, writeFileSync, readFileSync, readdirSync, statSync } from 'fs';
|
||||
import { join, resolve, relative } from 'path';
|
||||
|
||||
// ============================================================================
|
||||
// Types
|
||||
// ============================================================================
|
||||
|
||||
export type Permission = 'read' | 'write' | 'execute' | 'delete' | 'network' | 'git';
|
||||
|
||||
export interface WorkspaceConfig {
|
||||
id: string;
|
||||
projectId: string;
|
||||
agentId: string;
|
||||
role: string;
|
||||
basePath: string;
|
||||
permissions: Permission[];
|
||||
resourceLimits: ResourceLimits;
|
||||
environment: Record<string, string>;
|
||||
mountPoints: MountPoint[];
|
||||
}
|
||||
|
||||
export interface ResourceLimits {
|
||||
maxMemoryMB: number;
|
||||
maxCpuPercent: number;
|
||||
maxFileSizeMB: number;
|
||||
maxExecutionTimeMs: number;
|
||||
maxFileCount: number;
|
||||
}
|
||||
|
||||
export interface MountPoint {
|
||||
source: string;
|
||||
target: string;
|
||||
readOnly: boolean;
|
||||
}
|
||||
|
||||
export interface AgentTool {
|
||||
name: string;
|
||||
description: string;
|
||||
permissions: Permission[];
|
||||
execute: (params: unknown, context: ToolContext) => Promise<ToolResult>;
|
||||
}
|
||||
|
||||
export interface ToolContext {
|
||||
workspace: WorkspaceManager;
|
||||
agentId: string;
|
||||
sessionId: string;
|
||||
permissions: Permission[];
|
||||
}
|
||||
|
||||
export interface ToolResult {
|
||||
success: boolean;
|
||||
output?: unknown;
|
||||
error?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface MemoryStore {
|
||||
shortTerm: Map<string, unknown>;
|
||||
longTerm: Map<string, unknown>;
|
||||
session: Map<string, unknown>;
|
||||
}
|
||||
|
||||
export interface AgentIdentity {
|
||||
id: string;
|
||||
name: string;
|
||||
role: string;
|
||||
description: string;
|
||||
personality: string;
|
||||
systemPrompt: string;
|
||||
capabilities: string[];
|
||||
constraints: string[];
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Workspace Manager
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* WorkspaceManager - Isolated workspace for an agent
|
||||
*/
|
||||
export class WorkspaceManager extends EventEmitter {
|
||||
private config: WorkspaceConfig;
|
||||
private workspacePath: string;
|
||||
private memory: MemoryStore;
|
||||
private identity: AgentIdentity;
|
||||
private tools: Map<string, AgentTool> = new Map();
|
||||
private fileHandles: Map<string, unknown> = new Map();
|
||||
private active = true;
|
||||
|
||||
constructor(config: WorkspaceConfig) {
|
||||
super();
|
||||
this.config = config;
|
||||
this.workspacePath = resolve(config.basePath, config.projectId, config.agentId);
|
||||
this.memory = {
|
||||
shortTerm: new Map(),
|
||||
longTerm: new Map(),
|
||||
session: new Map()
|
||||
};
|
||||
|
||||
this.initializeWorkspace();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the workspace directory
|
||||
*/
|
||||
private initializeWorkspace(): void {
|
||||
if (!existsSync(this.workspacePath)) {
|
||||
mkdirSync(this.workspacePath, { recursive: true });
|
||||
}
|
||||
|
||||
// Create subdirectories
|
||||
const subdirs = ['memory', 'output', 'cache', 'logs'];
|
||||
for (const dir of subdirs) {
|
||||
const path = join(this.workspacePath, dir);
|
||||
if (!existsSync(path)) {
|
||||
mkdirSync(path, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
this.emit('workspaceInitialized', { path: this.workspacePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* Set agent identity
|
||||
*/
|
||||
setIdentity(identity: AgentIdentity): void {
|
||||
this.identity = identity;
|
||||
this.emit('identitySet', { identity });
|
||||
}
|
||||
|
||||
/**
|
||||
* Get agent identity
|
||||
*/
|
||||
getIdentity(): AgentIdentity | undefined {
|
||||
return this.identity;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a tool
|
||||
*/
|
||||
registerTool(tool: AgentTool): void {
|
||||
// Check if agent has required permissions
|
||||
const hasPermission = tool.permissions.every(p =>
|
||||
this.config.permissions.includes(p)
|
||||
);
|
||||
|
||||
if (!hasPermission) {
|
||||
throw new Error(`Agent does not have required permissions for tool: ${tool.name}`);
|
||||
}
|
||||
|
||||
this.tools.set(tool.name, tool);
|
||||
this.emit('toolRegistered', { tool });
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregister a tool
|
||||
*/
|
||||
unregisterTool(name: string): boolean {
|
||||
return this.tools.delete(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a tool
|
||||
*/
|
||||
async executeTool(name: string, params: unknown): Promise<ToolResult> {
|
||||
const tool = this.tools.get(name);
|
||||
if (!tool) {
|
||||
return { success: false, error: `Tool not found: ${name}` };
|
||||
}
|
||||
|
||||
const context: ToolContext = {
|
||||
workspace: this,
|
||||
agentId: this.config.agentId,
|
||||
sessionId: this.config.id,
|
||||
permissions: this.config.permissions
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await tool.execute(params, context);
|
||||
this.emit('toolExecuted', { name, params, result });
|
||||
return result;
|
||||
} catch (error) {
|
||||
const result: ToolResult = {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : String(error)
|
||||
};
|
||||
this.emit('toolError', { name, params, error: result.error });
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get available tools
|
||||
*/
|
||||
getAvailableTools(): AgentTool[] {
|
||||
return Array.from(this.tools.values());
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Memory Management
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Store value in short-term memory
|
||||
*/
|
||||
remember(key: string, value: unknown): void {
|
||||
this.memory.shortTerm.set(key, value);
|
||||
this.emit('memoryStored', { type: 'shortTerm', key });
|
||||
}
|
||||
|
||||
/**
|
||||
* Store value in long-term memory
|
||||
*/
|
||||
memorize(key: string, value: unknown): void {
|
||||
this.memory.longTerm.set(key, value);
|
||||
this.saveMemoryToFile(key, value, 'longTerm');
|
||||
this.emit('memoryStored', { type: 'longTerm', key });
|
||||
}
|
||||
|
||||
/**
|
||||
* Store value in session memory
|
||||
*/
|
||||
storeSession(key: string, value: unknown): void {
|
||||
this.memory.session.set(key, value);
|
||||
this.emit('memoryStored', { type: 'session', key });
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve value from memory
|
||||
*/
|
||||
recall(key: string): unknown | undefined {
|
||||
return (
|
||||
this.memory.shortTerm.get(key) ||
|
||||
this.memory.longTerm.get(key) ||
|
||||
this.memory.session.get(key)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if memory exists
|
||||
*/
|
||||
hasMemory(key: string): boolean {
|
||||
return (
|
||||
this.memory.shortTerm.has(key) ||
|
||||
this.memory.longTerm.has(key) ||
|
||||
this.memory.session.has(key)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Forget a memory
|
||||
*/
|
||||
forget(key: string): boolean {
|
||||
return (
|
||||
this.memory.shortTerm.delete(key) ||
|
||||
this.memory.longTerm.delete(key) ||
|
||||
this.memory.session.delete(key)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all short-term memory
|
||||
*/
|
||||
clearShortTerm(): void {
|
||||
this.memory.shortTerm.clear();
|
||||
this.emit('memoryCleared', { type: 'shortTerm' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear session memory
|
||||
*/
|
||||
clearSession(): void {
|
||||
this.memory.session.clear();
|
||||
this.emit('memoryCleared', { type: 'session' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Save memory to file
|
||||
*/
|
||||
private saveMemoryToFile(key: string, value: unknown, type: string): void {
|
||||
const memoryPath = join(this.workspacePath, 'memory', `${type}.json`);
|
||||
let data: Record<string, unknown> = {};
|
||||
|
||||
if (existsSync(memoryPath)) {
|
||||
try {
|
||||
data = JSON.parse(readFileSync(memoryPath, 'utf-8'));
|
||||
} catch {
|
||||
data = {};
|
||||
}
|
||||
}
|
||||
|
||||
data[key] = value;
|
||||
writeFileSync(memoryPath, JSON.stringify(data, null, 2), 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Load long-term memory from file
|
||||
*/
|
||||
loadLongTermMemory(): void {
|
||||
const memoryPath = join(this.workspacePath, 'memory', 'longTerm.json');
|
||||
if (existsSync(memoryPath)) {
|
||||
try {
|
||||
const data = JSON.parse(readFileSync(memoryPath, 'utf-8'));
|
||||
for (const [key, value] of Object.entries(data)) {
|
||||
this.memory.longTerm.set(key, value);
|
||||
}
|
||||
} catch {
|
||||
// Ignore errors
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// File Operations
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Read a file
|
||||
*/
|
||||
readFile(path: string): string {
|
||||
this.checkPermission('read');
|
||||
const fullPath = this.resolvePath(path);
|
||||
this.checkPathInWorkspace(fullPath);
|
||||
|
||||
return readFileSync(fullPath, 'utf-8');
|
||||
}
|
||||
|
||||
/**
|
||||
* Write a file
|
||||
*/
|
||||
writeFile(path: string, content: string): void {
|
||||
this.checkPermission('write');
|
||||
const fullPath = this.resolvePath(path);
|
||||
this.checkPathInWorkspace(fullPath);
|
||||
this.checkFileSize(content.length);
|
||||
|
||||
writeFileSync(fullPath, content, 'utf-8');
|
||||
this.emit('fileWritten', { path: fullPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a file
|
||||
*/
|
||||
deleteFile(path: string): void {
|
||||
this.checkPermission('delete');
|
||||
const fullPath = this.resolvePath(path);
|
||||
this.checkPathInWorkspace(fullPath);
|
||||
|
||||
rmSync(fullPath, { force: true });
|
||||
this.emit('fileDeleted', { path: fullPath });
|
||||
}
|
||||
|
||||
/**
|
||||
* List files in a directory
|
||||
*/
|
||||
listFiles(path: string = ''): string[] {
|
||||
this.checkPermission('read');
|
||||
const fullPath = this.resolvePath(path);
|
||||
this.checkPathInWorkspace(fullPath);
|
||||
|
||||
if (!existsSync(fullPath)) return [];
|
||||
|
||||
return readdirSync(fullPath).map(name => join(path, name));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if file exists
|
||||
*/
|
||||
fileExists(path: string): boolean {
|
||||
const fullPath = this.resolvePath(path);
|
||||
this.checkPathInWorkspace(fullPath);
|
||||
return existsSync(fullPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file stats
|
||||
*/
|
||||
getFileStats(path: string): { size: number; modified: Date; isDirectory: boolean } | null {
|
||||
const fullPath = this.resolvePath(path);
|
||||
this.checkPathInWorkspace(fullPath);
|
||||
|
||||
if (!existsSync(fullPath)) return null;
|
||||
|
||||
const stats = statSync(fullPath);
|
||||
return {
|
||||
size: stats.size,
|
||||
modified: stats.mtime,
|
||||
isDirectory: stats.isDirectory()
|
||||
};
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Permission & Security
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Check if agent has a permission
|
||||
*/
|
||||
hasPermission(permission: Permission): boolean {
|
||||
return this.config.permissions.includes(permission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check permission and throw if missing
|
||||
*/
|
||||
private checkPermission(permission: Permission): void {
|
||||
if (!this.hasPermission(permission)) {
|
||||
throw new Error(`Permission denied: ${permission}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve path relative to workspace
|
||||
*/
|
||||
private resolvePath(path: string): string {
|
||||
return resolve(this.workspacePath, path);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if path is within workspace
|
||||
*/
|
||||
private checkPathInWorkspace(fullPath: string): void {
|
||||
const relativePath = relative(this.workspacePath, fullPath);
|
||||
if (relativePath.startsWith('..') || relativePath.startsWith('/')) {
|
||||
throw new Error('Path is outside workspace boundaries');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check file size limit
|
||||
*/
|
||||
private checkFileSize(size: number): void {
|
||||
const maxBytes = this.config.resourceLimits.maxFileSizeMB * 1024 * 1024;
|
||||
if (size > maxBytes) {
|
||||
throw new Error(`File size exceeds limit: ${this.config.resourceLimits.maxFileSizeMB}MB`);
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Lifecycle
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Get workspace path
|
||||
*/
|
||||
getPath(): string {
|
||||
return this.workspacePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workspace config
|
||||
*/
|
||||
getConfig(): WorkspaceConfig {
|
||||
return { ...this.config };
|
||||
}
|
||||
|
||||
/**
|
||||
* Clean up workspace
|
||||
*/
|
||||
cleanup(): void {
|
||||
this.active = false;
|
||||
this.clearSession();
|
||||
this.emit('workspaceCleanup', { path: this.workspacePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy workspace (delete files)
|
||||
*/
|
||||
destroy(): void {
|
||||
this.cleanup();
|
||||
|
||||
if (existsSync(this.workspacePath)) {
|
||||
rmSync(this.workspacePath, { recursive: true, force: true });
|
||||
}
|
||||
|
||||
this.emit('workspaceDestroyed', { path: this.workspacePath });
|
||||
}
|
||||
|
||||
/**
|
||||
* Export workspace state
|
||||
*/
|
||||
exportState(): {
|
||||
config: WorkspaceConfig;
|
||||
memory: Record<string, unknown>;
|
||||
identity?: AgentIdentity;
|
||||
tools: string[];
|
||||
} {
|
||||
return {
|
||||
config: this.getConfig(),
|
||||
memory: {
|
||||
shortTerm: Object.fromEntries(this.memory.shortTerm),
|
||||
longTerm: Object.fromEntries(this.memory.longTerm),
|
||||
session: Object.fromEntries(this.memory.session)
|
||||
},
|
||||
identity: this.identity,
|
||||
tools: Array.from(this.tools.keys())
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Workspace Factory
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* WorkspaceFactory - Creates and manages workspaces
|
||||
*/
|
||||
export class WorkspaceFactory {
|
||||
private basePath: string;
|
||||
private workspaces: Map<string, WorkspaceManager> = new Map();
|
||||
|
||||
constructor(basePath: string = './workspaces') {
|
||||
this.basePath = resolve(basePath);
|
||||
|
||||
if (!existsSync(this.basePath)) {
|
||||
mkdirSync(this.basePath, { recursive: true });
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new workspace
|
||||
*/
|
||||
createWorkspace(config: {
|
||||
projectId: string;
|
||||
agentId: string;
|
||||
role: string;
|
||||
permissions?: Permission[];
|
||||
resourceLimits?: Partial<ResourceLimits>;
|
||||
}): WorkspaceManager {
|
||||
const id = `ws-${randomUUID().substring(0, 8)}`;
|
||||
|
||||
const fullConfig: WorkspaceConfig = {
|
||||
id,
|
||||
projectId: config.projectId,
|
||||
agentId: config.agentId,
|
||||
role: config.role,
|
||||
basePath: this.basePath,
|
||||
permissions: config.permissions || ['read'],
|
||||
resourceLimits: {
|
||||
maxMemoryMB: 512,
|
||||
maxCpuPercent: 50,
|
||||
maxFileSizeMB: 10,
|
||||
maxExecutionTimeMs: 60000,
|
||||
maxFileCount: 1000,
|
||||
...config.resourceLimits
|
||||
},
|
||||
environment: {},
|
||||
mountPoints: []
|
||||
};
|
||||
|
||||
const workspace = new WorkspaceManager(fullConfig);
|
||||
this.workspaces.set(id, workspace);
|
||||
|
||||
return workspace;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a workspace by ID
|
||||
*/
|
||||
getWorkspace(id: string): WorkspaceManager | undefined {
|
||||
return this.workspaces.get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workspaces by project
|
||||
*/
|
||||
getWorkspacesByProject(projectId: string): WorkspaceManager[] {
|
||||
return Array.from(this.workspaces.values())
|
||||
.filter(w => w.getConfig().projectId === projectId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all workspaces
|
||||
*/
|
||||
getAllWorkspaces(): WorkspaceManager[] {
|
||||
return Array.from(this.workspaces.values());
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy a workspace
|
||||
*/
|
||||
destroyWorkspace(id: string): boolean {
|
||||
const workspace = this.workspaces.get(id);
|
||||
if (workspace) {
|
||||
workspace.destroy();
|
||||
return this.workspaces.delete(id);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Destroy all workspaces for a project
|
||||
*/
|
||||
destroyProjectWorkspaces(projectId: string): number {
|
||||
const projectWorkspaces = this.getWorkspacesByProject(projectId);
|
||||
let count = 0;
|
||||
|
||||
for (const workspace of projectWorkspaces) {
|
||||
workspace.destroy();
|
||||
this.workspaces.delete(workspace.getConfig().id);
|
||||
count++;
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get factory stats
|
||||
*/
|
||||
getStats(): {
|
||||
totalWorkspaces: number;
|
||||
byProject: Record<string, number>;
|
||||
byRole: Record<string, number>;
|
||||
} {
|
||||
const byProject: Record<string, number> = {};
|
||||
const byRole: Record<string, number> = {};
|
||||
|
||||
for (const workspace of this.workspaces.values()) {
|
||||
const config = workspace.getConfig();
|
||||
byProject[config.projectId] = (byProject[config.projectId] || 0) + 1;
|
||||
byRole[config.role] = (byRole[config.role] || 0) + 1;
|
||||
}
|
||||
|
||||
return {
|
||||
totalWorkspaces: this.workspaces.size,
|
||||
byProject,
|
||||
byRole
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Default factory instance
|
||||
export const defaultWorkspaceFactory = new WorkspaceFactory();
|
||||
Reference in New Issue
Block a user