- 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
233 lines
5.4 KiB
TypeScript
233 lines
5.4 KiB
TypeScript
/**
|
|
* Agent Storage Module
|
|
*
|
|
* Persistent storage for agent state, conversations, and results.
|
|
* Uses filesystem for persistence.
|
|
*/
|
|
|
|
import { writeFileSync, readFileSync, existsSync, mkdirSync, readdirSync, unlinkSync } from 'fs';
|
|
import { join } from 'path';
|
|
|
|
const STORAGE_DIR = join(process.cwd(), '.agent-storage');
|
|
|
|
export interface StoredConversation {
|
|
id: string;
|
|
agentId: string;
|
|
messages: Array<{
|
|
role: 'user' | 'assistant' | 'system';
|
|
content: string;
|
|
timestamp: string;
|
|
}>;
|
|
metadata?: Record<string, unknown>;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
export interface StoredTask {
|
|
id: string;
|
|
type: string;
|
|
status: string;
|
|
input: unknown;
|
|
output?: unknown;
|
|
error?: string;
|
|
createdAt: string;
|
|
completedAt?: string;
|
|
}
|
|
|
|
export interface StoredAgentState {
|
|
id: string;
|
|
name: string;
|
|
type: string;
|
|
status: string;
|
|
memory: Record<string, unknown>;
|
|
createdAt: string;
|
|
updatedAt: string;
|
|
}
|
|
|
|
/**
|
|
* AgentStorage - Persistent storage for agents
|
|
*/
|
|
export class AgentStorage {
|
|
private baseDir: string;
|
|
|
|
constructor(baseDir: string = STORAGE_DIR) {
|
|
this.baseDir = baseDir;
|
|
this.ensureDirectory();
|
|
}
|
|
|
|
/**
|
|
* Ensure storage directory exists
|
|
*/
|
|
private ensureDirectory(): void {
|
|
if (!existsSync(this.baseDir)) {
|
|
mkdirSync(this.baseDir, { recursive: true });
|
|
}
|
|
|
|
const subdirs = ['conversations', 'tasks', 'agents'];
|
|
for (const subdir of subdirs) {
|
|
const dir = join(this.baseDir, subdir);
|
|
if (!existsSync(dir)) {
|
|
mkdirSync(dir, { recursive: true });
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save a conversation
|
|
*/
|
|
saveConversation(conversation: StoredConversation): void {
|
|
const path = join(this.baseDir, 'conversations', `${conversation.id}.json`);
|
|
writeFileSync(path, JSON.stringify(conversation, null, 2), 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Load a conversation
|
|
*/
|
|
loadConversation(id: string): StoredConversation | null {
|
|
try {
|
|
const path = join(this.baseDir, 'conversations', `${id}.json`);
|
|
if (!existsSync(path)) return null;
|
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List all conversations
|
|
*/
|
|
listConversations(agentId?: string): StoredConversation[] {
|
|
const dir = join(this.baseDir, 'conversations');
|
|
if (!existsSync(dir)) return [];
|
|
|
|
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
|
|
return files.map(file => {
|
|
const content = readFileSync(join(dir, file), 'utf-8');
|
|
return JSON.parse(content) as StoredConversation;
|
|
}).filter(conv => !agentId || conv.agentId === agentId);
|
|
}
|
|
|
|
/**
|
|
* Delete a conversation
|
|
*/
|
|
deleteConversation(id: string): boolean {
|
|
try {
|
|
const path = join(this.baseDir, 'conversations', `${id}.json`);
|
|
if (existsSync(path)) {
|
|
unlinkSync(path);
|
|
return true;
|
|
}
|
|
return false;
|
|
} catch {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save a task
|
|
*/
|
|
saveTask(task: StoredTask): void {
|
|
const path = join(this.baseDir, 'tasks', `${task.id}.json`);
|
|
writeFileSync(path, JSON.stringify(task, null, 2), 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Load a task
|
|
*/
|
|
loadTask(id: string): StoredTask | null {
|
|
try {
|
|
const path = join(this.baseDir, 'tasks', `${id}.json`);
|
|
if (!existsSync(path)) return null;
|
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List all tasks
|
|
*/
|
|
listTasks(status?: string): StoredTask[] {
|
|
const dir = join(this.baseDir, 'tasks');
|
|
if (!existsSync(dir)) return [];
|
|
|
|
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
|
|
return files.map(file => {
|
|
const content = readFileSync(join(dir, file), 'utf-8');
|
|
return JSON.parse(content) as StoredTask;
|
|
}).filter(task => !status || task.status === status);
|
|
}
|
|
|
|
/**
|
|
* Save agent state
|
|
*/
|
|
saveAgentState(state: StoredAgentState): void {
|
|
const path = join(this.baseDir, 'agents', `${state.id}.json`);
|
|
writeFileSync(path, JSON.stringify(state, null, 2), 'utf-8');
|
|
}
|
|
|
|
/**
|
|
* Load agent state
|
|
*/
|
|
loadAgentState(id: string): StoredAgentState | null {
|
|
try {
|
|
const path = join(this.baseDir, 'agents', `${id}.json`);
|
|
if (!existsSync(path)) return null;
|
|
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List all agent states
|
|
*/
|
|
listAgentStates(): StoredAgentState[] {
|
|
const dir = join(this.baseDir, 'agents');
|
|
if (!existsSync(dir)) return [];
|
|
|
|
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
|
|
return files.map(file => {
|
|
const content = readFileSync(join(dir, file), 'utf-8');
|
|
return JSON.parse(content) as StoredAgentState;
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Clear all storage
|
|
*/
|
|
clearAll(): void {
|
|
const subdirs = ['conversations', 'tasks', 'agents'];
|
|
for (const subdir of subdirs) {
|
|
const dir = join(this.baseDir, subdir);
|
|
if (existsSync(dir)) {
|
|
const files = readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
for (const file of files) {
|
|
unlinkSync(join(dir, file));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Get storage stats
|
|
*/
|
|
getStats(): {
|
|
conversations: number;
|
|
tasks: number;
|
|
agents: number;
|
|
} {
|
|
return {
|
|
conversations: this.listConversations().length,
|
|
tasks: this.listTasks().length,
|
|
agents: this.listAgentStates().length
|
|
};
|
|
}
|
|
}
|
|
|
|
// Default storage instance
|
|
export const defaultStorage = new AgentStorage();
|