diff --git a/src/card/streaming-card-controller.ts b/src/card/streaming-card-controller.ts index 7e98a6eb..8c57ffc7 100644 --- a/src/card/streaming-card-controller.ts +++ b/src/card/streaming-card-controller.ts @@ -12,7 +12,7 @@ */ import { readFile } from 'node:fs/promises'; -import { resolveDefaultAgentId } from 'openclaw/plugin-sdk/agent-runtime'; + import type { ReplyPayload } from 'openclaw/plugin-sdk'; import { SILENT_REPLY_TOKEN } from 'openclaw/plugin-sdk/reply-runtime'; import { extractLarkApiCode } from '../core/api-error'; @@ -141,13 +141,13 @@ export class StreamingCardController { const runtime = LarkClient.runtime as { agent?: { session?: { - resolveStorePath?: (storePath?: string) => string; + resolveStorePath?: (storePath?: string, opts?: { agentId?: string }) => string; loadSessionStore?: (storePath: string) => Record>; }; }; channel?: { session?: { - resolveStorePath?: (storePath?: string) => string; + resolveStorePath?: (storePath?: string, opts?: { agentId?: string }) => string; }; }; } | null; @@ -156,41 +156,20 @@ export class StreamingCardController { const cfgWithSession = this.deps.cfg as { sessions?: { store?: string }; session?: { store?: string } }; const sessionStorePath = cfgWithSession.sessions?.store ?? cfgWithSession.session?.store; const key = this.deps.sessionKey.trim().toLowerCase(); - - // WORKAROUND: SDK session key round-trip bug. - // The SDK's toAgentRequestSessionKey() strips the agent scope from keys - // like "agent:hr:main" → "main", then toAgentStoreSessionKey() rebuilds - // using the default agent ID → "agent:main:main". This means metrics - // written by the SDK always land under "agent::…" - // regardless of the account-scoped agent ID the plugin routing generated. - // Fallback: when the primary key misses, try replacing the agent-id - // segment with the resolved default agent ID. - // TODO: remove once the SDK preserves the original agent ID during the - // request→store key round-trip. - const defaultAgentId = resolveDefaultAgentId(this.deps.cfg as Record); - const fallbackKey = key.replace(/^(agent):[^:]+:/, `$1:${defaultAgentId}:`); - const candidateKeys = fallbackKey !== key ? [key, fallbackKey] : [key]; + // Extract agentId from session key (format: "agent::feishu:...") + // to route the store lookup to the correct per-agent session file. + const agentId = key.match(/^agent:([^:]+):/)?.[1]; const sessionApi = runtime.agent?.session; if (sessionApi?.resolveStorePath && sessionApi?.loadSessionStore) { - const storePath = sessionApi.resolveStorePath(sessionStorePath); + const storePath = sessionApi.resolveStorePath(sessionStorePath, { agentId }); const store = sessionApi.loadSessionStore(storePath); - let entry: Record | undefined; - let matchedKey: string | undefined; - for (const candidate of candidateKeys) { - const val = store[candidate]; - if (val && typeof val === 'object') { - entry = val as Record; - matchedKey = candidate; - break; - } - } - - if (!entry) { + const entry = store[key]; + if (!entry || typeof entry !== 'object') { log.debug('footer metrics lookup: session entry missing', { sessionKey: this.deps.sessionKey, - candidateKeys, + key, storePath, source: 'runtime.agent.session', }); @@ -209,7 +188,7 @@ export class StreamingCardController { }; log.debug('footer metrics lookup: session entry found', { sessionKey: this.deps.sessionKey, - matchedKey, + key, storePath, source: 'runtime.agent.session', }); @@ -221,7 +200,7 @@ export class StreamingCardController { return undefined; } - const storePath = channelSession.resolveStorePath(sessionStorePath); + const storePath = channelSession.resolveStorePath(sessionStorePath, { agentId }); const raw = await readFile(storePath, 'utf8'); const parsed: unknown = JSON.parse(raw); const store = @@ -229,21 +208,11 @@ export class StreamingCardController { ? (parsed as Record>) : {}; - let entry: Record | undefined; - let matchedKey: string | undefined; - for (const candidate of candidateKeys) { - const val = store[candidate]; - if (val && typeof val === 'object') { - entry = val as Record; - matchedKey = candidate; - break; - } - } - - if (!entry) { + const entry = store[key]; + if (!entry || typeof entry !== 'object') { log.debug('footer metrics lookup: session entry missing', { sessionKey: this.deps.sessionKey, - candidateKeys, + key, storePath, source: 'channel.session.file', }); @@ -262,7 +231,7 @@ export class StreamingCardController { }; log.debug('footer metrics lookup: session entry found', { sessionKey: this.deps.sessionKey, - matchedKey, + key, storePath, source: 'channel.session.file', });