11import type { SessionIdentitySnapshot , SessionMetadataCacheEntry , SessionStoreInternals } from "./types.ts" ;
22import { asString , isRecord } from "./shared.ts" ;
33import { getResolvedOpenClawRootDir , importOpenClawInternalModule } from "./openclaw-runtime.ts" ;
4+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk/core" ;
45
56const SESSION_META_CACHE_TTL_MS = 2_000 ;
67const SESSION_META_CACHE_MAX = 512 ;
78
89let sessionStoreInternalsPromise : Promise < SessionStoreInternals > | null = null ;
910const sessionMetadataCache = new Map < string , SessionMetadataCacheEntry > ( ) ;
1011
11- async function loadSessionStoreInternals ( ) : Promise < SessionStoreInternals > {
12+ function resolveRuntimeSessionStoreInternals (
13+ api : OpenClawPluginApi | undefined ,
14+ ) : SessionStoreInternals | null {
15+ const runtime = isRecord ( api ?. runtime ) ? api . runtime : undefined ;
16+ const runtimeConfig = isRecord ( runtime ?. config ) ? runtime . config : undefined ;
17+ const runtimeAgent = isRecord ( runtime ?. agent ) ? runtime . agent : undefined ;
18+ const runtimeAgentSession = isRecord ( runtimeAgent ?. session ) ? runtimeAgent . session : undefined ;
19+
20+ const resolveStorePath = runtimeAgentSession ?. resolveStorePath ;
21+ const loadSessionStore = runtimeAgentSession ?. loadSessionStore ;
22+ if ( typeof resolveStorePath !== "function" || typeof loadSessionStore !== "function" ) {
23+ return null ;
24+ }
25+
26+ const loadConfig = runtimeConfig ?. loadConfig ;
27+ const fallbackConfig = isRecord ( api ?. config ) ? api . config : { } ;
28+ return {
29+ loadConfig :
30+ typeof loadConfig === "function"
31+ ? ( loadConfig as SessionStoreInternals [ "loadConfig" ] )
32+ : ( ) => fallbackConfig ,
33+ resolveStorePath : resolveStorePath as SessionStoreInternals [ "resolveStorePath" ] ,
34+ loadSessionStore : loadSessionStore as SessionStoreInternals [ "loadSessionStore" ] ,
35+ } ;
36+ }
37+
38+ async function loadSessionStoreInternals (
39+ api : OpenClawPluginApi | undefined ,
40+ ) : Promise < SessionStoreInternals > {
41+ const runtimeInternals = resolveRuntimeSessionStoreInternals ( api ) ;
42+ if ( runtimeInternals ) {
43+ return runtimeInternals ;
44+ }
45+
1246 if ( sessionStoreInternalsPromise ) {
1347 return sessionStoreInternalsPromise ;
1448 }
@@ -50,6 +84,19 @@ async function loadSessionStoreInternals(): Promise<SessionStoreInternals> {
5084 return sessionStoreInternalsPromise ;
5185}
5286
87+ export async function warmSessionIdentityResolver ( params : {
88+ api : OpenClawPluginApi ;
89+ sourceAgentId ?: string ;
90+ } ) : Promise < void > {
91+ const internals = await loadSessionStoreInternals ( params . api ) ;
92+ const cfg = internals . loadConfig ( ) ;
93+ const sessionCfg = isRecord ( cfg . session ) ? cfg . session : undefined ;
94+ const storePath = internals . resolveStorePath ( asString ( sessionCfg ?. store ) , {
95+ agentId : asString ( params . sourceAgentId ) ,
96+ } ) ;
97+ internals . loadSessionStore ( storePath ) ;
98+ }
99+
53100function unknownSessionIdentity ( ) : SessionIdentitySnapshot {
54101 return {
55102 provider : null ,
@@ -80,6 +127,18 @@ function resolveBaseSessionKey(sessionKey: string): string {
80127 return base || sessionKey ;
81128}
82129
130+ function resolveSessionAgentId (
131+ normalizedSessionKey : string ,
132+ sourceAgentId : string | undefined ,
133+ ) : string | undefined {
134+ const parts = normalizedSessionKey . split ( ":" ) . filter ( Boolean ) ;
135+ if ( parts . length >= 2 && parts [ 0 ] === "agent" && parts [ 1 ] ) {
136+ return parts [ 1 ] ;
137+ }
138+ const normalizedSourceAgentId = asString ( sourceAgentId ) ;
139+ return normalizedSourceAgentId === "default" ? undefined : normalizedSourceAgentId ;
140+ }
141+
83142function readSessionIdentityFromEntry ( entry : Record < string , unknown > ) : SessionIdentitySnapshot {
84143 const origin = isRecord ( entry . origin ) ? entry . origin : undefined ;
85144 const deliveryContext = isRecord ( entry . deliveryContext ) ? entry . deliveryContext : undefined ;
@@ -120,7 +179,12 @@ function readSessionIdentityFromEntry(entry: Record<string, unknown>): SessionId
120179}
121180
122181function setSessionMetadataCache ( key : string , data : SessionIdentitySnapshot ) : void {
123- sessionMetadataCache . set ( key , { at : Date . now ( ) , data } ) ;
182+ const now = Date . now ( ) ;
183+ sessionMetadataCache . set ( key , {
184+ at : now ,
185+ data,
186+ expiresAt : now + SESSION_META_CACHE_TTL_MS ,
187+ } ) ;
124188 if ( sessionMetadataCache . size > SESSION_META_CACHE_MAX ) {
125189 const oldest = sessionMetadataCache . keys ( ) . next ( ) . value ;
126190 if ( typeof oldest === "string" ) {
@@ -129,36 +193,115 @@ function setSessionMetadataCache(key: string, data: SessionIdentitySnapshot): vo
129193 }
130194}
131195
132- export async function resolveSessionIdentity (
133- sessionKey : string | undefined ,
134- ) : Promise < SessionIdentitySnapshot > {
135- const normalizedKey = normalizeSessionStoreKey ( sessionKey ) ;
136- if ( ! normalizedKey ) {
137- return unknownSessionIdentity ( ) ;
138- }
139-
140- const cached = sessionMetadataCache . get ( normalizedKey ) ;
141- if ( cached && Date . now ( ) - cached . at < SESSION_META_CACHE_TTL_MS ) {
142- return cached . data ;
196+ function setSessionMetadataCachePromise (
197+ key : string ,
198+ promise : Promise < SessionIdentitySnapshot > ,
199+ ) : void {
200+ sessionMetadataCache . set ( key , { at : Date . now ( ) , promise } ) ;
201+ if ( sessionMetadataCache . size > SESSION_META_CACHE_MAX ) {
202+ const oldest = sessionMetadataCache . keys ( ) . next ( ) . value ;
203+ if ( typeof oldest === "string" && oldest !== key ) {
204+ sessionMetadataCache . delete ( oldest ) ;
205+ }
143206 }
207+ }
144208
209+ async function readSessionIdentity ( params : {
210+ normalizedKey : string ;
211+ internals : SessionStoreInternals ;
212+ storePath : string ;
213+ } ) : Promise < SessionIdentitySnapshot > {
145214 try {
146- const internals = await loadSessionStoreInternals ( ) ;
147- const cfg = internals . loadConfig ( ) ;
148- const sessionCfg = isRecord ( cfg . session ) ? cfg . session : undefined ;
149- const storePath = internals . resolveStorePath ( asString ( sessionCfg ?. store ) ) ;
150- const store = internals . loadSessionStore ( storePath ) ;
151- const directEntry = store [ normalizedKey ] ;
152- const baseEntry = store [ resolveBaseSessionKey ( normalizedKey ) ] ;
215+ const store = params . internals . loadSessionStore ( params . storePath ) ;
216+ const directEntry = store [ params . normalizedKey ] ;
217+ const baseEntry = store [ resolveBaseSessionKey ( params . normalizedKey ) ] ;
153218 const entry : Record < string , unknown > | undefined = isRecord ( directEntry )
154219 ? directEntry
155220 : isRecord ( baseEntry )
156221 ? baseEntry
157222 : undefined ;
158- const data = entry ? readSessionIdentityFromEntry ( entry ) : unknownSessionIdentity ( ) ;
159- setSessionMetadataCache ( normalizedKey , data ) ;
160- return data ;
223+ return entry ? readSessionIdentityFromEntry ( entry ) : unknownSessionIdentity ( ) ;
224+ } catch {
225+ return unknownSessionIdentity ( ) ;
226+ }
227+ }
228+
229+ function buildSessionMetadataCacheKey ( params : {
230+ normalizedKey : string ;
231+ storePath : string ;
232+ } ) : string {
233+ return JSON . stringify ( {
234+ normalizedKey : params . normalizedKey ,
235+ storePath : params . storePath ,
236+ } ) ;
237+ }
238+
239+ async function resolveSessionStoreLookupContext ( params : {
240+ api ?: OpenClawPluginApi ;
241+ normalizedKey : string ;
242+ sourceAgentId ?: string ;
243+ } ) : Promise < { internals : SessionStoreInternals ; storePath : string } | null > {
244+ try {
245+ const internals = await loadSessionStoreInternals ( params . api ) ;
246+ const cfg = internals . loadConfig ( ) ;
247+ const sessionCfg = isRecord ( cfg . session ) ? cfg . session : undefined ;
248+ const storeAgentId = resolveSessionAgentId ( params . normalizedKey , params . sourceAgentId ) ;
249+ const storePath = internals . resolveStorePath ( asString ( sessionCfg ?. store ) , {
250+ agentId : storeAgentId ,
251+ } ) ;
252+ return { internals, storePath } ;
161253 } catch {
254+ return null ;
255+ }
256+ }
257+
258+ export async function resolveSessionIdentity (
259+ input :
260+ | string
261+ | undefined
262+ | {
263+ api ?: OpenClawPluginApi ;
264+ sessionKey ?: string ;
265+ sourceAgentId ?: string ;
266+ } ,
267+ ) : Promise < SessionIdentitySnapshot > {
268+ const sessionKey = typeof input === "object" ? input . sessionKey : input ;
269+ const api = typeof input === "object" ? input . api : undefined ;
270+ const sourceAgentId = typeof input === "object" ? input . sourceAgentId : undefined ;
271+ const normalizedKey = normalizeSessionStoreKey ( sessionKey ) ;
272+ if ( ! normalizedKey ) {
273+ return unknownSessionIdentity ( ) ;
274+ }
275+
276+ const lookupContext = await resolveSessionStoreLookupContext ( {
277+ api,
278+ normalizedKey,
279+ sourceAgentId,
280+ } ) ;
281+ if ( ! lookupContext ) {
162282 return unknownSessionIdentity ( ) ;
163283 }
284+
285+ const cacheKey = buildSessionMetadataCacheKey ( {
286+ normalizedKey,
287+ storePath : lookupContext . storePath ,
288+ } ) ;
289+ const cached = sessionMetadataCache . get ( cacheKey ) ;
290+ if ( cached ?. data && cached . expiresAt && Date . now ( ) < cached . expiresAt ) {
291+ return cached . data ;
292+ }
293+ if ( cached ?. promise ) {
294+ return cached . promise ;
295+ }
296+
297+ const promise = readSessionIdentity ( {
298+ normalizedKey,
299+ internals : lookupContext . internals ,
300+ storePath : lookupContext . storePath ,
301+ } ) . then ( ( data ) => {
302+ setSessionMetadataCache ( cacheKey , data ) ;
303+ return data ;
304+ } ) ;
305+ setSessionMetadataCachePromise ( cacheKey , promise ) ;
306+ return promise ;
164307}
0 commit comments