Skip to main content
Glama
manager.ts7.02 kB
import { readFile, writeFile } from 'fs/promises'; import path from 'path'; import { v4 as uuidv4 } from 'uuid'; import { ProjectState, WorkPackageState, TaskState, Checkpoint, Result, FileChange } from '../types.js'; interface StateData { projects: Map<string, ProjectState>; workPackages: Map<string, WorkPackageState>; tasks: Map<string, TaskState>; checkpoints: Map<string, Checkpoint>; } export class StateManager { private contextDir: string; private state: StateData; constructor(contextDir: string) { this.contextDir = contextDir; this.state = { projects: new Map(), workPackages: new Map(), tasks: new Map(), checkpoints: new Map() }; } private async loadState(): Promise<void> { try { const statePath = path.join(this.contextDir, 'state.json'); const data = await readFile(statePath, 'utf-8'); const parsed = JSON.parse(data); // Convert arrays back to Maps this.state = { projects: new Map(Object.entries(parsed.projects || {})), workPackages: new Map(Object.entries(parsed.workPackages || {})), tasks: new Map(Object.entries(parsed.tasks || {})), checkpoints: new Map(Object.entries(parsed.checkpoints || {})) }; } catch (error) { // If file doesn't exist, use empty state if ((error as NodeJS.ErrnoException).code !== 'ENOENT') { throw error; } } } public async saveState(): Promise<void> { const statePath = path.join(this.contextDir, 'state.json'); const data = { projects: Object.fromEntries(this.state.projects), workPackages: Object.fromEntries(this.state.workPackages), tasks: Object.fromEntries(this.state.tasks), checkpoints: Object.fromEntries(this.state.checkpoints) }; await writeFile(statePath, JSON.stringify(data, null, 2)); } async initialize(): Promise<void> { await this.loadState(); } public getWorkPackage(id: string): Promise<Result<WorkPackageState>> { const workPackage = this.state.workPackages.get(id); if (!workPackage) { return Promise.resolve({ success: false, error: `Work package not found: ${id}` }); } return Promise.resolve({ success: true, data: workPackage }); } public async getTask(id: string): Promise<Result<TaskState>> { const task = this.state.tasks.get(id); if (!task) { return { success: false, error: `Task not found: ${id}` }; } return { success: true, data: task }; } public async createTask( workPackageId: string, name: string, metadata: Record<string, any> ): Promise<Result<TaskState>> { const wp = this.state.workPackages.get(workPackageId); if (!wp) { return { success: false, error: `Work package not found: ${workPackageId}` }; } const task: TaskState = { id: uuidv4(), workPackageId, status: 'PENDING', changes: [], metadata: { name, createdAt: Date.now(), ...metadata } }; this.state.tasks.set(task.id, task); await this.saveState(); return { success: true, data: task }; } public async updateTaskStatus( taskId: string, status: TaskState['status'] ): Promise<Result<TaskState>> { const task = this.state.tasks.get(taskId); if (!task) { return { success: false, error: `Task not found: ${taskId}` }; } task.status = status; await this.saveState(); return { success: true, data: task }; } public async recordFileChange( taskId: string, change: FileChange ): Promise<Result<TaskState>> { const task = this.state.tasks.get(taskId); if (!task) { return { success: false, error: `Task not found: ${taskId}` }; } task.changes.push(change); await this.saveState(); return { success: true, data: task }; } async createProject(name: string, metadata: Record<string, any>): Promise<Result<ProjectState>> { const project: ProjectState = { id: uuidv4(), currentRole: 'TRIAGE', status: 'ACTIVE', metadata: { name, ...metadata, createdAt: Date.now() } }; this.state.projects.set(project.id, project); await this.saveState(); return { success: true, data: project }; } async getProject(id: string): Promise<Result<ProjectState>> { const project = this.state.projects.get(id); if (!project) { return { success: false, error: `Project not found: ${id}` }; } return { success: true, data: project }; } async createWorkPackage( projectId: string, name: string, metadata: Record<string, any> ): Promise<Result<WorkPackageState>> { const project = this.state.projects.get(projectId); if (!project) { return { success: false, error: `Project not found: ${projectId}` }; } const workPackage: WorkPackageState = { id: uuidv4(), projectId, status: 'PENDING', progress: 0, metadata: { name, ...metadata, createdAt: Date.now() } }; this.state.workPackages.set(workPackage.id, workPackage); await this.saveState(); return { success: true, data: workPackage }; } async createCheckpoint( parentId: string, type: 'PROJECT' | 'TASK', metadata: Record<string, any> ): Promise<Result<Checkpoint>> { const checkpoint: Checkpoint = { id: uuidv4(), timestamp: Date.now(), type, parentId, state: JSON.stringify(this.state), metadata }; this.state.checkpoints.set(checkpoint.id, checkpoint); // Save checkpoint to file const checkpointPath = path.join( this.contextDir, 'checkpoints', `${checkpoint.id}.json` ); await writeFile(checkpointPath, JSON.stringify(checkpoint, null, 2)); await this.saveState(); return { success: true, data: checkpoint }; } async restoreCheckpoint(checkpointId: string): Promise<Result<void>> { const checkpoint = this.state.checkpoints.get(checkpointId); if (!checkpoint) { return { success: false, error: `Checkpoint not found: ${checkpointId}` }; } try { const restoredState = JSON.parse(checkpoint.state); this.state = { projects: new Map(Object.entries(restoredState.projects || {})), workPackages: new Map(Object.entries(restoredState.workPackages || {})), tasks: new Map(Object.entries(restoredState.tasks || {})), checkpoints: this.state.checkpoints // Keep existing checkpoints }; await this.saveState(); return { success: true, data: undefined }; } catch (error) { return { success: false, error: `Failed to restore checkpoint: ${error instanceof Error ? error.message : String(error)}` }; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/docherty/contextmgr-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server