mirror of
https://github.com/openclaw/openclaw.git
synced 2026-06-06 05:51:15 +08:00
* refactor: expand acp core package * chore: drop acp core package symlink * fix: keep acp core dependency graph stable * fix: add acp core tsconfig subpaths * fix: sync acp core boundary path artifacts * fix: use kysely for cron run-log queries * fix: resolve acp core subpaths in loaders
129 lines
4.5 KiB
JavaScript
129 lines
4.5 KiB
JavaScript
import { resolveIntegerOption } from "./numeric-options.mjs";
|
|
import { randomUUID } from "node:crypto";
|
|
//#region src/session.ts
|
|
const DEFAULT_MAX_SESSIONS = 5e3;
|
|
const DEFAULT_IDLE_TTL_MS = 1440 * 60 * 1e3;
|
|
function createInMemorySessionStore(options = {}) {
|
|
const maxSessions = resolveIntegerOption(options.maxSessions, DEFAULT_MAX_SESSIONS, { min: 1 });
|
|
const idleTtlMs = resolveIntegerOption(options.idleTtlMs, DEFAULT_IDLE_TTL_MS, { min: 1e3 });
|
|
const now = options.now ?? Date.now;
|
|
const sessions = /* @__PURE__ */ new Map();
|
|
const runIdToSessionId = /* @__PURE__ */ new Map();
|
|
const touchSession = (session, nowMs) => {
|
|
session.lastTouchedAt = nowMs;
|
|
};
|
|
const removeSession = (sessionId) => {
|
|
const session = sessions.get(sessionId);
|
|
if (!session) return false;
|
|
if (session.activeRunId) runIdToSessionId.delete(session.activeRunId);
|
|
session.abortController?.abort();
|
|
sessions.delete(sessionId);
|
|
return true;
|
|
};
|
|
const reapIdleSessions = (nowMs) => {
|
|
const idleBefore = nowMs - idleTtlMs;
|
|
for (const [sessionId, session] of sessions.entries()) {
|
|
if (session.activeRunId || session.abortController) continue;
|
|
if (session.lastTouchedAt > idleBefore) continue;
|
|
removeSession(sessionId);
|
|
}
|
|
};
|
|
const evictOldestIdleSession = () => {
|
|
let oldestSessionId = null;
|
|
let oldestLastTouchedAt = Number.POSITIVE_INFINITY;
|
|
for (const [sessionId, session] of sessions.entries()) {
|
|
if (session.activeRunId || session.abortController) continue;
|
|
if (session.lastTouchedAt >= oldestLastTouchedAt) continue;
|
|
oldestLastTouchedAt = session.lastTouchedAt;
|
|
oldestSessionId = sessionId;
|
|
}
|
|
if (!oldestSessionId) return false;
|
|
return removeSession(oldestSessionId);
|
|
};
|
|
const createSession = (params) => {
|
|
const nowMs = now();
|
|
const sessionId = params.sessionId ?? randomUUID();
|
|
const existingSession = sessions.get(sessionId);
|
|
if (existingSession) {
|
|
existingSession.sessionKey = params.sessionKey;
|
|
if ("ledgerSessionId" in params) existingSession.ledgerSessionId = params.ledgerSessionId;
|
|
existingSession.cwd = params.cwd;
|
|
touchSession(existingSession, nowMs);
|
|
return existingSession;
|
|
}
|
|
reapIdleSessions(nowMs);
|
|
if (sessions.size >= maxSessions && !evictOldestIdleSession()) throw new Error(`ACP session limit reached (max ${maxSessions}). Close idle ACP clients and retry.`);
|
|
const session = {
|
|
sessionId,
|
|
sessionKey: params.sessionKey,
|
|
...params.ledgerSessionId ? { ledgerSessionId: params.ledgerSessionId } : {},
|
|
cwd: params.cwd,
|
|
createdAt: nowMs,
|
|
lastTouchedAt: nowMs,
|
|
abortController: null,
|
|
activeRunId: null
|
|
};
|
|
sessions.set(sessionId, session);
|
|
return session;
|
|
};
|
|
const hasSession = (sessionId) => sessions.has(sessionId);
|
|
const getSession = (sessionId) => {
|
|
const session = sessions.get(sessionId);
|
|
if (session) touchSession(session, now());
|
|
return session;
|
|
};
|
|
const getSessionByRunId = (runId) => {
|
|
const sessionId = runIdToSessionId.get(runId);
|
|
if (!sessionId) return;
|
|
const session = sessions.get(sessionId);
|
|
if (session) touchSession(session, now());
|
|
return session;
|
|
};
|
|
const setActiveRun = (sessionId, runId, abortController) => {
|
|
const session = sessions.get(sessionId);
|
|
if (!session) return;
|
|
session.activeRunId = runId;
|
|
session.abortController = abortController;
|
|
runIdToSessionId.set(runId, sessionId);
|
|
touchSession(session, now());
|
|
};
|
|
const clearActiveRun = (sessionId) => {
|
|
const session = sessions.get(sessionId);
|
|
if (!session) return;
|
|
if (session.activeRunId) runIdToSessionId.delete(session.activeRunId);
|
|
session.activeRunId = null;
|
|
session.abortController = null;
|
|
touchSession(session, now());
|
|
};
|
|
const cancelActiveRun = (sessionId) => {
|
|
const session = sessions.get(sessionId);
|
|
if (!session?.abortController) return false;
|
|
session.abortController.abort();
|
|
if (session.activeRunId) runIdToSessionId.delete(session.activeRunId);
|
|
session.abortController = null;
|
|
session.activeRunId = null;
|
|
touchSession(session, now());
|
|
return true;
|
|
};
|
|
const deleteSession = (sessionId) => removeSession(sessionId);
|
|
const clearAllSessionsForTest = () => {
|
|
for (const session of sessions.values()) session.abortController?.abort();
|
|
sessions.clear();
|
|
runIdToSessionId.clear();
|
|
};
|
|
return {
|
|
createSession,
|
|
hasSession,
|
|
getSession,
|
|
getSessionByRunId,
|
|
setActiveRun,
|
|
clearActiveRun,
|
|
cancelActiveRun,
|
|
deleteSession,
|
|
clearAllSessionsForTest
|
|
};
|
|
}
|
|
const defaultAcpSessionStore = createInMemorySessionStore();
|
|
//#endregion
|
|
export { createInMemorySessionStore, defaultAcpSessionStore };
|