This commit is contained in:
parent
3a19b0201c
commit
91944ede4c
@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Providers/OpenRouter: preserve stored session provider when model IDs are vendor-prefixed (for example, `anthropic/...`) so follow-up turns do not incorrectly route to direct provider APIs. (#22753) Thanks @dndodson.
|
||||
- Providers/OpenRouter: preserve the required `openrouter/` prefix for OpenRouter-native model IDs during model-ref normalization. (#12942) Thanks @omair445.
|
||||
- Providers/OpenRouter: pass through provider routing parameters from model params.provider to OpenRouter request payloads for provider selection controls. (#17148) Thanks @carrotRakko.
|
||||
- Cron/Auth: propagate auth-profile resolution to isolated cron sessions so provider API keys are resolved the same way as main sessions, fixing 401 errors when using providers configured via auth-profiles. (#20689) Thanks @lailoo.
|
||||
- Telegram/Webhook: keep webhook monitors alive until gateway abort signals fire, preventing false channel exits and immediate webhook auto-restart loops.
|
||||
- Telegram/Polling: retry recoverable setup-time network failures in monitor startup and await runner teardown before retry to avoid overlapping polling sessions.
|
||||
- Telegram/Polling: clear Telegram webhooks (`deleteWebhook`) before starting long-poll `getUpdates`, including retry handling for transient cleanup failures.
|
||||
|
||||
117
src/cron/isolated-agent.auth-profile-propagation.test.ts
Normal file
117
src/cron/isolated-agent.auth-profile-propagation.test.ts
Normal file
@ -0,0 +1,117 @@
|
||||
import "./isolated-agent.mocks.js";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { runEmbeddedPiAgent } from "../agents/pi-embedded.js";
|
||||
import { runCronIsolatedAgentTurn } from "./isolated-agent.js";
|
||||
import { makeCfg, makeJob, withTempCronHome } from "./isolated-agent.test-harness.js";
|
||||
import { setupIsolatedAgentTurnMocks } from "./isolated-agent.test-setup.js";
|
||||
|
||||
describe("runCronIsolatedAgentTurn auth profile propagation (#20624)", () => {
|
||||
beforeEach(() => {
|
||||
setupIsolatedAgentTurnMocks({ fast: true });
|
||||
});
|
||||
|
||||
it("passes authProfileId to runEmbeddedPiAgent when auth profiles exist", async () => {
|
||||
await withTempCronHome(async (home) => {
|
||||
// 1. Write session store
|
||||
const sessionsDir = path.join(home, ".openclaw", "sessions");
|
||||
await fs.mkdir(sessionsDir, { recursive: true });
|
||||
const storePath = path.join(sessionsDir, "sessions.json");
|
||||
await fs.writeFile(
|
||||
storePath,
|
||||
JSON.stringify(
|
||||
{
|
||||
"agent:main:main": {
|
||||
sessionId: "main-session",
|
||||
updatedAt: Date.now(),
|
||||
lastProvider: "webchat",
|
||||
lastTo: "",
|
||||
},
|
||||
},
|
||||
null,
|
||||
2,
|
||||
),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
// 2. Write auth-profiles.json in the agent directory
|
||||
// resolveAgentDir returns <stateDir>/agents/main/agent
|
||||
// stateDir = <home>/.openclaw
|
||||
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-12345",
|
||||
},
|
||||
},
|
||||
order: {
|
||||
openrouter: ["openrouter:default"],
|
||||
},
|
||||
}),
|
||||
"utf-8",
|
||||
);
|
||||
|
||||
// 3. Mock runEmbeddedPiAgent to return ok
|
||||
vi.mocked(runEmbeddedPiAgent).mockResolvedValue({
|
||||
payloads: [{ text: "done" }],
|
||||
meta: {
|
||||
durationMs: 5,
|
||||
agentMeta: { sessionId: "s", provider: "openrouter", model: "kimi-k2.5" },
|
||||
},
|
||||
});
|
||||
|
||||
// 4. Run cron isolated agent turn with openrouter model
|
||||
const cfg = makeCfg(home, storePath, {
|
||||
agents: {
|
||||
defaults: {
|
||||
model: { primary: "openrouter/moonshotai/kimi-k2.5" },
|
||||
workspace: path.join(home, "openclaw"),
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const res = await runCronIsolatedAgentTurn({
|
||||
cfg,
|
||||
deps: {
|
||||
sendMessageSlack: vi.fn(),
|
||||
sendMessageWhatsApp: vi.fn(),
|
||||
sendMessageTelegram: vi.fn(),
|
||||
sendMessageDiscord: vi.fn(),
|
||||
sendMessageSignal: vi.fn(),
|
||||
sendMessageIMessage: vi.fn(),
|
||||
},
|
||||
job: makeJob({ kind: "agentTurn", message: "check status", deliver: false }),
|
||||
message: "check status",
|
||||
sessionKey: "cron:job-1",
|
||||
lane: "cron",
|
||||
});
|
||||
|
||||
expect(res.status).toBe("ok");
|
||||
expect(vi.mocked(runEmbeddedPiAgent)).toHaveBeenCalledTimes(1);
|
||||
|
||||
// 5. Check that authProfileId was passed
|
||||
const callArgs = vi.mocked(runEmbeddedPiAgent).mock.calls[0]?.[0] as {
|
||||
authProfileId?: string;
|
||||
authProfileIdSource?: string;
|
||||
};
|
||||
|
||||
console.log(`authProfileId passed to runEmbeddedPiAgent: ${callArgs?.authProfileId}`);
|
||||
console.log(`authProfileIdSource passed: ${callArgs?.authProfileIdSource}`);
|
||||
|
||||
if (!callArgs?.authProfileId) {
|
||||
console.log("❌ BUG CONFIRMED: isolated cron session does NOT pass authProfileId");
|
||||
console.log(" This causes 401 errors when using providers that require auth profiles");
|
||||
}
|
||||
|
||||
// This assertion will FAIL on main — proving the bug
|
||||
expect(callArgs?.authProfileId).toBe("openrouter:default");
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -32,14 +32,20 @@ vi.mock("../../agents/model-catalog.js", () => ({
|
||||
loadModelCatalog: vi.fn().mockResolvedValue({ models: [] }),
|
||||
}));
|
||||
|
||||
vi.mock("../../agents/model-selection.js", () => ({
|
||||
getModelRefStatus: vi.fn().mockReturnValue({ allowed: false }),
|
||||
isCliProvider: vi.fn().mockReturnValue(false),
|
||||
resolveAllowedModelRef: vi.fn().mockReturnValue({ ref: { provider: "openai", model: "gpt-4" } }),
|
||||
resolveConfiguredModelRef: vi.fn().mockReturnValue({ provider: "openai", model: "gpt-4" }),
|
||||
resolveHooksGmailModel: vi.fn().mockReturnValue(null),
|
||||
resolveThinkingDefault: vi.fn().mockReturnValue(undefined),
|
||||
}));
|
||||
vi.mock("../../agents/model-selection.js", async (importOriginal) => {
|
||||
const actual = await importOriginal<typeof import("../../agents/model-selection.js")>();
|
||||
return {
|
||||
...actual,
|
||||
getModelRefStatus: vi.fn().mockReturnValue({ allowed: false }),
|
||||
isCliProvider: vi.fn().mockReturnValue(false),
|
||||
resolveAllowedModelRef: vi
|
||||
.fn()
|
||||
.mockReturnValue({ ref: { provider: "openai", model: "gpt-4" } }),
|
||||
resolveConfiguredModelRef: vi.fn().mockReturnValue({ provider: "openai", model: "gpt-4" }),
|
||||
resolveHooksGmailModel: vi.fn().mockReturnValue(null),
|
||||
resolveThinkingDefault: vi.fn().mockReturnValue(undefined),
|
||||
};
|
||||
});
|
||||
|
||||
vi.mock("../../agents/model-fallback.js", () => ({
|
||||
runWithModelFallback: vi.fn().mockResolvedValue({
|
||||
|
||||
@ -5,6 +5,7 @@ import {
|
||||
resolveAgentWorkspaceDir,
|
||||
resolveDefaultAgentId,
|
||||
} from "../../agents/agent-scope.js";
|
||||
import { resolveSessionAuthProfileOverride } from "../../agents/auth-profiles/session-override.js";
|
||||
import { runCliAgent } from "../../agents/cli-runner.js";
|
||||
import { getCliSessionId, setCliSessionId } from "../../agents/cli-session.js";
|
||||
import { lookupContextTokens } from "../../agents/context.js";
|
||||
@ -432,6 +433,21 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
cronSession.sessionEntry.systemSent = true;
|
||||
await persistSessionEntry();
|
||||
|
||||
// Resolve auth profile for the session, mirroring the inbound auto-reply path
|
||||
// (get-reply-run.ts). Without this, isolated cron sessions fall back to env-var
|
||||
// auth which may not match the configured auth-profiles, causing 401 errors.
|
||||
const authProfileId = await resolveSessionAuthProfileOverride({
|
||||
cfg: cfgWithAgentDefaults,
|
||||
provider,
|
||||
agentDir,
|
||||
sessionEntry: cronSession.sessionEntry,
|
||||
sessionStore: cronSession.store,
|
||||
sessionKey: agentSessionKey,
|
||||
storePath: cronSession.storePath,
|
||||
isNewSession: cronSession.isNewSession,
|
||||
});
|
||||
const authProfileIdSource = cronSession.sessionEntry.authProfileOverrideSource;
|
||||
|
||||
let runResult: Awaited<ReturnType<typeof runEmbeddedPiAgent>>;
|
||||
let fallbackProvider = provider;
|
||||
let fallbackModel = model;
|
||||
@ -490,6 +506,8 @@ export async function runCronIsolatedAgentTurn(params: {
|
||||
lane: params.lane ?? "cron",
|
||||
provider: providerOverride,
|
||||
model: modelOverride,
|
||||
authProfileId,
|
||||
authProfileIdSource,
|
||||
thinkLevel,
|
||||
verboseLevel: resolvedVerboseLevel,
|
||||
timeoutMs,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user