diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a149cb9fb5..7a363cf4da0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Docs: https://docs.openclaw.ai ### Changes +- Plugins/active-memory: skip session-store channel entries that contain `:` when resolving the recall subagent's channel, so QQ c2c agent IDs (e.g. `c2c:10D4F7C2…`) and other scoped conversation IDs do not reach bundled-plugin `dirName` validation and crash the recall run. The same guard already applied to explicit `channelId` params (#76704); this extends it to store-derived channels. (#77396) Thanks @hclsys. - Models/auth: add `openclaw models auth list [--provider ] [--json]` so users can inspect saved per-agent auth profiles without dumping secrets or hitting the old “too many arguments” path. Thanks @vincentkoc. - Control UI/header: show the active agent name in dashboard breadcrumbs without adding the current session key, keeping non-chat views oriented without crowding the topbar. - Control UI/cron: make the New Job sidebar collapsible so the jobs list can reclaim space while keeping the form one click away. Thanks @BunsDev. diff --git a/extensions/active-memory/index.test.ts b/extensions/active-memory/index.test.ts index 9d695573c09..695c15bde84 100644 --- a/extensions/active-memory/index.test.ts +++ b/extensions/active-memory/index.test.ts @@ -2753,6 +2753,33 @@ describe("active-memory plugin", () => { }); }); + it("skips colon-containing session-store channels for embedded recall (#77396)", async () => { + hoisted.sessionStore["agent:main:qqbot:direct:12345"] = { + sessionId: "session-a", + updatedAt: 25, + channel: "c2c:10D4F7C2", + origin: { + provider: "qqbot", + }, + }; + + await hooks.before_prompt_build( + { prompt: "what wings should i order? scoped stored channel", messages: [] }, + { + agentId: "main", + trigger: "user", + sessionKey: "agent:main:qqbot:direct:12345", + messageProvider: "qqbot", + channelId: "qqbot", + }, + ); + + expect(runEmbeddedPiAgent.mock.calls.at(-1)?.[0]).toMatchObject({ + messageChannel: "qqbot", + messageProvider: "qqbot", + }); + }); + it("preserves an explicit real channel hint over a stale stored wrapper channel", async () => { hoisted.sessionStore["agent:main:telegram:direct:12345"] = { sessionId: "session-a", diff --git a/extensions/active-memory/index.ts b/extensions/active-memory/index.ts index a7d4e195953..314c3df6490 100644 --- a/extensions/active-memory/index.ts +++ b/extensions/active-memory/index.ts @@ -560,9 +560,17 @@ function resolveRecallRunChannelContext(params: { store, sessionKey: resolvedSessionKey, }).existing; - const strongEntryChannel = + const rawStrongEntryChannel = normalizeOptionalString(sessionEntry?.lastChannel) ?? normalizeOptionalString(sessionEntry?.channel); + // Channel IDs containing ":" are scoped conversation IDs (e.g. QQ c2c + // "c2c:10D4F7C2..."), not runnable channel names. The same guard that + // applies to explicit channelId (#76704) must also apply to channels + // read from the session store (#77396). + const strongEntryChannel = + rawStrongEntryChannel && !rawStrongEntryChannel.includes(":") + ? rawStrongEntryChannel + : undefined; const weakEntryChannel = normalizeOptionalString(sessionEntry?.origin?.provider); return resolveReturnValue({ resolvedChannel: strongEntryChannel ?? weakEntryChannel,