import crypto from "node:crypto"; import type { MsgContext } from "../../auto-reply/templating.js"; import { normalizeThinkLevel, normalizeVerboseLevel, type ThinkLevel, type VerboseLevel, } from "../../auto-reply/thinking.js"; import type { ClawdbotConfig } from "../../config/config.js"; import { evaluateSessionFreshness, loadSessionStore, resolveAgentIdFromSessionKey, resolveExplicitAgentSessionKey, resolveSessionResetPolicy, resolveSessionResetType, resolveSessionKey, resolveStorePath, type SessionEntry, } from "../../config/sessions.js"; import { normalizeMainKey } from "../../routing/session-key.js"; export type SessionResolution = { sessionId: string; sessionKey?: string; sessionEntry?: SessionEntry; sessionStore?: Record; storePath: string; isNewSession: boolean; persistedThinking?: ThinkLevel; persistedVerbose?: VerboseLevel; }; type SessionKeyResolution = { sessionKey?: string; sessionStore: Record; storePath: string; }; export function resolveSessionKeyForRequest(opts: { cfg: ClawdbotConfig; to?: string; sessionId?: string; sessionKey?: string; agentId?: string; }): SessionKeyResolution { const sessionCfg = opts.cfg.session; const scope = sessionCfg?.scope ?? "per-sender"; const mainKey = normalizeMainKey(sessionCfg?.mainKey); const explicitSessionKey = opts.sessionKey?.trim() || resolveExplicitAgentSessionKey({ cfg: opts.cfg, agentId: opts.agentId, }); const storeAgentId = resolveAgentIdFromSessionKey(explicitSessionKey); const storePath = resolveStorePath(sessionCfg?.store, { agentId: storeAgentId, }); const sessionStore = loadSessionStore(storePath); const ctx: MsgContext | undefined = opts.to?.trim() ? { From: opts.to } : undefined; let sessionKey: string | undefined = explicitSessionKey ?? (ctx ? resolveSessionKey(scope, ctx, mainKey) : undefined); // If a session id was provided, prefer to re-use its entry (by id) even when no key was derived. if ( !explicitSessionKey && opts.sessionId && (!sessionKey || sessionStore[sessionKey]?.sessionId !== opts.sessionId) ) { const foundKey = Object.keys(sessionStore).find( (key) => sessionStore[key]?.sessionId === opts.sessionId, ); if (foundKey) sessionKey = foundKey; } return { sessionKey, sessionStore, storePath }; } export function resolveSession(opts: { cfg: ClawdbotConfig; to?: string; sessionId?: string; sessionKey?: string; agentId?: string; }): SessionResolution { const sessionCfg = opts.cfg.session; const { sessionKey, sessionStore, storePath } = resolveSessionKeyForRequest({ cfg: opts.cfg, to: opts.to, sessionId: opts.sessionId, sessionKey: opts.sessionKey, agentId: opts.agentId, }); const now = Date.now(); const sessionEntry = sessionKey ? sessionStore[sessionKey] : undefined; const resetType = resolveSessionResetType({ sessionKey }); const resetPolicy = resolveSessionResetPolicy({ sessionCfg, resetType }); const fresh = sessionEntry ? evaluateSessionFreshness({ updatedAt: sessionEntry.updatedAt, now, policy: resetPolicy }) .fresh : false; const sessionId = opts.sessionId?.trim() || (fresh ? sessionEntry?.sessionId : undefined) || crypto.randomUUID(); const isNewSession = !fresh && !opts.sessionId; const persistedThinking = fresh && sessionEntry?.thinkingLevel ? normalizeThinkLevel(sessionEntry.thinkingLevel) : undefined; const persistedVerbose = fresh && sessionEntry?.verboseLevel ? normalizeVerboseLevel(sessionEntry.verboseLevel) : undefined; return { sessionId, sessionKey, sessionEntry, sessionStore, storePath, isNewSession, persistedThinking, persistedVerbose, }; }