167 lines
5.7 KiB
JavaScript
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);
|
|
}
|
|
}
|
|
}
|