diff --git a/CHANGELOG.md b/CHANGELOG.md index 3aa9365a818..e23d01b868e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,7 @@ Docs: https://docs.openclaw.ai - Plugins/onboarding: record local plugin install source metadata without duplicating raw absolute local paths in persisted `plugins.installs`, while preserving linked load-path cleanup. (#70970) Thanks @vincentkoc. - Browser/tool: tell agents not to pass per-call `timeoutMs` on existing-session type, evaluate, and other Chrome MCP actions that reject timeout overrides. - Codex/GPT-5.4: harden fallback, auth-profile, tool-schema, and replay edge cases across native and embedded runtime paths. (#70743) Thanks @100yenadmin. +- Agents/CLI: keep `--agent` plus `--session-id` lookup scoped to the requested agent store, so explicit agent resumes cannot select another agent's session. (#70985) Thanks @frankekn. - Voice-call/Telnyx: preserve inbound/outbound callback metadata and read transcription text from Telnyx's current `transcription_data` payload. - Codex harness: send verbose tool progress to chat channels for native app-server runs, matching the Pi harness `/verbose on` and `/verbose full` behavior. (#70966) Thanks @jalehman. - Codex models: fetch paginated Codex app-server model catalogs, mark truncated `/codex models` output, and keep ChatGPT OAuth defaults on the `openai-codex/gpt-5.5` route instead of the OpenAI API-key route. diff --git a/src/agents/command/session.ts b/src/agents/command/session.ts index f01e4854bc0..6908142efc9 100644 --- a/src/agents/command/session.ts +++ b/src/agents/command/session.ts @@ -58,6 +58,7 @@ function collectSessionIdMatchesForRequest(opts: { storePath: string; storeAgentId?: string; sessionId: string; + searchOtherAgentStores: boolean; }): SessionIdMatchSet { const matches: Array<[string, SessionEntry]> = []; const primaryStoreMatches: Array<[string, SessionEntry]> = []; @@ -85,6 +86,10 @@ function collectSessionIdMatchesForRequest(opts: { }; addMatches(opts.sessionStore, opts.storePath, { primary: true }); + if (!opts.searchOtherAgentStores) { + return { matches, primaryStoreMatches, storeByKey }; + } + for (const agentId of listAgentIds(opts.cfg)) { if (agentId === opts.storeAgentId) { continue; @@ -146,7 +151,9 @@ export function resolveSessionKeyForRequest(opts: { agentId: requestedAgentId, }) : undefined); - const storeAgentId = resolveAgentIdFromSessionKey(explicitSessionKey) ?? requestedAgentId; + const storeAgentId = explicitSessionKey + ? resolveAgentIdFromSessionKey(explicitSessionKey) + : (requestedAgentId ?? normalizeAgentId(undefined)); const storePath = resolveStorePath(sessionCfg?.store, { agentId: storeAgentId, }); @@ -171,6 +178,7 @@ export function resolveSessionKeyForRequest(opts: { storePath, storeAgentId, sessionId: opts.sessionId, + searchOtherAgentStores: requestedAgentId === undefined, }); const preferredSelection = resolveSessionIdMatchSelection(matches, opts.sessionId); const currentStoreSelection = diff --git a/src/commands/agent/session.test.ts b/src/commands/agent/session.test.ts index f112361ff1f..dc1c99fa4ad 100644 --- a/src/commands/agent/session.test.ts +++ b/src/commands/agent/session.test.ts @@ -126,6 +126,30 @@ describe("resolveSessionKeyForRequest", () => { expect(result.storePath).toBe(MYBOT_STORE_PATH); }); + it("does not search other agent stores when --agent scopes --session-id", async () => { + setupMainAndMybotStorePaths(); + mockStoresByPath({ + [MAIN_STORE_PATH]: { + "agent:main:whatsapp:direct:+15550000000": { + sessionId: "target-session-id", + updatedAt: 10, + }, + }, + [MYBOT_STORE_PATH]: {}, + }); + + const result = resolveSessionKeyForRequest({ + cfg: baseCfg, + agentId: "mybot", + sessionId: "target-session-id", + }); + + expect(result.sessionKey).toBe("agent:mybot:explicit:target-session-id"); + expect(result.storePath).toBe(MYBOT_STORE_PATH); + expect(mocks.loadSessionStore).toHaveBeenCalledTimes(1); + expect(mocks.loadSessionStore).toHaveBeenCalledWith(MYBOT_STORE_PATH); + }); + it("returns correct sessionStore when session found in non-primary agent store", async () => { const mybotStore = { "agent:mybot:main": { sessionId: "target-session-id", updatedAt: 0 }, diff --git a/src/gateway/server-methods/agent.test.ts b/src/gateway/server-methods/agent.test.ts index 1dbc6d1f31a..7e4c0b3556b 100644 --- a/src/gateway/server-methods/agent.test.ts +++ b/src/gateway/server-methods/agent.test.ts @@ -1018,9 +1018,11 @@ describe("gateway agent handler", () => { await waitForAssertion(() => expect(mocks.agentCommand).toHaveBeenCalled()); const call = mocks.agentCommand.mock.calls.at(-1)?.[0] as { + agentId?: string; sessionId?: string; sessionKey?: string; }; + expect(call?.agentId).toBe("main"); expect(call?.sessionId).toBe("resume-whatsapp-session"); expect(call?.sessionKey).toBeUndefined(); }); diff --git a/src/gateway/server-methods/agent.ts b/src/gateway/server-methods/agent.ts index 61bdd928f14..419c2f90568 100644 --- a/src/gateway/server-methods/agent.ts +++ b/src/gateway/server-methods/agent.ts @@ -921,6 +921,7 @@ export const agentHandlers: GatewayRequestHandlers = { message, images, imageOrder, + agentId, provider: providerOverride, model: modelOverride, to: resolvedTo,