diff --git a/src/agents/model-thinking-default.ts b/src/agents/model-thinking-default.ts index 5479152253d..de415432a23 100644 --- a/src/agents/model-thinking-default.ts +++ b/src/agents/model-thinking-default.ts @@ -18,9 +18,11 @@ export function resolveThinkingDefault(params: { }): ThinkLevel { const normalizedProvider = normalizeProviderId(params.provider); const normalizedModel = normalizeLowercaseStringOrEmpty(params.model).replace(/\./g, "-"); - const catalogCandidate = params.catalog?.find( - (entry) => entry.provider === params.provider && entry.id === params.model, - ); + const catalogCandidate = Array.isArray(params.catalog) + ? params.catalog.find( + (entry) => entry.provider === params.provider && entry.id === params.model, + ) + : undefined; const configuredModels = params.cfg.agents?.defaults?.models; const canonicalKey = modelKey(params.provider, params.model); const legacyKey = legacyModelKey(params.provider, params.model); diff --git a/src/commands/agent/session.test.ts b/src/commands/agent/session.test.ts index c970b8a59bd..b030ac1522c 100644 --- a/src/commands/agent/session.test.ts +++ b/src/commands/agent/session.test.ts @@ -8,17 +8,24 @@ const mocks = vi.hoisted(() => ({ listAgentIds: vi.fn(), })); -vi.mock("../../config/sessions.js", async () => { - const actual = await vi.importActual( - "../../config/sessions.js", +vi.mock("../../config/sessions/main-session.js", async () => { + const actual = await vi.importActual( + "../../config/sessions/main-session.js", ); return { ...actual, - loadSessionStore: mocks.loadSessionStore, - resolveStorePath: mocks.resolveStorePath, + resolveExplicitAgentSessionKey: () => undefined, }; }); +vi.mock("../../config/sessions/paths.js", () => ({ + resolveStorePath: mocks.resolveStorePath, +})); + +vi.mock("../../config/sessions/store-load.js", () => ({ + loadSessionStore: mocks.loadSessionStore, +})); + vi.mock("../../agents/agent-scope.js", () => ({ listAgentIds: mocks.listAgentIds, })); diff --git a/src/cron/isolated-agent.hook-content-wrapping.test.ts b/src/cron/isolated-agent.hook-content-wrapping.test.ts index bbf78536cd7..e3c51c815ab 100644 --- a/src/cron/isolated-agent.hook-content-wrapping.test.ts +++ b/src/cron/isolated-agent.hook-content-wrapping.test.ts @@ -1,7 +1,6 @@ import "./isolated-agent.mocks.js"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { loadModelCatalog } from "../agents/model-catalog.js"; -import * as modelSelection from "../agents/model-selection.js"; import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; import { DEFAULT_MESSAGE, @@ -10,10 +9,11 @@ import { runCronTurn, withTempHome, } from "./isolated-agent.turn-test-helpers.js"; +import * as isolatedAgentRunRuntime from "./isolated-agent/run.runtime.js"; describe("runCronIsolatedAgentTurn hook content wrapping", () => { beforeEach(() => { - vi.spyOn(modelSelection, "resolveThinkingDefault").mockReturnValue("off"); + vi.spyOn(isolatedAgentRunRuntime, "resolveThinkingDefault").mockReturnValue("off"); vi.mocked(runEmbeddedPiAgent).mockClear(); vi.mocked(loadModelCatalog).mockResolvedValue([]); }); diff --git a/src/cron/isolated-agent.model-formatting.test.ts b/src/cron/isolated-agent.model-formatting.test.ts index 68d22b496af..3d58187906e 100644 --- a/src/cron/isolated-agent.model-formatting.test.ts +++ b/src/cron/isolated-agent.model-formatting.test.ts @@ -1,4 +1,5 @@ import { beforeEach, describe, expect, it, vi } from "vitest"; +import { DEFAULT_MODEL, DEFAULT_PROVIDER } from "../agents/defaults.js"; const { loadModelCatalogMock, @@ -49,8 +50,6 @@ vi.mock("../agents/model-selection.js", () => ({ import { resolveCronModelSelection } from "./isolated-agent/model-selection.js"; const DEFAULT_MESSAGE = "do it"; -const DEFAULT_PROVIDER = "anthropic"; -const DEFAULT_MODEL = "claude-opus-4-6"; type AgentTurnPayload = { kind: "agentTurn"; @@ -88,7 +87,7 @@ function parseModelRef(raw: string): { provider: string; model: string } | { err } const provider = providerRaw === "bedrock" ? "amazon-bedrock" : providerRaw; - const model = provider === "anthropic" && modelRaw === "opus-4.5" ? "claude-opus-4-6" : modelRaw; + const model = provider === "anthropic" && modelRaw === "opus-4.5" ? "claude-opus-4-5" : modelRaw; return { provider, model }; } @@ -204,7 +203,7 @@ describe("cron model formatting and precedence edge cases", () => { selectModel({ payload: { kind: "agentTurn", message: DEFAULT_MESSAGE, model: "openai/" }, }), - ).resolves.toEqual({ ok: false, error: "invalid model" }); + ).resolves.toEqual({ ok: false, error: "invalid model: openai/" }); }); it("rejects model with leading slash (empty provider)", async () => { @@ -212,7 +211,7 @@ describe("cron model formatting and precedence edge cases", () => { selectModel({ payload: { kind: "agentTurn", message: DEFAULT_MESSAGE, model: "/gpt-4.1-mini" }, }), - ).resolves.toEqual({ ok: false, error: "invalid model" }); + ).resolves.toEqual({ ok: false, error: "invalid model: /gpt-4.1-mini" }); }); it("normalizes provider casing", async () => { @@ -237,7 +236,7 @@ describe("cron model formatting and precedence edge cases", () => { model: "anthropic/opus-4.5", }, }, - { provider: "anthropic", model: "claude-opus-4-6" }, + { provider: "anthropic", model: "claude-opus-4-5" }, ); }); diff --git a/src/cron/isolated-agent.model-overrides.test.ts b/src/cron/isolated-agent.model-overrides.test.ts index 9a976137078..4db5f5fa2e0 100644 --- a/src/cron/isolated-agent.model-overrides.test.ts +++ b/src/cron/isolated-agent.model-overrides.test.ts @@ -1,7 +1,6 @@ import "./isolated-agent.mocks.js"; import { beforeEach, describe, expect, it, vi } from "vitest"; import { loadModelCatalog } from "../agents/model-catalog.js"; -import * as modelSelection from "../agents/model-selection.js"; import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; import { DEFAULT_AGENT_TURN_PAYLOAD, @@ -13,10 +12,11 @@ import { runTurnWithStoredModelOverride, withTempHome, } from "./isolated-agent.turn-test-helpers.js"; +import * as isolatedAgentRunRuntime from "./isolated-agent/run.runtime.js"; describe("runCronIsolatedAgentTurn model overrides", () => { beforeEach(() => { - vi.spyOn(modelSelection, "resolveThinkingDefault").mockReturnValue("off"); + vi.spyOn(isolatedAgentRunRuntime, "resolveThinkingDefault").mockReturnValue("off"); vi.mocked(runEmbeddedPiAgent).mockClear(); vi.mocked(loadModelCatalog).mockResolvedValue([]); }); @@ -176,7 +176,7 @@ describe("runCronIsolatedAgentTurn model overrides", () => { it("passes through the resolved default thinking level", async () => { await withTempHome(async (home) => { - vi.mocked(modelSelection.resolveThinkingDefault).mockReturnValueOnce("low"); + vi.mocked(isolatedAgentRunRuntime.resolveThinkingDefault).mockReturnValueOnce("low"); await runCronTurn(home, { jobPayload: DEFAULT_AGENT_TURN_PAYLOAD, diff --git a/src/cron/isolated-agent.session-identity.test.ts b/src/cron/isolated-agent.session-identity.test.ts index 36df45280f4..bd166b23ab0 100644 --- a/src/cron/isolated-agent.session-identity.test.ts +++ b/src/cron/isolated-agent.session-identity.test.ts @@ -1,9 +1,7 @@ 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 * as modelSelection from "../agents/model-selection.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; +import { beforeEach, describe, expect, it } from "vitest"; import { runCronIsolatedAgentTurn } from "./isolated-agent.js"; import { makeCfg, makeJob, writeSessionStore } from "./isolated-agent.test-harness.js"; import { @@ -16,13 +14,17 @@ import { withTempHome, } from "./isolated-agent.turn-test-helpers.js"; import { setupRunCronIsolatedAgentTurnSuite } from "./isolated-agent/run.suite-helpers.js"; +import { + mockRunCronFallbackPassthrough, + runEmbeddedPiAgentMock, +} from "./isolated-agent/run.test-harness.js"; setupRunCronIsolatedAgentTurnSuite(); describe("runCronIsolatedAgentTurn session identity", () => { beforeEach(() => { - vi.spyOn(modelSelection, "resolveThinkingDefault").mockReturnValue("off"); - vi.mocked(runEmbeddedPiAgent).mockClear(); + mockRunCronFallbackPassthrough(); + runEmbeddedPiAgentMock.mockClear(); }); it("passes resolved agentDir to runEmbeddedPiAgent", async () => { @@ -32,7 +34,7 @@ describe("runCronIsolatedAgentTurn session identity", () => { }); expect(res.status).toBe("ok"); - const call = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0] as { + const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { agentDir?: string; }; expect(call?.agentDir).toBe(path.join(home, ".openclaw", "agents", "main", "agent")); @@ -45,7 +47,7 @@ describe("runCronIsolatedAgentTurn session identity", () => { jobPayload: DEFAULT_AGENT_TURN_PAYLOAD, }); - const call = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0] as { + const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { prompt?: string; }; const lines = call?.prompt?.split("\n") ?? []; @@ -93,7 +95,7 @@ describe("runCronIsolatedAgentTurn session identity", () => { }); expect(res.status).toBe("ok"); - const call = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0] as { + const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { sessionKey?: string; workspaceDir?: string; sessionFile?: string; @@ -109,7 +111,7 @@ describe("runCronIsolatedAgentTurn session identity", () => { await runCronTurn(home, { jobPayload: DEFAULT_AGENT_TURN_PAYLOAD, }); - const call = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0] as { + const call = runEmbeddedPiAgentMock.mock.calls.at(-1)?.[0] as { sessionFile?: string; }; diff --git a/src/cron/isolated-agent/run.test-harness.ts b/src/cron/isolated-agent/run.test-harness.ts index 162b9b3bb33..eada1c3bf92 100644 --- a/src/cron/isolated-agent/run.test-harness.ts +++ b/src/cron/isolated-agent/run.test-harness.ts @@ -143,7 +143,7 @@ vi.mock("./skills-snapshot.runtime.js", () => ({ })); vi.mock("./run-model-selection.runtime.js", () => ({ - DEFAULT_MODEL: "gpt-4", + DEFAULT_MODEL: "gpt-5.4", DEFAULT_PROVIDER: "openai", loadModelCatalog: loadModelCatalogMock, getModelRefStatus: getModelRefStatusMock, @@ -252,7 +252,7 @@ function makeDefaultModelFallbackResult() { meta: { agentMeta: { usage: { input: 10, output: 20 } } }, }, provider: "openai", - model: "gpt-4", + model: "gpt-5.4", }; } @@ -292,8 +292,8 @@ function resetRunConfigMocks(): void { ); resolveAgentModelFallbacksOverrideMock.mockReturnValue(undefined); resolveAgentSkillsFilterMock.mockReturnValue(undefined); - resolveConfiguredModelRefMock.mockReturnValue({ provider: "openai", model: "gpt-4" }); - resolveAllowedModelRefMock.mockReturnValue({ ref: { provider: "openai", model: "gpt-4" } }); + resolveConfiguredModelRefMock.mockReturnValue({ provider: "openai", model: "gpt-5.4" }); + resolveAllowedModelRefMock.mockReturnValue({ ref: { provider: "openai", model: "gpt-5.4" } }); resolveHooksGmailModelMock.mockReturnValue(null); resolveThinkingDefaultMock.mockReturnValue("off"); getModelRefStatusMock.mockReturnValue({ allowed: false }); @@ -315,7 +315,7 @@ function resetRunConfigMocks(): void { isExternalHookSessionMock.mockReturnValue(false); resolveHookExternalContentSourceMock.mockReturnValue(undefined); getSkillsSnapshotVersionMock.mockReturnValue(42); - loadModelCatalogMock.mockResolvedValue({ models: [] }); + loadModelCatalogMock.mockResolvedValue([]); getRemoteSkillEligibilityMock.mockResolvedValue({ remoteSkillsEnabled: false }); }