matrix-plugin/dist/session-manager.js

167 lines
5.7 KiB
JavaScript

import fs from "fs";
import path from "path";
const SESSION_MAP_FILE = path.join(process.env.HOME || "/tmp", ".opencode-matrix-sessions.json");
console.log(`[SessionManager] SESSION_MAP_FILE=${SESSION_MAP_FILE}`);
function loadSessionMap() {
const result = new Map();
try {
if (fs.existsSync(SESSION_MAP_FILE)) {
const data = JSON.parse(fs.readFileSync(SESSION_MAP_FILE, "utf-8"));
console.log(`[SessionManager] Loaded ${data.length} sessions from ${SESSION_MAP_FILE}`);
if (Array.isArray(data)) {
for (const entry of data) {
const key = `${entry.matrixRoomId}::${entry.threadRootId}`;
result.set(key, entry);
}
}
}
else {
console.log(`[SessionManager] File not found: ${SESSION_MAP_FILE}`);
}
}
catch (e) {
console.log(`[SessionManager] Error loading: ${e}`);
}
return result;
}
function saveSessionMap(map) {
try {
const entries = [];
for (const [, entry] of map) {
entries.push(entry);
}
fs.writeFileSync(SESSION_MAP_FILE, JSON.stringify(entries, null, 2));
}
catch (e) {
// ignore
}
}
export class SessionManager {
sessions = new Map();
persistentMap;
activeQueries = new Map();
rateLimitMs;
lastRequestByUser = new Map();
processedEvents = new Set();
constructor(rateLimitSeconds) {
this.rateLimitMs = (rateLimitSeconds || 5) * 1000;
this.persistentMap = loadSessionMap();
// Restore sessions from persistent map
for (const [key, entry] of this.persistentMap) {
const [matrixRoomId, threadRootId] = key.split("::");
this.sessions.set(key, {
matrixRoomId,
threadRootId,
opencodeSessionId: entry.opencodeSessionId,
eventId: "",
isActive: false,
messageCount: entry.messageCount,
inputChars: entry.inputChars,
outputChars: entry.outputChars,
lastActivity: entry.lastActivity,
lastEventIds: new Map(),
});
}
}
getSessionKey(matrixRoomId, threadRootId) {
return `${matrixRoomId}::${threadRootId}`;
}
getOrCreateSession(matrixRoomId, threadRootId, eventId) {
const key = this.getSessionKey(matrixRoomId, threadRootId);
let session = this.sessions.get(key);
if (!session) {
session = {
matrixRoomId,
threadRootId,
opencodeSessionId: "",
eventId,
isActive: false,
messageCount: 0,
inputChars: 0,
outputChars: 0,
lastActivity: Date.now(),
lastEventIds: new Map(),
};
this.sessions.set(key, session);
}
return session;
}
getSession(matrixRoomId, threadRootId) {
const key = this.getSessionKey(matrixRoomId, threadRootId);
return this.sessions.get(key);
}
removeSession(matrixRoomId, threadRootId) {
const key = this.getSessionKey(matrixRoomId, threadRootId);
this.sessions.delete(key);
this.persistentMap.delete(key);
saveSessionMap(this.persistentMap);
this.activeQueries.delete(key);
}
has(matrixRoomId, threadRootId) {
const key = this.getSessionKey(matrixRoomId, threadRootId);
return this.sessions.has(key);
}
hasActiveQuery(matrixRoomId, threadRootId) {
const key = this.getSessionKey(matrixRoomId, threadRootId);
return this.activeQueries.has(key);
}
markQueryActive(matrixRoomId, threadRootId, abortFn) {
const key = this.getSessionKey(matrixRoomId, threadRootId);
this.activeQueries.set(key, abortFn);
return () => this.activeQueries.delete(key);
}
isRateLimited(sender) {
const now = Date.now();
const last = this.lastRequestByUser.get(sender);
if (last && now - last < this.rateLimitMs) {
return true;
}
this.lastRequestByUser.set(sender, now);
return false;
}
isDuplicateEvent(eventId) {
if (!eventId)
return false;
if (this.processedEvents.has(eventId))
return true;
if (this.processedEvents.size > 10000) {
this.processedEvents.clear();
}
this.processedEvents.add(eventId);
return false;
}
saveSession(matrixRoomId, threadRootId, opencodeSessionId) {
const key = this.getSessionKey(matrixRoomId, threadRootId);
const session = this.sessions.get(key);
if (session) {
const entry = {
matrixRoomId,
threadRootId,
opencodeSessionId,
messageCount: session.messageCount,
inputChars: session.inputChars,
outputChars: session.outputChars,
lastActivity: session.lastActivity,
};
this.persistentMap.set(key, entry);
saveSessionMap(this.persistentMap);
}
}
syncWithOpenCode(openCodeSessionIds) {
const toDelete = [];
for (const [, entry] of this.persistentMap) {
if (!openCodeSessionIds.has(entry.opencodeSessionId)) {
toDelete.push(`${entry.matrixRoomId}::${entry.threadRootId}`);
}
}
for (const key of toDelete) {
this.persistentMap.delete(key);
this.sessions.delete(key);
this.activeQueries.delete(key);
}
if (toDelete.length > 0) {
saveSessionMap(this.persistentMap);
}
}
}