From fb9be1fcb6b6e401ae0b9f606e752bc183400123 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 3 Apr 2026 18:32:00 +0100 Subject: [PATCH] test: trim models and cron partial mocks --- src/commands/models.list.e2e.test.ts | 105 ++----- .../list.list-command.forward-compat.test.ts | 66 +---- src/commands/models/list.list-command.ts | 3 +- src/commands/models/list.registry.ts | 17 +- src/commands/models/list.rows.ts | 3 +- src/commands/models/list.runtime.ts | 12 + src/commands/models/load-config.runtime.ts | 8 + src/commands/models/load-config.ts | 8 +- src/cron/isolated-agent/run.test-harness.ts | 270 ++++++------------ src/cron/isolated-agent/run.ts | 82 +++--- 10 files changed, 208 insertions(+), 366 deletions(-) create mode 100644 src/commands/models/list.runtime.ts create mode 100644 src/commands/models/load-config.runtime.ts diff --git a/src/commands/models.list.e2e.test.ts b/src/commands/models.list.e2e.test.ts index 48962432e6f..f0fc1de3f5f 100644 --- a/src/commands/models.list.e2e.test.ts +++ b/src/commands/models.list.e2e.test.ts @@ -4,7 +4,7 @@ let modelsListCommand: typeof import("./models/list.list-command.js").modelsList let loadModelRegistry: typeof import("./models/list.registry.js").loadModelRegistry; let toModelRow: typeof import("./models/list.registry.js").toModelRow; -const loadConfig = vi.fn(); +const getRuntimeConfig = vi.fn(); const readConfigFileSnapshotForWrite = vi.fn().mockResolvedValue({ snapshot: { valid: false, resolved: {} }, writeOptions: {}, @@ -14,16 +14,9 @@ const ensureOpenClawModelsJson = vi.fn().mockResolvedValue(undefined); const resolveOpenClawAgentDir = vi.fn().mockReturnValue("/tmp/openclaw-agent"); const ensureAuthProfileStore = vi.fn().mockReturnValue({ version: 1, profiles: {} }); const listProfilesForProvider = vi.fn().mockReturnValue([]); -const resolveAuthProfileDisplayLabel = vi.fn(({ profileId }: { profileId: string }) => profileId); -const resolveAuthStorePathForDisplay = vi - .fn() - .mockReturnValue("/tmp/openclaw-agent/auth-profiles.json"); -const resolveProfileUnusableUntilForDisplay = vi.fn().mockReturnValue(null); const resolveEnvApiKey = vi.fn().mockReturnValue(undefined); const resolveAwsSdkEnvVarName = vi.fn().mockReturnValue(undefined); const hasUsableCustomProviderApiKey = vi.fn().mockReturnValue(false); -const resolveUsableCustomProviderApiKey = vi.fn().mockReturnValue(null); -const getCustomProviderApiKey = vi.fn().mockReturnValue(undefined); const modelRegistryState = { models: [] as Array>, available: [] as Array>, @@ -32,60 +25,18 @@ const modelRegistryState = { }; let previousExitCode: typeof process.exitCode; -vi.mock("../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - CONFIG_PATH: "/tmp/openclaw.json", - STATE_DIR: "/tmp/openclaw-state", - loadConfig, - readConfigFileSnapshotForWrite, - setRuntimeConfigSnapshot, - }; -}); +vi.mock("./models/load-config.runtime.js", () => ({ + getRuntimeConfig, + readSourceConfigSnapshotForWrite: readConfigFileSnapshotForWrite, + setRuntimeConfigSnapshot, + getModelsCommandSecretTargetIds: vi.fn().mockReturnValue([]), + resolveCommandSecretRefsViaGateway: vi.fn(async ({ config }: { config: unknown }) => ({ + resolvedConfig: config, + diagnostics: [], + })), +})); -vi.mock("../agents/models-config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - ensureOpenClawModelsJson, - }; -}); - -vi.mock("../agents/agent-paths.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - resolveOpenClawAgentDir, - }; -}); - -vi.mock("../agents/auth-profiles.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - ensureAuthProfileStore, - listProfilesForProvider, - resolveAuthProfileDisplayLabel, - resolveAuthStorePathForDisplay, - resolveProfileUnusableUntilForDisplay, - }; -}); - -vi.mock("../agents/model-auth.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - resolveEnvApiKey, - resolveAwsSdkEnvVarName, - hasUsableCustomProviderApiKey, - resolveUsableCustomProviderApiKey, - getCustomProviderApiKey, - }; -}); - -vi.mock("../agents/pi-model-discovery.js", async (importOriginal) => { - const actual = await importOriginal(); +vi.mock("./models/list.runtime.js", () => { class MockModelRegistry { find(provider: string, id: string) { return ( @@ -110,16 +61,16 @@ vi.mock("../agents/pi-model-discovery.js", async (importOriginal) => { } return { - ...actual, + ensureAuthProfileStore, + ensureOpenClawModelsJson, + resolveOpenClawAgentDir, + listProfilesForProvider, + resolveEnvApiKey, + resolveAwsSdkEnvVarName, + hasUsableCustomProviderApiKey, + loadModelCatalog: vi.fn(async () => []), discoverAuthStorage: () => ({}) as unknown, discoverModels: () => new MockModelRegistry() as unknown, - }; -}); - -vi.mock("../agents/pi-embedded-runner/model.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, resolveModelWithRegistry: ({ provider, modelId, @@ -158,6 +109,8 @@ beforeEach(() => { process.exitCode = undefined; modelRegistryState.getAllError = undefined; modelRegistryState.getAvailableError = undefined; + getRuntimeConfig.mockReset(); + getRuntimeConfig.mockReturnValue({}); listProfilesForProvider.mockReturnValue([]); ensureOpenClawModelsJson.mockClear(); readConfigFileSnapshotForWrite.mockClear(); @@ -225,13 +178,13 @@ describe("models list/status", () => { }; function setDefaultModel(model: string) { - loadConfig.mockReturnValue({ + getRuntimeConfig.mockReturnValue({ agents: { defaults: { model } }, }); } function configureModelAsConfigured(model: string) { - loadConfig.mockReturnValue({ + getRuntimeConfig.mockReturnValue({ agents: { defaults: { model, @@ -310,7 +263,7 @@ describe("models list/status", () => { }); it("models list plain outputs canonical zai key", async () => { - loadConfig.mockReturnValue({ + getRuntimeConfig.mockReturnValue({ agents: { defaults: { model: "z.ai/glm-4.7" } }, }); const runtime = makeRuntime(); @@ -324,7 +277,7 @@ describe("models list/status", () => { }); it("models list plain keeps canonical OpenRouter native ids", async () => { - loadConfig.mockReturnValue({ + getRuntimeConfig.mockReturnValue({ agents: { defaults: { model: "openrouter/hunter-alpha" } }, }); const runtime = makeRuntime(); @@ -441,7 +394,7 @@ describe("models list/status", () => { expect(Array.from(loaded.availableKeys ?? [])).toEqual(["openai-codex/gpt-5.3-codex-spark"]); }); - it("modelsListCommand persists using the write snapshot config when provided", async () => { + it("modelsListCommand persists using the source snapshot config when provided", async () => { modelRegistryState.models = [OPENAI_MODEL]; modelRegistryState.available = [OPENAI_MODEL]; const sourceConfig = { @@ -451,7 +404,7 @@ describe("models list/status", () => { models: { providers: { openai: { apiKey: "sk-resolved-runtime-value" } } }, // pragma: allowlist secret }; readConfigFileSnapshotForWrite.mockResolvedValue({ - snapshot: { valid: true, resolved: resolvedConfig, source: sourceConfig }, + snapshot: { valid: true, resolved: resolvedConfig, sourceConfig }, writeOptions: {}, }); setDefaultModel("openai/gpt-4.1-mini"); @@ -460,7 +413,7 @@ describe("models list/status", () => { await modelsListCommand({ all: true, json: true }, runtime); expect(ensureOpenClawModelsJson).toHaveBeenCalled(); - expect(ensureOpenClawModelsJson.mock.calls[0]?.[0]).toEqual(resolvedConfig); + expect(ensureOpenClawModelsJson.mock.calls[0]?.[0]).toEqual(sourceConfig); }); it("toModelRow does not crash without cfg/authStore when availability is undefined", async () => { diff --git a/src/commands/models/list.list-command.forward-compat.test.ts b/src/commands/models/list.list-command.forward-compat.test.ts index 7b50305e340..277abf43c70 100644 --- a/src/commands/models/list.list-command.forward-compat.test.ts +++ b/src/commands/models/list.list-command.forward-compat.test.ts @@ -42,8 +42,8 @@ const mocks = vi.hoisted(() => { return { sourceConfig, resolvedConfig, - loadConfig: vi.fn(), loadModelsConfigWithSource: vi.fn(), + ensureOpenClawModelsJson: vi.fn(), ensureAuthProfileStore: vi.fn(), loadModelRegistry: vi.fn(), loadModelCatalog: vi.fn(), @@ -55,15 +55,12 @@ const mocks = vi.hoisted(() => { }); function resetMocks() { - mocks.loadConfig.mockReturnValue({ - agents: { defaults: { model: { primary: "openai-codex/gpt-5.4" } } }, - models: { providers: {} }, - }); mocks.loadModelsConfigWithSource.mockResolvedValue({ sourceConfig: mocks.sourceConfig, resolvedConfig: mocks.resolvedConfig, diagnostics: [], }); + mocks.ensureOpenClawModelsJson.mockResolvedValue({ wrote: false }); mocks.ensureAuthProfileStore.mockReturnValue({ version: 1, profiles: {}, order: {} }); mocks.loadModelRegistry.mockResolvedValue({ models: [], @@ -114,46 +111,9 @@ async function runAllOpenAiCodexCommand() { } let modelsListCommand: typeof import("./list.list-command.js").modelsListCommand; +let listRegistryModule: typeof import("./list.registry.js"); function installModelsListCommandForwardCompatMocks() { - vi.doMock("../../config/config.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadConfig: mocks.loadConfig, - getRuntimeConfigSnapshot: vi.fn().mockReturnValue(null), - getRuntimeConfigSourceSnapshot: vi.fn().mockReturnValue(null), - }; - }); - - vi.doMock("../../agents/auth-profiles.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - ensureAuthProfileStore: mocks.ensureAuthProfileStore, - listProfilesForProvider: mocks.listProfilesForProvider, - }; - }); - vi.doMock("../../agents/auth-profiles.runtime.js", () => ({ - ensureAuthProfileStore: mocks.ensureAuthProfileStore, - })); - - vi.doMock("../../agents/models-config.js", () => ({ - ensureOpenClawModelsJson: vi.fn(async () => ({ wrote: false })), - })); - - vi.doMock("../../agents/model-catalog.js", () => ({ - loadModelCatalog: mocks.loadModelCatalog, - })); - - vi.doMock("./list.registry.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - loadModelRegistry: mocks.loadModelRegistry, - }; - }); - vi.doMock("./load-config.js", () => ({ loadModelsConfigWithSource: mocks.loadModelsConfigWithSource, })); @@ -166,18 +126,22 @@ function installModelsListCommandForwardCompatMocks() { printModelTable: mocks.printModelTable, })); - vi.doMock("../../agents/pi-embedded-runner/model.js", async (importOriginal) => { - const actual = - await importOriginal(); - return { - ...actual, - resolveModelWithRegistry: mocks.resolveModelWithRegistry, - }; - }); + vi.doMock("./list.runtime.js", () => ({ + ensureOpenClawModelsJson: mocks.ensureOpenClawModelsJson, + ensureAuthProfileStore: mocks.ensureAuthProfileStore, + listProfilesForProvider: mocks.listProfilesForProvider, + loadModelCatalog: mocks.loadModelCatalog, + resolveModelWithRegistry: mocks.resolveModelWithRegistry, + resolveEnvApiKey: vi.fn().mockReturnValue(undefined), + resolveAwsSdkEnvVarName: vi.fn().mockReturnValue(undefined), + hasUsableCustomProviderApiKey: vi.fn().mockReturnValue(false), + })); } beforeAll(async () => { installModelsListCommandForwardCompatMocks(); + listRegistryModule = await import("./list.registry.js"); + vi.spyOn(listRegistryModule, "loadModelRegistry").mockImplementation(mocks.loadModelRegistry); ({ modelsListCommand } = await import("./list.list-command.js")); }); diff --git a/src/commands/models/list.list-command.ts b/src/commands/models/list.list-command.ts index 57d0af32b95..9d88814cb9b 100644 --- a/src/commands/models/list.list-command.ts +++ b/src/commands/models/list.list-command.ts @@ -25,8 +25,7 @@ export async function modelsListCommand( runtime: RuntimeEnv, ) { ensureFlagCompatibility(opts); - const { ensureAuthProfileStore } = await import("../../agents/auth-profiles.runtime.js"); - const { ensureOpenClawModelsJson } = await import("../../agents/models-config.js"); + const { ensureAuthProfileStore, ensureOpenClawModelsJson } = await import("./list.runtime.js"); const { sourceConfig, resolvedConfig: cfg } = await loadModelsConfigWithSource({ commandName: "models list", runtime, diff --git a/src/commands/models/list.registry.ts b/src/commands/models/list.registry.ts index 0b68d9685e3..dceaead8795 100644 --- a/src/commands/models/list.registry.ts +++ b/src/commands/models/list.registry.ts @@ -1,21 +1,22 @@ import type { Api, Model } from "@mariozechner/pi-ai"; import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; -import { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; import type { AuthProfileStore } from "../../agents/auth-profiles.js"; -import { listProfilesForProvider } from "../../agents/auth-profiles.js"; -import { - hasUsableCustomProviderApiKey, - resolveAwsSdkEnvVarName, - resolveEnvApiKey, -} from "../../agents/model-auth.js"; import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js"; -import { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js"; import type { OpenClawConfig } from "../../config/config.js"; import { formatErrorWithStack, MODEL_AVAILABILITY_UNAVAILABLE_CODE, shouldFallbackToAuthHeuristics, } from "./list.errors.js"; +import { + discoverAuthStorage, + discoverModels, + hasUsableCustomProviderApiKey, + listProfilesForProvider, + resolveAwsSdkEnvVarName, + resolveEnvApiKey, + resolveOpenClawAgentDir, +} from "./list.runtime.js"; import type { ModelRow } from "./list.types.js"; import { isLocalBaseUrl, modelKey } from "./shared.js"; diff --git a/src/commands/models/list.rows.ts b/src/commands/models/list.rows.ts index d1af21c8701..b48b7e16652 100644 --- a/src/commands/models/list.rows.ts +++ b/src/commands/models/list.rows.ts @@ -1,12 +1,11 @@ import type { Api, Model } from "@mariozechner/pi-ai"; import type { ModelRegistry } from "@mariozechner/pi-coding-agent"; import type { AuthProfileStore } from "../../agents/auth-profiles.js"; -import { loadModelCatalog } from "../../agents/model-catalog.js"; import { shouldSuppressBuiltInModel } from "../../agents/model-suppression.js"; -import { resolveModelWithRegistry } from "../../agents/pi-embedded-runner/model.js"; import { normalizeProviderId } from "../../agents/provider-id.js"; import type { OpenClawConfig } from "../../config/config.js"; import { loadModelRegistry, toModelRow } from "./list.registry.js"; +import { loadModelCatalog, resolveModelWithRegistry } from "./list.runtime.js"; import type { ConfiguredEntry, ModelRow } from "./list.types.js"; import { isLocalBaseUrl, modelKey } from "./shared.js"; diff --git a/src/commands/models/list.runtime.ts b/src/commands/models/list.runtime.ts new file mode 100644 index 00000000000..56c6c38ac79 --- /dev/null +++ b/src/commands/models/list.runtime.ts @@ -0,0 +1,12 @@ +export { ensureAuthProfileStore } from "../../agents/auth-profiles.runtime.js"; +export { ensureOpenClawModelsJson } from "../../agents/models-config.js"; +export { resolveOpenClawAgentDir } from "../../agents/agent-paths.js"; +export { listProfilesForProvider } from "../../agents/auth-profiles.js"; +export { + hasUsableCustomProviderApiKey, + resolveAwsSdkEnvVarName, + resolveEnvApiKey, +} from "../../agents/model-auth.js"; +export { loadModelCatalog } from "../../agents/model-catalog.js"; +export { resolveModelWithRegistry } from "../../agents/pi-embedded-runner/model.js"; +export { discoverAuthStorage, discoverModels } from "../../agents/pi-model-discovery.js"; diff --git a/src/commands/models/load-config.runtime.ts b/src/commands/models/load-config.runtime.ts new file mode 100644 index 00000000000..b4d7c5724d0 --- /dev/null +++ b/src/commands/models/load-config.runtime.ts @@ -0,0 +1,8 @@ +export { resolveCommandSecretRefsViaGateway } from "../../cli/command-secret-gateway.js"; +export { getModelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js"; +export { + getRuntimeConfig, + readSourceConfigSnapshotForWrite, + setRuntimeConfigSnapshot, + type OpenClawConfig, +} from "../../config/config.js"; diff --git a/src/commands/models/load-config.ts b/src/commands/models/load-config.ts index c74e6ed9039..a565bceb5e8 100644 --- a/src/commands/models/load-config.ts +++ b/src/commands/models/load-config.ts @@ -1,12 +1,12 @@ -import { resolveCommandSecretRefsViaGateway } from "../../cli/command-secret-gateway.js"; -import { getModelsCommandSecretTargetIds } from "../../cli/command-secret-targets.js"; +import type { RuntimeEnv } from "../../runtime.js"; import { getRuntimeConfig, readSourceConfigSnapshotForWrite, setRuntimeConfigSnapshot, type OpenClawConfig, -} from "../../config/config.js"; -import type { RuntimeEnv } from "../../runtime.js"; + getModelsCommandSecretTargetIds, + resolveCommandSecretRefsViaGateway, +} from "./load-config.runtime.js"; export type LoadedModelsConfig = { sourceConfig: OpenClawConfig; diff --git a/src/cron/isolated-agent/run.test-harness.ts b/src/cron/isolated-agent/run.test-harness.ts index 974df944237..db534c133fa 100644 --- a/src/cron/isolated-agent/run.test-harness.ts +++ b/src/cron/isolated-agent/run.test-harness.ts @@ -1,4 +1,5 @@ import { vi, type Mock } from "vitest"; +import { LiveSessionModelSwitchError } from "../../agents/live-model-switch-error.js"; type CronSessionEntry = { sessionId: string; @@ -60,101 +61,79 @@ export const resolveCronPayloadOutcomeMock = createMock(); export const resolveCronDeliveryPlanMock = createMock(); export const resolveDeliveryTargetMock = createMock(); export const resolveSessionAuthProfileOverrideMock = createMock(); +const resolveBootstrapWarningSignaturesSeenMock = createMock(); +const resolveCronStyleNowMock = createMock(); +const resolveFastModeStateMock = createMock(); +const resolveNestedAgentLaneMock = createMock(); +const resolveAgentTimeoutMsMock = createMock(); +const deriveSessionTotalTokensMock = createMock(); +const hasNonzeroUsageMock = createMock(); +const ensureAgentWorkspaceMock = createMock(); +const normalizeThinkLevelMock = createMock(); +const normalizeVerboseLevelMock = createMock(); +const supportsXHighThinkingMock = createMock(); +const resolveSessionTranscriptPathMock = createMock(); +const setSessionRuntimeModelMock = createMock(); +const registerAgentRunContextMock = createMock(); +const buildSafeExternalPromptMock = createMock(); +const detectSuspiciousPatternsMock = createMock(); +const mapHookExternalContentSourceMock = createMock(); +const isExternalHookSessionMock = createMock(); +const resolveHookExternalContentSourceMock = createMock(); +const getSkillsSnapshotVersionMock = createMock(); +const loadModelCatalogMock = createMock(); +const getRemoteSkillEligibilityMock = createMock(); -vi.mock("../../agents/agent-scope.js", () => ({ +vi.mock("./run.runtime.js", () => ({ resolveAgentConfig: resolveAgentConfigMock, resolveAgentDir: vi.fn().mockReturnValue("/tmp/agent-dir"), resolveAgentModelFallbacksOverride: resolveAgentModelFallbacksOverrideMock, resolveAgentWorkspaceDir: vi.fn().mockReturnValue("/tmp/workspace"), resolveDefaultAgentId: vi.fn().mockReturnValue("default"), resolveAgentSkillsFilter: resolveAgentSkillsFilterMock, -})); - -vi.mock("../../agents/skills.js", () => ({ - buildWorkspaceSkillSnapshot: buildWorkspaceSkillSnapshotMock, -})); - -vi.mock("../../agents/skills/refresh.js", () => ({ - getSkillsSnapshotVersion: vi.fn().mockReturnValue(42), -})); - -vi.mock("../../agents/workspace.js", () => ({ - DEFAULT_IDENTITY_FILENAME: "IDENTITY.md", - ensureAgentWorkspace: vi.fn().mockResolvedValue({ dir: "/tmp/workspace" }), -})); - -vi.mock("../../agents/model-catalog.js", () => ({ - loadModelCatalog: vi.fn().mockResolvedValue({ models: [] }), -})); - -vi.mock("../../agents/model-selection.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - getModelRefStatus: getModelRefStatusMock, - isCliProvider: isCliProviderMock, - normalizeModelSelection: normalizeModelSelectionForTest, - resolveAllowedModelRef: resolveAllowedModelRefMock, - resolveConfiguredModelRef: resolveConfiguredModelRefMock, - resolveHooksGmailModel: resolveHooksGmailModelMock, - resolveThinkingDefault: resolveThinkingDefaultMock, - }; -}); - -vi.mock("../../agents/model-fallback.js", () => ({ - runWithModelFallback: runWithModelFallbackMock, -})); - -vi.mock("../../agents/auth-profiles/session-override.js", () => ({ resolveSessionAuthProfileOverride: resolveSessionAuthProfileOverrideMock, -})); - -vi.mock("../../agents/live-model-switch-error.js", async (importOriginal) => { - return await importOriginal(); -}); - -vi.mock("../../agents/pi-embedded.js", () => ({ + resolveBootstrapWarningSignaturesSeen: resolveBootstrapWarningSignaturesSeenMock, + lookupContextTokens: lookupContextTokensMock, + resolveCronStyleNow: resolveCronStyleNowMock, + DEFAULT_CONTEXT_TOKENS: 128000, + DEFAULT_MODEL: "gpt-4", + DEFAULT_PROVIDER: "openai", + resolveFastModeState: resolveFastModeStateMock, + resolveNestedAgentLane: resolveNestedAgentLaneMock, + LiveSessionModelSwitchError, + loadModelCatalog: loadModelCatalogMock, + runWithModelFallback: runWithModelFallbackMock, + getModelRefStatus: getModelRefStatusMock, + isCliProvider: isCliProviderMock, + normalizeModelSelection: normalizeModelSelectionForTest, + resolveAllowedModelRef: resolveAllowedModelRefMock, + resolveConfiguredModelRef: resolveConfiguredModelRefMock, + resolveHooksGmailModel: resolveHooksGmailModelMock, + resolveThinkingDefault: resolveThinkingDefaultMock, runEmbeddedPiAgent: runEmbeddedPiAgentMock, -})); - -vi.mock("../../agents/context.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - lookupContextTokens: lookupContextTokensMock, - }; -}); - -vi.mock("../../agents/date-time.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - formatUserTime: vi.fn().mockReturnValue("2026-02-10 12:00"), - resolveUserTimeFormat: vi.fn().mockReturnValue("24h"), - resolveUserTimezone: vi.fn().mockReturnValue("UTC"), - }; -}); - -vi.mock("../../agents/timeout.js", () => ({ - resolveAgentTimeoutMs: vi.fn().mockReturnValue(60_000), -})); - -vi.mock("../../agents/usage.js", () => ({ - deriveSessionTotalTokens: vi.fn().mockReturnValue(30), - hasNonzeroUsage: vi.fn().mockReturnValue(false), -})); - -vi.mock("../../agents/subagent-announce.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - runSubagentAnnounceFlow: vi.fn().mockResolvedValue(true), - }; -}); - -vi.mock("../../agents/subagent-registry-read.js", () => ({ + buildWorkspaceSkillSnapshot: buildWorkspaceSkillSnapshotMock, + getSkillsSnapshotVersion: getSkillsSnapshotVersionMock, countActiveDescendantRuns: countActiveDescendantRunsMock, listDescendantRunsForRequester: listDescendantRunsForRequesterMock, + resolveAgentTimeoutMs: resolveAgentTimeoutMsMock, + deriveSessionTotalTokens: deriveSessionTotalTokensMock, + hasNonzeroUsage: hasNonzeroUsageMock, + DEFAULT_IDENTITY_FILENAME: "IDENTITY.md", + ensureAgentWorkspace: ensureAgentWorkspaceMock, + normalizeThinkLevel: normalizeThinkLevelMock, + normalizeVerboseLevel: normalizeVerboseLevelMock, + supportsXHighThinking: supportsXHighThinkingMock, + resolveSessionTranscriptPath: resolveSessionTranscriptPathMock, + setSessionRuntimeModel: setSessionRuntimeModelMock, + registerAgentRunContext: registerAgentRunContextMock, + logWarn: (...args: unknown[]) => logWarnMock(...args), + normalizeAgentId: vi.fn((id: string) => id), + buildSafeExternalPrompt: buildSafeExternalPromptMock, + detectSuspiciousPatterns: detectSuspiciousPatternsMock, + mapHookExternalContentSource: mapHookExternalContentSourceMock, + isExternalHookSession: isExternalHookSessionMock, + resolveHookExternalContentSource: resolveHookExternalContentSourceMock, + getRemoteSkillEligibility: getRemoteSkillEligibilityMock, })); vi.mock("../../agents/cli-runner.runtime.js", () => ({ @@ -163,96 +142,10 @@ vi.mock("../../agents/cli-runner.runtime.js", () => ({ setCliSessionId: vi.fn(), })); -vi.mock("../../auto-reply/thinking.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - normalizeThinkLevel: vi.fn().mockReturnValue(undefined), - normalizeVerboseLevel: vi.fn().mockReturnValue("off"), - supportsXHighThinking: vi.fn().mockReturnValue(false), - }; -}); - -vi.mock("../../cli/outbound-send-deps.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - createOutboundSendDeps: vi.fn().mockReturnValue({}), - }; -}); - -vi.mock("../../config/sessions/paths.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - resolveSessionTranscriptPath: vi.fn().mockReturnValue("/tmp/transcript.jsonl"), - }; -}); - vi.mock("../../config/sessions/store.runtime.js", () => ({ updateSessionStore: updateSessionStoreMock, })); -vi.mock("../../config/sessions/types.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - setSessionRuntimeModel: vi.fn(), - }; -}); - -vi.mock("../../routing/session-key.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - buildAgentMainSessionKey: vi.fn().mockReturnValue("agent:default:cron:test"), - normalizeAgentId: vi.fn((id: string) => id), - }; -}); - -vi.mock("../../infra/agent-events.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - registerAgentRunContext: vi.fn(), - }; -}); - -vi.mock("../../infra/outbound/deliver.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - deliverOutboundPayloads: vi.fn().mockResolvedValue(undefined), - }; -}); - -vi.mock("../../infra/skills-remote.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - getRemoteSkillEligibility: vi.fn().mockReturnValue({}), - }; -}); - -vi.mock("../../logger.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - logWarn: (...args: unknown[]) => logWarnMock(...args), - }; -}); - -vi.mock("../../security/external-content.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - buildSafeExternalPrompt: vi.fn().mockReturnValue("safe prompt"), - detectSuspiciousPatterns: vi.fn().mockReturnValue([]), - getHookType: vi.fn().mockReturnValue("unknown"), - isExternalHookSession: vi.fn().mockReturnValue(false), - }; -}); - vi.mock("../delivery-plan.js", () => ({ resolveCronDeliveryPlan: resolveCronDeliveryPlanMock, })); @@ -275,16 +168,6 @@ vi.mock("./session.js", () => ({ resolveCronSession: resolveCronSessionMock, })); -vi.mock("../../agents/defaults.js", async (importOriginal) => { - const actual = await importOriginal(); - return { - ...actual, - DEFAULT_CONTEXT_TOKENS: 128000, - DEFAULT_MODEL: "gpt-4", - DEFAULT_PROVIDER: "openai", - }; -}); - export function makeCronSessionEntry(overrides?: Record): CronSessionEntry { return { sessionId: "test-session-id", @@ -349,6 +232,33 @@ export function resetRunCronIsolatedAgentTurnHarness(): void { resolveThinkingDefaultMock.mockReturnValue("off"); getModelRefStatusMock.mockReturnValue({ allowed: false }); isCliProviderMock.mockReturnValue(false); + resolveBootstrapWarningSignaturesSeenMock.mockReturnValue(new Set()); + resolveCronStyleNowMock.mockReturnValue({ + formattedTime: "2026-02-10 12:00", + timeLine: "Current time: 2026-02-10 12:00 UTC", + }); + resolveFastModeStateMock.mockReturnValue({ enabled: false }); + resolveNestedAgentLaneMock.mockReturnValue(undefined); + resolveAgentTimeoutMsMock.mockReturnValue(60_000); + deriveSessionTotalTokensMock.mockReturnValue(30); + hasNonzeroUsageMock.mockReturnValue(true); + ensureAgentWorkspaceMock.mockResolvedValue({ dir: "/tmp/workspace" }); + normalizeThinkLevelMock.mockImplementation((value: unknown) => value); + normalizeVerboseLevelMock.mockImplementation((value: unknown) => value ?? "off"); + supportsXHighThinkingMock.mockReturnValue(false); + resolveSessionTranscriptPathMock.mockReturnValue("/tmp/transcript.jsonl"); + setSessionRuntimeModelMock.mockReturnValue(undefined); + registerAgentRunContextMock.mockReturnValue(undefined); + buildSafeExternalPromptMock.mockImplementation( + ({ message }: { message?: string }) => message ?? "", + ); + detectSuspiciousPatternsMock.mockReturnValue([]); + mapHookExternalContentSourceMock.mockReturnValue("unknown"); + isExternalHookSessionMock.mockReturnValue(false); + resolveHookExternalContentSourceMock.mockReturnValue(undefined); + getSkillsSnapshotVersionMock.mockReturnValue(42); + loadModelCatalogMock.mockResolvedValue({ models: [] }); + getRemoteSkillEligibilityMock.mockResolvedValue({ remoteSkillsEnabled: false }); runWithModelFallbackMock.mockReset(); runWithModelFallbackMock.mockResolvedValue(makeDefaultModelFallbackResult()); diff --git a/src/cron/isolated-agent/run.ts b/src/cron/isolated-agent/run.ts index 66bd221755e..3222f2ee933 100644 --- a/src/cron/isolated-agent/run.ts +++ b/src/cron/isolated-agent/run.ts @@ -1,48 +1,5 @@ -import { - resolveAgentConfig, - resolveAgentDir, - resolveAgentModelFallbacksOverride, - resolveAgentWorkspaceDir, - resolveDefaultAgentId, -} from "../../agents/agent-scope.js"; -import { resolveSessionAuthProfileOverride } from "../../agents/auth-profiles/session-override.js"; -import { resolveBootstrapWarningSignaturesSeen } from "../../agents/bootstrap-budget.js"; -import { lookupContextTokens } from "../../agents/context.js"; -import { resolveCronStyleNow } from "../../agents/current-time.js"; -import { DEFAULT_CONTEXT_TOKENS } from "../../agents/defaults.js"; -import { resolveFastModeState } from "../../agents/fast-mode.js"; -import { resolveNestedAgentLane } from "../../agents/lanes.js"; -import { LiveSessionModelSwitchError } from "../../agents/live-model-switch-error.js"; -import { loadModelCatalog } from "../../agents/model-catalog.js"; -import { runWithModelFallback } from "../../agents/model-fallback.js"; -import { isCliProvider, resolveThinkingDefault } from "../../agents/model-selection.js"; -import { runEmbeddedPiAgent } from "../../agents/pi-embedded.js"; -import { - countActiveDescendantRuns, - listDescendantRunsForRequester, -} from "../../agents/subagent-registry-read.js"; -import { resolveAgentTimeoutMs } from "../../agents/timeout.js"; -import { deriveSessionTotalTokens, hasNonzeroUsage } from "../../agents/usage.js"; -import { ensureAgentWorkspace } from "../../agents/workspace.js"; -import { - normalizeThinkLevel, - normalizeVerboseLevel, - supportsXHighThinking, -} from "../../auto-reply/thinking.js"; import type { CliDeps } from "../../cli/outbound-send-deps.js"; import type { OpenClawConfig } from "../../config/config.js"; -import { resolveSessionTranscriptPath } from "../../config/sessions/paths.js"; -import { setSessionRuntimeModel } from "../../config/sessions/types.js"; -import { registerAgentRunContext } from "../../infra/agent-events.js"; -import { logWarn } from "../../logger.js"; -import { normalizeAgentId } from "../../routing/session-key.js"; -import { - buildSafeExternalPrompt, - detectSuspiciousPatterns, - mapHookExternalContentSource, - isExternalHookSession, - resolveHookExternalContentSource, -} from "../../security/external-content.js"; import { resolveCronDeliveryPlan } from "../delivery-plan.js"; import type { CronJob, CronRunOutcome, CronRunTelemetry } from "../types.js"; import { @@ -58,6 +15,45 @@ import { } from "./helpers.js"; import { resolveCronModelSelection } from "./model-selection.js"; import { buildCronAgentDefaultsConfig } from "./run-config.js"; +import { + DEFAULT_CONTEXT_TOKENS, + LiveSessionModelSwitchError, + buildSafeExternalPrompt, + countActiveDescendantRuns, + deriveSessionTotalTokens, + detectSuspiciousPatterns, + ensureAgentWorkspace, + hasNonzeroUsage, + isCliProvider, + isExternalHookSession, + listDescendantRunsForRequester, + loadModelCatalog, + logWarn, + lookupContextTokens, + mapHookExternalContentSource, + normalizeAgentId, + normalizeThinkLevel, + normalizeVerboseLevel, + registerAgentRunContext, + resolveAgentConfig, + resolveAgentDir, + resolveAgentModelFallbacksOverride, + resolveAgentTimeoutMs, + resolveAgentWorkspaceDir, + resolveBootstrapWarningSignaturesSeen, + resolveCronStyleNow, + resolveDefaultAgentId, + resolveFastModeState, + resolveHookExternalContentSource, + resolveNestedAgentLane, + resolveSessionAuthProfileOverride, + resolveSessionTranscriptPath, + resolveThinkingDefault, + runEmbeddedPiAgent, + runWithModelFallback, + setSessionRuntimeModel, + supportsXHighThinking, +} from "./run.runtime.js"; import { resolveCronAgentSessionKey } from "./session-key.js"; import { resolveCronSession } from "./session.js"; import { resolveCronSkillsSnapshot } from "./skills-snapshot.js";