From 5ad5ea53cdb08a5f7d6051f14419b1a91be27d7c Mon Sep 17 00:00:00 2001 From: Vignesh Natarajan Date: Sun, 22 Feb 2026 22:37:54 -0800 Subject: [PATCH] Agent: resolve resumed session agent scope before run --- CHANGELOG.md | 1 + src/commands/agent.test.ts | 25 +++++++++++++++++++++++++ src/commands/agent.ts | 22 ++++++++++++++-------- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a5240b9ab7..e5801687828 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -202,6 +202,7 @@ Docs: https://docs.openclaw.ai - Agents/Subagents: honor `tools.subagents.tools.alsoAllow` and explicit subagent `allow` entries when resolving built-in subagent deny defaults, so explicitly granted tools (for example `sessions_send`) are no longer blocked unless re-denied in `tools.subagents.tools.deny`. (#23359) Thanks @goren-beehero. - Agents/Subagents: make announce call timeouts configurable via `agents.defaults.subagents.announceTimeoutMs` and restore a 60s default to prevent false timeout failures on slower announce paths. (#22719) Thanks @Valadon. - Agents/Diagnostics: include resolved lifecycle error text in `embedded run agent end` warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize. +- Agents/Auth profiles: resolve `agentCommand` session scope before choosing `agentDir`/workspace so resumed runs no longer read auth from `agents/main/agent` when the resolved session belongs to a different/default agent (for example `agent:exec:*` sessions). (#24016) Thanks @abersonFAC. - Agents/Auth profiles: skip auth-profile cooldown writes for timeout failures in embedded runner rotation so model/network timeouts do not poison same-provider fallback model selection while still allowing in-turn account rotation. (#22622) Thanks @vageeshkumar. - Plugins/Hooks: run legacy `before_agent_start` once per agent turn and reuse that result across model-resolve and prompt-build compatibility paths, preventing duplicate hook side effects (for example duplicate external API calls). (#23289) Thanks @ksato8710. - Models/Config: default missing Anthropic provider/model `api` fields to `anthropic-messages` during config validation so custom relay model entries are preserved instead of being dropped by runtime model registry validation. (#23332) Thanks @bigbigmonkey123. diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index 91b4fd77979..3e26ec3ec00 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -205,6 +205,31 @@ describe("agentCommand", () => { }); }); + it("uses the resumed session agent scope when sessionId resolves to another agent store", async () => { + await withTempHome(async (home) => { + const storePattern = path.join(home, "sessions", "{agentId}", "sessions.json"); + const execStore = path.join(home, "sessions", "exec", "sessions.json"); + writeSessionStoreSeed(execStore, { + "agent:exec:hook:gmail:thread-1": { + sessionId: "session-exec-hook", + updatedAt: Date.now(), + systemSent: true, + }, + }); + mockConfig(home, storePattern, undefined, undefined, [ + { id: "dev" }, + { id: "exec", default: true }, + ]); + + await agentCommand({ message: "resume me", sessionId: "session-exec-hook" }, runtime); + + const callArgs = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0]; + expect(callArgs?.sessionKey).toBe("agent:exec:hook:gmail:thread-1"); + expect(callArgs?.agentId).toBe("exec"); + expect(callArgs?.agentDir).toContain(`${path.sep}agents${path.sep}exec${path.sep}agent`); + }); + }); + it("resolves resumed session transcript path from custom session store directory", async () => { await withTempHome(async (home) => { const customStoreDir = path.join(home, "custom-state"); diff --git a/src/commands/agent.ts b/src/commands/agent.ts index 314b2948b0c..6eddcfe7d67 100644 --- a/src/commands/agent.ts +++ b/src/commands/agent.ts @@ -3,6 +3,7 @@ import { resolveAgentDir, resolveEffectiveModelFallbacks, resolveAgentModelPrimary, + resolveSessionAgentId, resolveAgentSkillsFilter, resolveAgentWorkspaceDir, } from "../agents/agent-scope.js"; @@ -218,14 +219,6 @@ export async function agentCommand( } } const agentCfg = cfg.agents?.defaults; - const sessionAgentId = agentIdOverride ?? resolveAgentIdFromSessionKey(opts.sessionKey?.trim()); - const workspaceDirRaw = resolveAgentWorkspaceDir(cfg, sessionAgentId); - const agentDir = resolveAgentDir(cfg, sessionAgentId); - const workspace = await ensureAgentWorkspace({ - dir: workspaceDirRaw, - ensureBootstrapFiles: !agentCfg?.skipBootstrap, - }); - const workspaceDir = workspace.dir; const configuredModel = resolveConfiguredModelRef({ cfg, defaultProvider: DEFAULT_PROVIDER, @@ -284,6 +277,19 @@ export async function agentCommand( persistedThinking, persistedVerbose, } = sessionResolution; + const sessionAgentId = + agentIdOverride ?? + resolveSessionAgentId({ + sessionKey: sessionKey ?? opts.sessionKey?.trim(), + config: cfg, + }); + const workspaceDirRaw = resolveAgentWorkspaceDir(cfg, sessionAgentId); + const agentDir = resolveAgentDir(cfg, sessionAgentId); + const workspace = await ensureAgentWorkspace({ + dir: workspaceDirRaw, + ensureBootstrapFiles: !agentCfg?.skipBootstrap, + }); + const workspaceDir = workspace.dir; let sessionEntry = resolvedSessionEntry; const runId = opts.runId?.trim() || sessionId;