97 lines
3.2 KiB
TypeScript
97 lines
3.2 KiB
TypeScript
import * as fs from 'fs';
|
|
import * as path from 'path';
|
|
import * as os from 'os';
|
|
import { WSBridgeClient } from './ws-client';
|
|
|
|
export interface BrainWatcherContext {
|
|
logToFile: (msg: string) => void;
|
|
wsBridge: WSBridgeClient;
|
|
projectName: string;
|
|
}
|
|
|
|
export class BrainWatcher {
|
|
private brainDir: string;
|
|
private ctx: BrainWatcherContext;
|
|
private currentSessionId: string = '';
|
|
private watcher: fs.FSWatcher | null = null;
|
|
private lastEventTimes: Map<string, number> = new Map();
|
|
|
|
constructor(ctx: BrainWatcherContext) {
|
|
this.ctx = ctx;
|
|
// The bridgePath is ~/.gemini/antigravity/bridge, so brain is sibling
|
|
this.brainDir = path.join(os.homedir(), '.gemini', 'antigravity', 'brain');
|
|
}
|
|
|
|
public updateSession(sessionId: string) {
|
|
if (!sessionId || this.currentSessionId === sessionId) {
|
|
return;
|
|
}
|
|
this.currentSessionId = sessionId;
|
|
this.startWatching(sessionId);
|
|
}
|
|
|
|
private startWatching(sessionId: string) {
|
|
this.stop();
|
|
|
|
const sessionDir = path.join(this.brainDir, sessionId);
|
|
if (!fs.existsSync(sessionDir)) {
|
|
// It might not be created yet, poll gently
|
|
setTimeout(() => this.startWatching(sessionId), 2000);
|
|
return;
|
|
}
|
|
|
|
try {
|
|
this.watcher = fs.watch(sessionDir, { persistent: false }, (eventType, filename) => {
|
|
if (!filename || !filename.endsWith('.md')) return;
|
|
|
|
// Dedup rapid events
|
|
const now = Date.now();
|
|
const last = this.lastEventTimes.get(filename) || 0;
|
|
if (now - last < 500) return; // 500ms debounce
|
|
this.lastEventTimes.set(filename, now);
|
|
|
|
this.handleFileChange(sessionDir, filename, eventType);
|
|
});
|
|
this.ctx.logToFile(`[BRAIN-WATCHER] Started watching session: ${sessionId.substring(0, 8)}`);
|
|
} catch (e: any) {
|
|
this.ctx.logToFile(`[BRAIN-WATCHER] Failed to watch ${sessionId}: ${e.message}`);
|
|
}
|
|
}
|
|
|
|
private handleFileChange(dir: string, filename: string, rawEventType: string) {
|
|
const filePath = path.join(dir, filename);
|
|
let content = '';
|
|
let eventType = 'file_changed';
|
|
|
|
try {
|
|
if (fs.existsSync(filePath)) {
|
|
content = fs.readFileSync(filePath, 'utf-8');
|
|
} else {
|
|
eventType = 'file_deleted';
|
|
}
|
|
} catch (e) {
|
|
// File might be locked or deleted during read
|
|
return;
|
|
}
|
|
|
|
if (this.ctx.wsBridge && this.ctx.wsBridge.isConnected()) {
|
|
this.ctx.wsBridge.sendBrainEvent({
|
|
event_type: eventType,
|
|
conversation_id: this.currentSessionId,
|
|
file_name: filename,
|
|
content: content,
|
|
timestamp: Date.now() / 1000,
|
|
project_name: this.ctx.projectName,
|
|
});
|
|
this.ctx.logToFile(`[BRAIN-WATCHER] Sent ${eventType} for ${filename}`);
|
|
}
|
|
}
|
|
|
|
public stop() {
|
|
if (this.watcher) {
|
|
this.watcher.close();
|
|
this.watcher = null;
|
|
}
|
|
}
|
|
}
|