Slowtime MCP Server

by bmorphism
Verified
import { v4 as uuidv4 } from 'uuid'; import { SlowInterval, IntervalStatus, CreateIntervalParams, IntervalStore, IntervalProgress, } from './types.js'; import { TimeFuzz } from './timefuzz.js'; export class TimeKeeper { private intervals: IntervalStore = {}; createInterval(params: CreateIntervalParams): SlowInterval { const id = uuidv4(); const now = Date.now(); // Add fuzzing to duration and start time const fuzzedDuration = TimeFuzz.fuzzDuration(params.duration); const fuzzedStartTime = TimeFuzz.addJitter(now); const interval: SlowInterval = { id, label: params.label, duration: fuzzedDuration, startTime: fuzzedStartTime, status: 'active', progress: 0, }; this.intervals[id] = interval; return interval; } getInterval(id: string): SlowInterval | null { return this.intervals[id] || null; } async pauseInterval(id: string): Promise<boolean> { const interval = this.intervals[id]; if (!interval || interval.status !== 'active') { return false; } // Add random delay to prevent timing analysis await TimeFuzz.randomDelay(); interval.pausedAt = TimeFuzz.addJitter(Date.now()); interval.status = 'paused'; return true; } async resumeInterval(id: string): Promise<boolean> { const interval = this.intervals[id]; if (!interval || interval.status !== 'paused' || !interval.pausedAt) { return false; } // Add random delay to prevent timing analysis await TimeFuzz.randomDelay(); const now = TimeFuzz.addJitter(Date.now()); const pauseDuration = now - interval.pausedAt; interval.startTime += pauseDuration; interval.pausedAt = undefined; interval.status = 'active'; return true; } async getIntervalProgress(interval: SlowInterval): Promise<IntervalProgress> { // Add random delay to prevent timing analysis await TimeFuzz.randomDelay(); const now = Date.now(); let elapsedTime: number; if (interval.status === 'paused' && interval.pausedAt) { elapsedTime = interval.pausedAt - interval.startTime; } else { // Add jitter to current time to prevent timing correlation const fuzzedNow = TimeFuzz.addJitter(now); elapsedTime = fuzzedNow - interval.startTime; } const progress = Math.min(elapsedTime / interval.duration, 1); const remainingTime = Math.max(interval.duration - elapsedTime, 0); // Update status if completed if (progress >= 1 && interval.status === 'active') { interval.status = 'completed'; interval.progress = 1; } else { interval.progress = progress; } return { id: interval.id, label: interval.label, status: interval.status, progress: interval.progress, remainingTime, totalDuration: interval.duration, }; } async listIntervals(): Promise<IntervalProgress[]> { const intervals = await Promise.all( Object.values(this.intervals).map(interval => this.getIntervalProgress(interval) ) ); return intervals; } async listActiveIntervals(): Promise<IntervalProgress[]> { const intervals = await this.listIntervals(); return intervals.filter(interval => interval.status === 'active' || interval.status === 'paused' ); } async listCompletedIntervals(): Promise<IntervalProgress[]> { const intervals = await this.listIntervals(); return intervals.filter(interval => interval.status === 'completed' ); } // Clean up completed intervals older than the specified age async cleanupCompletedIntervals(maxAgeMs: number): Promise<void> { // Add random delay to prevent timing analysis await TimeFuzz.randomDelay(); const now = TimeFuzz.addJitter(Date.now()); Object.entries(this.intervals).forEach(([id, interval]) => { if (interval.status === 'completed' && (now - interval.startTime) > maxAgeMs) { delete this.intervals[id]; } }); } } // Create a singleton instance export const timekeeper = new TimeKeeper();