diff --git a/src/card/streaming-card-controller.ts b/src/card/streaming-card-controller.ts index 779e1ec0..68ee0013 100644 --- a/src/card/streaming-card-controller.ts +++ b/src/card/streaming-card-controller.ts @@ -12,7 +12,6 @@ */ 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'; @@ -157,40 +156,26 @@ export class StreamingCardController { 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. + // The original fallback mechanism was incorrect because it would read from + // the default agent's session file when the correct agent's file didn't + // have the entry, causing all non-default agents to show the same metrics + // values (from main). This approach avoids that by using the correct key + // directly without any fallback. + const agentId = key.match(/^agent:([^:]+):/)?.[1]; const sessionApi = runtime.agent?.session; if (sessionApi?.resolveStorePath && sessionApi?.loadSessionStore) { - const storePath = sessionApi.resolveStorePath(sessionStorePath, { agentId: this.deps.agentId }); + 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, + agentId, + key, storePath, source: 'runtime.agent.session', }); @@ -209,7 +194,8 @@ export class StreamingCardController { }; log.debug('footer metrics lookup: session entry found', { sessionKey: this.deps.sessionKey, - matchedKey, + agentId, + key, storePath, source: 'runtime.agent.session', }); @@ -221,7 +207,7 @@ export class StreamingCardController { return undefined; } - const storePath = channelSession.resolveStorePath(sessionStorePath, { agentId: this.deps.agentId }); + const storePath = channelSession.resolveStorePath(sessionStorePath, { agentId }); const raw = await readFile(storePath, 'utf8'); const parsed: unknown = JSON.parse(raw); const store = @@ -229,21 +215,12 @@ 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, + agentId, + key, storePath, source: 'channel.session.file', }); @@ -262,7 +239,8 @@ export class StreamingCardController { }; log.debug('footer metrics lookup: session entry found', { sessionKey: this.deps.sessionKey, - matchedKey, + agentId, + key, storePath, source: 'channel.session.file', });