Files
admin c629646b9f Complete Agent Pipeline System with Claude Code & OpenClaw Integration
- 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
2026-03-03 13:12:14 +00:00

643 lines
16 KiB
TypeScript

/**
* 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();