mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-20 05:07:34 +00:00
150 lines
4.6 KiB
TypeScript
150 lines
4.6 KiB
TypeScript
import { getRuntimeConfig } from "../config/io.js";
|
|
import type { SessionEntry } from "../config/sessions/types.js";
|
|
import type { OpenClawConfig } from "../config/types.openclaw.js";
|
|
import { normalizeAgentId } from "../routing/session-key.js";
|
|
import { resolvePreferredSessionKeyForSessionIdMatches } from "../sessions/session-id-resolution.js";
|
|
import { normalizeOptionalString } from "../shared/string-coerce.js";
|
|
import {
|
|
loadCombinedSessionEntriesForGateway,
|
|
resolveGatewaySessionDatabaseTarget,
|
|
resolveSessionTranscriptCandidates,
|
|
} from "./session-utils.js";
|
|
|
|
const TRANSCRIPT_SESSION_KEY_CACHE = new Map<string, string>();
|
|
const TRANSCRIPT_SESSION_KEY_CACHE_MAX = 256;
|
|
|
|
function resolveTranscriptIdentityForComparison(value: string | undefined): string | undefined {
|
|
const trimmed = normalizeOptionalString(value);
|
|
if (!trimmed) {
|
|
return undefined;
|
|
}
|
|
return trimmed;
|
|
}
|
|
|
|
function sessionKeyMatchesTranscriptLocator(params: {
|
|
cfg: OpenClawConfig;
|
|
store: Record<string, SessionEntry>;
|
|
key: string;
|
|
targetLocator: string;
|
|
}): boolean {
|
|
const entry = params.store[params.key];
|
|
if (!entry?.sessionId) {
|
|
return false;
|
|
}
|
|
const target = resolveGatewaySessionDatabaseTarget({
|
|
cfg: params.cfg,
|
|
key: params.key,
|
|
scanLegacyKeys: false,
|
|
store: params.store,
|
|
});
|
|
const sessionAgentId = normalizeAgentId(target.agentId);
|
|
return resolveSessionTranscriptCandidates(
|
|
entry.sessionId,
|
|
entry.sessionFile,
|
|
sessionAgentId,
|
|
).some((candidate) => resolveTranscriptIdentityForComparison(candidate) === params.targetLocator);
|
|
}
|
|
|
|
export function clearSessionTranscriptKeyCacheForTests(): void {
|
|
TRANSCRIPT_SESSION_KEY_CACHE.clear();
|
|
}
|
|
|
|
export function resolveSessionKeyForTranscriptLocator(locator: string): string | undefined {
|
|
const targetLocator = resolveTranscriptIdentityForComparison(locator);
|
|
if (!targetLocator) {
|
|
return undefined;
|
|
}
|
|
const cfg = getRuntimeConfig();
|
|
const { entries: store } = loadCombinedSessionEntriesForGateway(cfg);
|
|
|
|
const cachedKey = TRANSCRIPT_SESSION_KEY_CACHE.get(targetLocator);
|
|
if (
|
|
cachedKey &&
|
|
sessionKeyMatchesTranscriptLocator({
|
|
cfg,
|
|
store,
|
|
key: cachedKey,
|
|
targetLocator,
|
|
})
|
|
) {
|
|
return cachedKey;
|
|
}
|
|
|
|
const matchingEntries: Array<[string, SessionEntry]> = [];
|
|
for (const [key, entry] of Object.entries(store)) {
|
|
if (!entry?.sessionId || key === cachedKey) {
|
|
continue;
|
|
}
|
|
if (
|
|
sessionKeyMatchesTranscriptLocator({
|
|
cfg,
|
|
store,
|
|
key,
|
|
targetLocator,
|
|
})
|
|
) {
|
|
matchingEntries.push([key, entry]);
|
|
}
|
|
}
|
|
|
|
if (matchingEntries.length > 0) {
|
|
const matchesBySessionId = new Map<string, Array<[string, SessionEntry]>>();
|
|
for (const entry of matchingEntries) {
|
|
const sessionId = entry[1].sessionId;
|
|
if (!sessionId) {
|
|
continue;
|
|
}
|
|
const group = matchesBySessionId.get(sessionId);
|
|
if (group) {
|
|
group.push(entry);
|
|
} else {
|
|
matchesBySessionId.set(sessionId, [entry]);
|
|
}
|
|
}
|
|
|
|
const resolvedMatches = Array.from(matchesBySessionId.entries())
|
|
.map(([sessionId, matches]) => {
|
|
const resolvedKey =
|
|
resolvePreferredSessionKeyForSessionIdMatches(matches, sessionId) ?? matches[0]?.[0];
|
|
const resolvedEntry = resolvedKey
|
|
? matches.find(([key]) => key === resolvedKey)?.[1]
|
|
: undefined;
|
|
return resolvedKey && resolvedEntry
|
|
? {
|
|
key: resolvedKey,
|
|
updatedAt: resolvedEntry.updatedAt ?? 0,
|
|
}
|
|
: undefined;
|
|
})
|
|
.filter((match): match is { key: string; updatedAt: number } => match !== undefined);
|
|
|
|
const sortedResolvedMatches = [...resolvedMatches].toSorted(
|
|
(a, b) => b.updatedAt - a.updatedAt,
|
|
);
|
|
const [freshestMatch, secondFreshestMatch] = sortedResolvedMatches;
|
|
const resolvedKey =
|
|
resolvedMatches.length === 1
|
|
? freshestMatch?.key
|
|
: (freshestMatch?.updatedAt ?? 0) > (secondFreshestMatch?.updatedAt ?? 0)
|
|
? freshestMatch?.key
|
|
: undefined;
|
|
if (resolvedKey) {
|
|
// Evict oldest-inserted entry when cache exceeds size cap (FIFO bound).
|
|
if (
|
|
!TRANSCRIPT_SESSION_KEY_CACHE.has(targetLocator) &&
|
|
TRANSCRIPT_SESSION_KEY_CACHE.size >= TRANSCRIPT_SESSION_KEY_CACHE_MAX
|
|
) {
|
|
const oldest = TRANSCRIPT_SESSION_KEY_CACHE.keys().next().value;
|
|
if (oldest !== undefined) {
|
|
TRANSCRIPT_SESSION_KEY_CACHE.delete(oldest);
|
|
}
|
|
}
|
|
TRANSCRIPT_SESSION_KEY_CACHE.set(targetLocator, resolvedKey);
|
|
return resolvedKey;
|
|
}
|
|
}
|
|
|
|
TRANSCRIPT_SESSION_KEY_CACHE.delete(targetLocator);
|
|
return undefined;
|
|
}
|