import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync, statSync } from 'fs';
import { join } from 'path';
import { tmpdir, homedir } from 'os';
import type { CompactMessage, ClaudeInstance } from './types.js';
interface FastState {
instances: ClaudeInstance[];
lastClean: number;
}
export class MemoryManager {
private statePath: string;
private cache = new Map<string, any>();
private maxCacheSize = 100; // Keep it tiny
constructor() {
const baseDir = join(tmpdir(), 'claude-senator');
mkdirSync(baseDir, { recursive: true });
this.statePath = join(baseDir, 'fast-state.json');
}
// Lightning-fast bootstrap - no conversation scanning
init(): void {
const state: FastState = {
instances: [],
lastClean: Date.now()
};
this.saveState(state);
}
// On-demand memory search - only when user explicitly asks
searchMemory(query: string, limit = 5): CompactMessage[] {
const cacheKey = `search:${query}:${limit}`;
if (this.cache.has(cacheKey)) {
return this.cache.get(cacheKey);
}
const results = this.fastSearch(query, limit);
// Keep cache tiny
if (this.cache.size >= this.maxCacheSize) {
this.cache.clear();
}
this.cache.set(cacheKey, results);
return results;
}
private fastSearch(query: string, limit: number): CompactMessage[] {
try {
const cwd = process.cwd();
const claudeDir = join(homedir(), '.claude', 'conversations');
if (!existsSync(claudeDir)) return [];
// Only scan current project directory
const projectName = this.hashPath(cwd);
const projectDirs = readdirSync(claudeDir, { withFileTypes: true })
.filter(d => d.isDirectory())
.map(d => d.name)
.filter(name => name.includes(projectName.slice(0, 8))); // Fast match
if (projectDirs.length === 0) return [];
// Only check most recent file in most recent project
const projectDir = projectDirs[0];
const projectPath = join(claudeDir, projectDir);
const files = readdirSync(projectPath)
.filter(f => f.endsWith('.jsonl'))
.map(f => ({ name: f, mtime: statSync(join(projectPath, f)).mtime }))
.sort((a, b) => b.mtime.getTime() - a.mtime.getTime())
.slice(0, 1); // Only latest file
if (files.length === 0) return [];
return this.quickScan(join(projectPath, files[0].name), query, limit);
} catch {
return [];
}
}
private quickScan(filePath: string, query: string, limit: number): CompactMessage[] {
try {
const content = readFileSync(filePath, 'utf8');
const lines = content.split('\n').slice(-50); // Last 50 lines only
const queryLower = query.toLowerCase();
const results: CompactMessage[] = [];
for (const line of lines) {
if (results.length >= limit) break;
if (!line.trim()) continue;
try {
const msg = JSON.parse(line);
const msgContent = this.extractContent(msg);
if (msgContent && msgContent.toLowerCase().includes(queryLower)) {
results.push({
uuid: msg.uuid || '',
timestamp: msg.timestamp || '',
type: msg.type || 'user',
content: msgContent.slice(0, 200), // Tiny content
sessionId: msg.sessionId || '',
projectPath: process.cwd()
});
}
} catch {
continue;
}
}
return results;
} catch {
return [];
}
}
private extractContent(msg: any): string {
if (!msg.message?.content) return '';
if (typeof msg.message.content === 'string') {
return msg.message.content;
}
if (Array.isArray(msg.message.content)) {
return msg.message.content
.filter((item: any) => item.type === 'text')
.map((item: any) => item.text)
.join(' ');
}
return '';
}
private hashPath(path: string): string {
// Ultra-fast hash for path matching
let hash = 0;
for (let i = 0; i < path.length; i++) {
hash = ((hash << 5) - hash + path.charCodeAt(i)) & 0xffffffff;
}
return Math.abs(hash).toString(36);
}
// Instant instance management
updateInstances(instances: ClaudeInstance[]): void {
const state = this.loadState();
state.instances = instances;
this.saveState(state);
}
getInstances(): ClaudeInstance[] {
return this.loadState().instances;
}
private loadState(): FastState {
try {
if (!existsSync(this.statePath)) {
return { instances: [], lastClean: Date.now() };
}
return JSON.parse(readFileSync(this.statePath, 'utf8'));
} catch {
return { instances: [], lastClean: Date.now() };
}
}
private saveState(state: FastState): void {
try {
writeFileSync(this.statePath, JSON.stringify(state));
} catch {
// Fail silently
}
}
// Auto-cleanup - runs only when needed
cleanup(): void {
const state = this.loadState();
const now = Date.now();
// Only clean every 10 minutes
if (now - state.lastClean < 10 * 60 * 1000) return;
// Remove stale instances
state.instances = state.instances.filter(inst =>
now - inst.lastSeen < 5 * 60 * 1000 // 5 min timeout
);
state.lastClean = now;
this.saveState(state);
// Clear cache
this.cache.clear();
}
// Zero-overhead status
getStats(): { instances: number; cache: number } {
const state = this.loadState();
return {
instances: state.instances.length,
cache: this.cache.size
};
}
}