diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b74db4cf16..c2c72156307 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -186,6 +186,7 @@ Docs: https://docs.openclaw.ai - Agents/model resolution: let explicit `openai-codex/gpt-5.4` selection prefer provider runtime metadata when it reports a larger context window, keeping configured Codex runs aligned with the live provider limits. (#62694) Thanks @ruclaw7. - Agents/model resolution: keep explicit-model runtime comparisons on the configured workspace plugin registry, so workspace-installed providers do not silently fall back to stale explicit metadata during runtime model lookup. - Providers/Z.AI: default onboarding and endpoint detection to GLM-5.1 instead of GLM-5. (#61998) Thanks @serg0x. +- Cron/isolated: resolve auth profiles without treating every isolated run as a brand-new auth session, so profile-based providers (for example OpenRouter) keep a stable credential choice instead of rotating or ignoring stored keys. (#62783) Thanks @neeravmakwana. ## 2026.4.5 diff --git a/src/cron/isolated-agent.isolated-auth-session-flag.test.ts b/src/cron/isolated-agent.isolated-auth-session-flag.test.ts new file mode 100644 index 00000000000..00050dc61b2 --- /dev/null +++ b/src/cron/isolated-agent.isolated-auth-session-flag.test.ts @@ -0,0 +1,88 @@ +import "./isolated-agent.mocks.js"; +import fs from "node:fs/promises"; +import path from "node:path"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import * as sessionOverride from "../agents/auth-profiles/session-override.js"; +import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { createCliDeps } from "./isolated-agent.delivery.test-helpers.js"; +import { runCronIsolatedAgentTurn } from "./isolated-agent.js"; +import { + makeCfg, + makeJob, + withTempCronHome, + writeSessionStore, +} from "./isolated-agent.test-harness.js"; +import { setupIsolatedAgentTurnMocks } from "./isolated-agent.test-setup.js"; + +describe("isolated cron resolveSessionAuthProfileOverride isNewSession (#62783)", () => { + beforeEach(() => { + setupIsolatedAgentTurnMocks({ fast: true }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it("passes isNewSession=false when sessionTarget is isolated", async () => { + const spy = vi.spyOn(sessionOverride, "resolveSessionAuthProfileOverride"); + spy.mockResolvedValue("openrouter:default"); + + await withTempCronHome(async (home) => { + const storePath = await writeSessionStore(home, { lastProvider: "webchat", lastTo: "" }); + const agentDir = path.join(home, ".openclaw", "agents", "main", "agent"); + await fs.mkdir(agentDir, { recursive: true }); + await fs.writeFile( + path.join(agentDir, "auth-profiles.json"), + JSON.stringify({ + version: 1, + profiles: { + "openrouter:default": { + type: "api_key", + provider: "openrouter", + key: "sk-or-test-key", + }, + }, + order: { openrouter: ["openrouter:default"] }, + }), + "utf-8", + ); + + vi.mocked(runEmbeddedPiAgent).mockResolvedValue({ + payloads: [{ text: "ok" }], + meta: { + durationMs: 1, + agentMeta: { sessionId: "s", provider: "openrouter", model: "kimi-k2.5" }, + }, + }); + + const cfg = makeCfg(home, storePath, { + agents: { + defaults: { + model: { primary: "openrouter/moonshotai/kimi-k2.5" }, + workspace: path.join(home, "openclaw"), + }, + }, + }); + + await runCronIsolatedAgentTurn({ + cfg, + deps: createCliDeps(), + job: { + ...makeJob({ kind: "agentTurn", message: "hi" }), + sessionTarget: "isolated", + delivery: { mode: "none" }, + }, + message: "hi", + sessionKey: "cron:auth-flag-1", + lane: "cron", + }); + }); + + const openRouterCall = spy.mock.calls.find((c) => c[0]?.provider === "openrouter"); + expect( + openRouterCall, + "resolveSessionAuthProfileOverride was not called with provider openrouter", + ).toBeDefined(); + expect(openRouterCall?.[0]?.isNewSession).toBe(false); + }); +}); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 2ef274a85ef..f91c73d9ee5 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -409,7 +409,6 @@ async function prepareCronRunContext(params: { } catch (err) { logWarn(`[cron:${input.job.id}] Failed to persist pre-run session entry: ${String(err)}`); } - const authProfileId = await resolveSessionAuthProfileOverride({ cfg: cfgWithAgentDefaults, provider, @@ -418,7 +417,7 @@ async function prepareCronRunContext(params: { sessionStore: cronSession.store, sessionKey: agentSessionKey, storePath: cronSession.storePath, - isNewSession: cronSession.isNewSession, + isNewSession: cronSession.isNewSession && input.job.sessionTarget !== "isolated", }); const liveSelection: CronLiveSelection = { provider,