96 lines
2.9 KiB
TypeScript
96 lines
2.9 KiB
TypeScript
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 {
|
|
DEFAULT_IDLE_MINUTES,
|
|
loadSessionStore,
|
|
resolveAgentIdFromSessionKey,
|
|
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<string, SessionEntry>;
|
|
storePath: string;
|
|
isNewSession: boolean;
|
|
persistedThinking?: ThinkLevel;
|
|
persistedVerbose?: VerboseLevel;
|
|
};
|
|
|
|
export function resolveSession(opts: {
|
|
cfg: ClawdbotConfig;
|
|
to?: string;
|
|
sessionId?: string;
|
|
sessionKey?: string;
|
|
}): SessionResolution {
|
|
const sessionCfg = opts.cfg.session;
|
|
const scope = sessionCfg?.scope ?? "per-sender";
|
|
const mainKey = normalizeMainKey(sessionCfg?.mainKey);
|
|
const idleMinutes = Math.max(sessionCfg?.idleMinutes ?? DEFAULT_IDLE_MINUTES, 1);
|
|
const idleMs = idleMinutes * 60_000;
|
|
const explicitSessionKey = opts.sessionKey?.trim();
|
|
const storeAgentId = resolveAgentIdFromSessionKey(explicitSessionKey);
|
|
const storePath = resolveStorePath(sessionCfg?.store, {
|
|
agentId: storeAgentId,
|
|
});
|
|
const sessionStore = loadSessionStore(storePath);
|
|
const now = Date.now();
|
|
|
|
const ctx: MsgContext | undefined = opts.to?.trim() ? { From: opts.to } : undefined;
|
|
let sessionKey: string | undefined =
|
|
explicitSessionKey ?? (ctx ? resolveSessionKey(scope, ctx, mainKey) : undefined);
|
|
let sessionEntry = sessionKey ? sessionStore[sessionKey] : 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 &&
|
|
(!sessionEntry || sessionEntry.sessionId !== opts.sessionId)
|
|
) {
|
|
const foundKey = Object.keys(sessionStore).find(
|
|
(key) => sessionStore[key]?.sessionId === opts.sessionId,
|
|
);
|
|
if (foundKey) {
|
|
sessionKey = sessionKey ?? foundKey;
|
|
sessionEntry = sessionStore[foundKey];
|
|
}
|
|
}
|
|
|
|
const fresh = sessionEntry && sessionEntry.updatedAt >= now - idleMs;
|
|
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,
|
|
};
|
|
}
|