diff --git a/extensions/anthropic/index.test.ts b/extensions/anthropic/index.test.ts index f0407f780bc..64655fa3d4b 100644 --- a/extensions/anthropic/index.test.ts +++ b/extensions/anthropic/index.test.ts @@ -4,10 +4,10 @@ import { registerSingleProviderPlugin } from "../../test/helpers/plugins/plugin- import anthropicPlugin from "./index.js"; describe("anthropic provider replay hooks", () => { - it("registers no cli backends", async () => { + it("registers no cli commands", async () => { const captured = capturePluginRegistration({ register: anthropicPlugin.register }); - expect(captured.cliBackends).toEqual([]); + expect(captured.cliRegistrars).toEqual([]); }); it("owns native reasoning output mode for Claude transports", async () => { diff --git a/extensions/google/provider-models.test.ts b/extensions/google/provider-models.test.ts index 5ec5d4d79c4..ae01452fa92 100644 --- a/extensions/google/provider-models.test.ts +++ b/extensions/google/provider-models.test.ts @@ -54,7 +54,6 @@ describe("resolveGoogleGeminiForwardCompatModel", () => { it("resolves stable gemini 2.5 flash-lite from direct google templates for Gemini CLI when available", () => { const model = resolveGoogleGeminiForwardCompatModel({ providerId: "google-gemini-cli", - templateProviderId: "google", ctx: createContext({ provider: "google-gemini-cli", modelId: "gemini-2.5-flash-lite", @@ -73,7 +72,6 @@ describe("resolveGoogleGeminiForwardCompatModel", () => { it("resolves stable gemini 2.5 flash-lite from Gemini CLI templates when direct google templates are unavailable", () => { const model = resolveGoogleGeminiForwardCompatModel({ providerId: "google-gemini-cli", - templateProviderId: "google", ctx: createContext({ provider: "google-gemini-cli", modelId: "gemini-2.5-flash-lite", @@ -99,7 +97,6 @@ describe("resolveGoogleGeminiForwardCompatModel", () => { it("resolves gemini 3.1 pro for google aliases via an alternate template provider", () => { const model = resolveGoogleGeminiForwardCompatModel({ providerId: "google-vertex", - templateProviderId: "google-gemini-cli", ctx: createContext({ provider: "google-vertex", modelId: "gemini-3.1-pro-preview", @@ -118,7 +115,6 @@ describe("resolveGoogleGeminiForwardCompatModel", () => { it("keeps Gemini CLI 3.1 clones sourced from CLI templates when both catalogs exist", () => { const model = resolveGoogleGeminiForwardCompatModel({ providerId: "google-gemini-cli", - templateProviderId: "google", ctx: createContext({ provider: "google-gemini-cli", modelId: "gemini-3.1-pro-preview", @@ -149,7 +145,6 @@ describe("resolveGoogleGeminiForwardCompatModel", () => { it("preserves template reasoning metadata instead of forcing it on forward-compat clones", () => { const model = resolveGoogleGeminiForwardCompatModel({ providerId: "google", - templateProviderId: "google-gemini-cli", ctx: createContext({ provider: "google", modelId: "gemini-3.1-flash-preview", @@ -172,7 +167,6 @@ describe("resolveGoogleGeminiForwardCompatModel", () => { it("resolves gemini 3.1 flash from direct google templates", () => { const model = resolveGoogleGeminiForwardCompatModel({ providerId: "google", - templateProviderId: "google-gemini-cli", ctx: createContext({ provider: "google", modelId: "gemini-3.1-flash-preview", @@ -195,7 +189,6 @@ describe("resolveGoogleGeminiForwardCompatModel", () => { it("prefers the flash-lite template before the broader flash prefix", () => { const model = resolveGoogleGeminiForwardCompatModel({ providerId: "google-vertex", - templateProviderId: "google-gemini-cli", ctx: createContext({ provider: "google-vertex", modelId: "gemini-3.1-flash-lite-preview", diff --git a/extensions/lobster/src/lobster-tool.test.ts b/extensions/lobster/src/lobster-tool.test.ts index 3fd647914c1..16453c25bcb 100644 --- a/extensions/lobster/src/lobster-tool.test.ts +++ b/extensions/lobster/src/lobster-tool.test.ts @@ -44,7 +44,6 @@ function fakeApi(overrides: Partial = {}): OpenClawPluginApi registerGatewayMethod() {}, registerCli() {}, registerService() {}, - registerCliBackend() {}, registerConfigMigration() {}, registerAutoEnableProbe() {}, registerProvider() {}, diff --git a/src/agents/auth-profiles.store-cache.test.ts b/src/agents/auth-profiles.store-cache.test.ts index cfc36162832..b298c155b65 100644 --- a/src/agents/auth-profiles.store-cache.test.ts +++ b/src/agents/auth-profiles.store-cache.test.ts @@ -2,9 +2,11 @@ import fs from "node:fs"; import os from "node:os"; import path from "node:path"; import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import { AUTH_STORE_VERSION, EXTERNAL_CLI_SYNC_TTL_MS } from "./auth-profiles/constants.js"; +import { AUTH_STORE_VERSION } from "./auth-profiles/constants.js"; import type { AuthProfileStore } from "./auth-profiles/types.js"; +const AUTH_STORE_CACHE_TTL_MS = 15 * 60 * 1000; + const mocks = vi.hoisted(() => ({ syncExternalCliCredentials: vi.fn((_: AuthProfileStore) => false), })); @@ -138,7 +140,7 @@ describe("auth profile store cache", () => { expect(second.profiles["openai-codex:default"]).toMatchObject({ access: "access-1" }); expect(mocks.syncExternalCliCredentials).toHaveBeenCalledTimes(1); - vi.advanceTimersByTime(EXTERNAL_CLI_SYNC_TTL_MS + 1); + vi.advanceTimersByTime(AUTH_STORE_CACHE_TTL_MS + 1); const third = ensureAuthProfileStore(agentDir); diff --git a/src/agents/command/session-store.test.ts b/src/agents/command/session-store.test.ts index 5a8c12fa8b4..86f005107ba 100644 --- a/src/agents/command/session-store.test.ts +++ b/src/agents/command/session-store.test.ts @@ -20,7 +20,7 @@ describe("updateSessionStoreAfterAgentRun", () => { await fs.rm(tmpDir, { recursive: true, force: true }); }); - it("persists cli session bindings when the provider is configured as a CLI backend", async () => { + it("persists the runtime provider/model used by the completed run", async () => { const cfg = { agents: { defaults: { @@ -47,9 +47,6 @@ describe("updateSessionStoreAfterAgentRun", () => { sessionId: "cli-session-123", provider: "codex-cli", model: "gpt-5.4", - cliSessionBinding: { - sessionId: "cli-session-123", - }, }, }, }; @@ -65,15 +62,11 @@ describe("updateSessionStoreAfterAgentRun", () => { result, }); - expect(sessionStore[sessionKey]?.cliSessionBindings?.["codex-cli"]).toEqual({ - sessionId: "cli-session-123", - }); - expect(sessionStore[sessionKey]?.cliSessionIds?.["codex-cli"]).toBe("cli-session-123"); + expect(sessionStore[sessionKey]?.modelProvider).toBe("codex-cli"); + expect(sessionStore[sessionKey]?.model).toBe("gpt-5.4"); const persisted = loadSessionStore(storePath); - expect(persisted[sessionKey]?.cliSessionBindings?.["codex-cli"]).toEqual({ - sessionId: "cli-session-123", - }); - expect(persisted[sessionKey]?.cliSessionIds?.["codex-cli"]).toBe("cli-session-123"); + expect(persisted[sessionKey]?.modelProvider).toBe("codex-cli"); + expect(persisted[sessionKey]?.model).toBe("gpt-5.4"); }); }); diff --git a/src/agents/model-selection.test.ts b/src/agents/model-selection.test.ts index 2eb17fd6863..47ac1c81b0b 100644 --- a/src/agents/model-selection.test.ts +++ b/src/agents/model-selection.test.ts @@ -1,8 +1,6 @@ import { describe, it, expect, vi } from "vitest"; import type { OpenClawConfig } from "../config/config.js"; import { resetLogger, setLoggerOverride } from "../logging/logger.js"; -import { createEmptyPluginRegistry } from "../plugins/registry-empty.js"; -import { getActivePluginRegistry, setActivePluginRegistry } from "../plugins/runtime.js"; import { buildAllowedModelSet, inferUniqueProviderFromConfiguredModels, @@ -134,27 +132,8 @@ describe("model-selection", () => { }); describe("isCliProvider", () => { - it("treats runtime-registered CLI backends as CLI providers", () => { - const previousRegistry = getActivePluginRegistry(); - const registry = createEmptyPluginRegistry(); - try { - registry.cliBackends = [ - { - pluginId: "example-plugin", - source: "test", - backend: { - id: "example-cli", - config: { - command: "example", - }, - }, - }, - ]; - setActivePluginRegistry(registry); - expect(isCliProvider("example-cli", {} as OpenClawConfig)).toBe(true); - } finally { - setActivePluginRegistry(previousRegistry ?? createEmptyPluginRegistry()); - } + it("returns false for provider ids", () => { + expect(isCliProvider("example-cli", {} as OpenClawConfig)).toBe(false); }); }); diff --git a/src/agents/skills/plugin-skills.test.ts b/src/agents/skills/plugin-skills.test.ts index 2bd79a43a4a..8e19434d78a 100644 --- a/src/agents/skills/plugin-skills.test.ts +++ b/src/agents/skills/plugin-skills.test.ts @@ -26,7 +26,6 @@ function buildRegistry(params: { acpxRoot: string; helperRoot: string }): Plugin name: "ACPX Runtime", channels: [], providers: [], - cliBackends: [], skills: ["./skills"], hooks: [], origin: "workspace", @@ -39,7 +38,6 @@ function buildRegistry(params: { acpxRoot: string; helperRoot: string }): Plugin name: "Helper", channels: [], providers: [], - cliBackends: [], skills: ["./skills"], hooks: [], origin: "workspace", @@ -67,7 +65,6 @@ function createSinglePluginRegistry(params: { channels: [], providers: [], legacyPluginIds: params.legacyPluginIds, - cliBackends: [], skills: params.skills, hooks: [], origin: "workspace", diff --git a/src/auto-reply/reply.triggers.trigger-handling.test-harness.ts b/src/auto-reply/reply.triggers.trigger-handling.test-harness.ts index 23165905b32..b1b97e5426f 100644 --- a/src/auto-reply/reply.triggers.trigger-handling.test-harness.ts +++ b/src/auto-reply/reply.triggers.trigger-handling.test-harness.ts @@ -4,7 +4,6 @@ import os from "node:os"; import { join } from "node:path"; import { afterAll, afterEach, beforeAll, expect, vi } from "vitest"; import { clearRuntimeAuthProfileStoreSnapshots } from "../agents/auth-profiles.js"; -import { resetCliCredentialCachesForTest } from "../agents/cli-credentials.js"; import type { OpenClawConfig } from "../config/config.js"; import { resetProviderRuntimeHookCacheForTest } from "../plugins/provider-runtime.js"; import { resolveRelativeBundledPluginPublicModuleId } from "../test-utils/bundled-plugin-public-surface.js"; @@ -447,7 +446,6 @@ export async function runGreetingPromptForBareNewOrReset(params: { export function installTriggerHandlingE2eTestHooks() { afterEach(() => { clearRuntimeAuthProfileStoreSnapshots(); - resetCliCredentialCachesForTest(); resetProviderRuntimeHookCacheForTest(); vi.clearAllMocks(); }); diff --git a/src/auto-reply/reply/agent-runner-execution.test.ts b/src/auto-reply/reply/agent-runner-execution.test.ts index 852165a209c..838a7d16442 100644 --- a/src/auto-reply/reply/agent-runner-execution.test.ts +++ b/src/auto-reply/reply/agent-runner-execution.test.ts @@ -31,14 +31,6 @@ vi.mock("../../agents/model-selection.js", () => ({ isCliProvider: () => false, })); -vi.mock("../../agents/cli-runner.js", () => ({ - runCliAgent: vi.fn(), -})); - -vi.mock("../../agents/cli-session.js", () => ({ - getCliSessionId: vi.fn(), -})); - vi.mock("../../agents/bootstrap-budget.js", () => ({ resolveBootstrapWarningSignaturesSeen: () => [], })); diff --git a/src/auto-reply/reply/agent-runner-execution.ts b/src/auto-reply/reply/agent-runner-execution.ts index 56626fc0926..f94f8d5d2c3 100644 --- a/src/auto-reply/reply/agent-runner-execution.ts +++ b/src/auto-reply/reply/agent-runner-execution.ts @@ -27,7 +27,7 @@ import { updateSessionStore, } from "../../config/sessions.js"; import { logVerbose } from "../../globals.js"; -import { emitAgentEvent, registerAgentRunContext } from "../../infra/agent-events.js"; +import { registerAgentRunContext } from "../../infra/agent-events.js"; import { CommandLaneClearedError, GatewayDrainingError } from "../../process/command-queue.js"; import { defaultRuntime } from "../../runtime.js"; import { sanitizeForLog } from "../../terminal/ansi.js"; diff --git a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts index cc4181d7e1e..34c856415d3 100644 --- a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts +++ b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts @@ -37,7 +37,6 @@ function createCliBackendTestConfig() { } const runEmbeddedPiAgentMock = vi.fn(); -const runCliAgentMock = vi.fn(); const runWithModelFallbackMock = vi.fn(); const runtimeErrorMock = vi.fn(); const compactState = vi.hoisted(() => ({ @@ -83,16 +82,6 @@ vi.mock("../../agents/pi-embedded.js", async () => { }; }); -vi.mock("../../agents/cli-runner.js", async () => { - const actual = await vi.importActual( - "../../agents/cli-runner.js", - ); - return { - ...actual, - runCliAgent: (params: unknown) => runCliAgentMock(params), - }; -}); - vi.mock("../../runtime.js", async () => { const actual = await vi.importActual("../../runtime.js"); return { @@ -134,7 +123,6 @@ type RunWithModelFallbackParams = { beforeEach(() => { runEmbeddedPiAgentMock.mockClear(); - runCliAgentMock.mockClear(); runWithModelFallbackMock.mockClear(); runtimeErrorMock.mockClear(); loadCronStoreMock.mockClear(); @@ -242,8 +230,8 @@ describe("runReplyAgent onAgentRunStart", () => { }); }); - it("emits start callback when cli runner starts", async () => { - runCliAgentMock.mockResolvedValueOnce({ + it("emits start callback when the embedded runner starts", async () => { + runEmbeddedPiAgentMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }], meta: { agentMeta: { @@ -1576,7 +1564,7 @@ describe("runReplyAgent cli routing", () => { }); } - it("uses the CLI runner for codex-cli providers", async () => { + it("uses the embedded runner for codex-cli providers", async () => { const runId = "00000000-0000-0000-0000-000000000001"; const randomSpy = vi.spyOn(crypto, "randomUUID").mockReturnValue(runId); const lifecyclePhases: string[] = []; @@ -1592,7 +1580,7 @@ describe("runReplyAgent cli routing", () => { lifecyclePhases.push(phase); } }); - runCliAgentMock.mockResolvedValueOnce({ + runEmbeddedPiAgentMock.mockResolvedValueOnce({ payloads: [{ text: "ok" }], meta: { agentMeta: { @@ -1606,8 +1594,7 @@ describe("runReplyAgent cli routing", () => { unsubscribe(); randomSpy.mockRestore(); - expect(runCliAgentMock).toHaveBeenCalledTimes(1); - expect(runEmbeddedPiAgentMock).not.toHaveBeenCalled(); + expect(runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); expect(lifecyclePhases).toEqual(["start", "end"]); expect(result).toMatchObject({ text: "ok" }); }); diff --git a/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts b/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts index 1328d499257..1334044f695 100644 --- a/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts +++ b/src/auto-reply/reply/agent-runner.runreplyagent.e2e.test.ts @@ -42,7 +42,6 @@ type EmbeddedRunParams = { const state = vi.hoisted(() => ({ compactEmbeddedPiSessionMock: vi.fn(), runEmbeddedPiAgentMock: vi.fn(), - runCliAgentMock: vi.fn(), })); let modelFallbackModule: typeof import("../../agents/model-fallback.js"); @@ -86,10 +85,6 @@ vi.mock("../../agents/pi-embedded.js", () => ({ runEmbeddedPiAgent: (params: unknown) => state.runEmbeddedPiAgentMock(params), })); -vi.mock("../../agents/cli-runner.js", () => ({ - runCliAgent: (params: unknown) => state.runCliAgentMock(params), -})); - vi.mock("./queue.js", () => ({ enqueueFollowupRun: vi.fn(), refreshQueuedFollowupSession: vi.fn(), @@ -106,7 +101,6 @@ beforeAll(async () => { beforeEach(() => { state.compactEmbeddedPiSessionMock.mockClear(); state.runEmbeddedPiAgentMock.mockClear(); - state.runCliAgentMock.mockClear(); vi.mocked(enqueueFollowupRun).mockClear(); vi.mocked(refreshQueuedFollowupSession).mockClear(); vi.mocked(scheduleFollowupDrain).mockClear(); @@ -1756,11 +1750,6 @@ describe("runReplyAgent memory flush", () => { payloads: [{ text: "ok" }], meta: { agentMeta: { usage: { input: 1, output: 1 } } }, })); - state.runCliAgentMock.mockResolvedValue({ - payloads: [{ text: "ok" }], - meta: { agentMeta: { usage: { input: 1, output: 1 } } }, - }); - const baseRun = createBaseRun({ storePath, sessionEntry, @@ -1775,10 +1764,11 @@ describe("runReplyAgent memory flush", () => { commandBody: "hello", }); - expect(state.runCliAgentMock).toHaveBeenCalledTimes(1); - const call = state.runCliAgentMock.mock.calls[0]?.[0] as { prompt?: string } | undefined; + expect(state.runEmbeddedPiAgentMock).toHaveBeenCalledTimes(1); + const call = state.runEmbeddedPiAgentMock.mock.calls[0]?.[0] as + | { prompt?: string } + | undefined; expect(call?.prompt).toBe("hello"); - expect(state.runEmbeddedPiAgentMock).not.toHaveBeenCalled(); }); }); diff --git a/src/auto-reply/reply/session.test.ts b/src/auto-reply/reply/session.test.ts index 81bc1e6f114..46b8316cd75 100644 --- a/src/auto-reply/reply/session.test.ts +++ b/src/auto-reply/reply/session.test.ts @@ -1573,7 +1573,7 @@ describe("initSessionState preserves behavior overrides across /new and /reset", } }); - it("preserves selected auth profile overrides but clears stale cli session bindings across /new and /reset", async () => { + it("preserves selected auth profile overrides across /new and /reset", async () => { const storePath = await createStorePath("openclaw-reset-model-auth-"); const sessionKey = "agent:main:telegram:dm:user-model-auth"; const existingSessionId = "existing-session-model-auth"; @@ -1583,13 +1583,6 @@ describe("initSessionState preserves behavior overrides across /new and /reset", authProfileOverride: "20251001", authProfileOverrideSource: "user", authProfileOverrideCompactionCount: 2, - cliSessionIds: { "codex-cli": "cli-session-123" }, - cliSessionBindings: { - "codex-cli": { - sessionId: "cli-session-123", - authProfileId: "openai-codex:default", - }, - }, } as const; const cases = [ { @@ -1640,12 +1633,6 @@ describe("initSessionState preserves behavior overrides across /new and /reset", authProfileOverrideSource: overrides.authProfileOverrideSource, authProfileOverrideCompactionCount: overrides.authProfileOverrideCompactionCount, }); - expect(result.sessionEntry.cliSessionIds).toBeUndefined(); - expect(result.sessionEntry.cliSessionBindings).toBeUndefined(); - - const stored = JSON.parse(await fs.readFile(storePath, "utf-8")); - expect(stored[sessionKey].cliSessionIds).toBeUndefined(); - expect(stored[sessionKey].cliSessionBindings).toBeUndefined(); } }); @@ -2114,25 +2101,12 @@ describe("persistSessionUsageUpdate", () => { usage: { input: 24_000, output: 2_000, cacheRead: 8_000 }, usageIsContextSnapshot: true, providerUsed: "codex-cli", - cliSessionBinding: { - sessionId: "cli-session-1", - authProfileId: "openai-codex:default", - extraSystemPromptHash: "prompt-hash", - mcpConfigHash: "mcp-hash", - }, contextTokensUsed: 200_000, }); const stored = JSON.parse(await fs.readFile(storePath, "utf-8")); expect(stored[sessionKey].totalTokens).toBe(32_000); expect(stored[sessionKey].totalTokensFresh).toBe(true); - expect(stored[sessionKey].cliSessionIds?.["codex-cli"]).toBe("cli-session-1"); - expect(stored[sessionKey].cliSessionBindings?.["codex-cli"]).toEqual({ - sessionId: "cli-session-1", - authProfileId: "openai-codex:default", - extraSystemPromptHash: "prompt-hash", - mcpConfigHash: "mcp-hash", - }); }); it("persists totalTokens from promptTokens when usage is unavailable", async () => { diff --git a/src/commands/agent.cli-provider.test.ts b/src/commands/agent.cli-provider.test.ts deleted file mode 100644 index cf02a8b2762..00000000000 --- a/src/commands/agent.cli-provider.test.ts +++ /dev/null @@ -1,274 +0,0 @@ -import fs from "node:fs"; -import path from "node:path"; -import { beforeEach, describe, expect, it, vi } from "vitest"; -import { withTempHome as withTempHomeBase } from "../../test/helpers/temp-home.js"; -import "../cron/isolated-agent.mocks.js"; -import { __testing as acpManagerTesting } from "../acp/control-plane/manager.js"; -import * as cliRunnerModule from "../agents/cli-runner.js"; -import { FailoverError } from "../agents/failover-error.js"; -import { loadModelCatalog } from "../agents/model-catalog.js"; -import * as modelSelectionModule from "../agents/model-selection.js"; -import { runEmbeddedPiAgent } from "../agents/pi-embedded.js"; -import type { OpenClawConfig } from "../config/config.js"; -import * as configModule from "../config/config.js"; -import { clearSessionStoreCacheForTest } from "../config/sessions.js"; -import { resetAgentEventsForTest, resetAgentRunContextForTest } from "../infra/agent-events.js"; -import { resetPluginRuntimeStateForTest } from "../plugins/runtime.js"; -import type { RuntimeEnv } from "../runtime.js"; -import { agentCommand } from "./agent.js"; - -vi.mock("../logging/subsystem.js", () => { - const createMockLogger = () => ({ - subsystem: "test", - isEnabled: vi.fn(() => true), - trace: vi.fn(), - info: vi.fn(), - warn: vi.fn(), - error: vi.fn(), - debug: vi.fn(), - fatal: vi.fn(), - raw: vi.fn(), - child: vi.fn(() => createMockLogger()), - }); - return { - createSubsystemLogger: vi.fn(() => createMockLogger()), - }; -}); - -vi.mock("../agents/workspace.js", () => ({ - DEFAULT_AGENT_WORKSPACE_DIR: "/tmp/openclaw-workspace", - DEFAULT_AGENTS_FILENAME: "AGENTS.md", - DEFAULT_IDENTITY_FILENAME: "IDENTITY.md", - resolveDefaultAgentWorkspaceDir: () => "/tmp/openclaw-workspace", - ensureAgentWorkspace: vi.fn(async ({ dir }: { dir: string }) => ({ dir })), -})); - -vi.mock("../agents/skills.js", () => ({ - buildWorkspaceSkillSnapshot: vi.fn(() => undefined), - loadWorkspaceSkillEntries: vi.fn(() => []), -})); - -vi.mock("../agents/skills/refresh.js", () => ({ - getSkillsSnapshotVersion: vi.fn(() => 0), -})); - -const runtime: RuntimeEnv = { - log: vi.fn(), - error: vi.fn(), - exit: vi.fn(() => { - throw new Error("exit"); - }), -}; - -const configSpy = vi.spyOn(configModule, "loadConfig"); -const readConfigFileSnapshotForWriteSpy = vi.spyOn(configModule, "readConfigFileSnapshotForWrite"); -const runCliAgentSpy = vi.spyOn(cliRunnerModule, "runCliAgent"); - -async function withTempHome(fn: (home: string) => Promise): Promise { - return withTempHomeBase(fn, { prefix: "openclaw-agent-cli-" }); -} - -function mockConfig( - home: string, - storePath: string, - agentOverrides?: Partial["defaults"]>>, -) { - const cfg = { - agents: { - defaults: { - model: { primary: "anthropic/claude-opus-4-6" }, - models: { "anthropic/claude-opus-4-6": {} }, - workspace: path.join(home, "openclaw"), - ...agentOverrides, - }, - }, - session: { store: storePath, mainKey: "main" }, - } as OpenClawConfig; - configSpy.mockReturnValue(cfg); - return cfg; -} - -function writeSessionStoreSeed( - storePath: string, - sessions: Record>, -) { - fs.mkdirSync(path.dirname(storePath), { recursive: true }); - fs.writeFileSync(storePath, JSON.stringify(sessions, null, 2)); -} - -function readSessionStore(storePath: string): Record { - return JSON.parse(fs.readFileSync(storePath, "utf-8")) as Record; -} - -function createDefaultAgentResult() { - return { - payloads: [{ text: "ok" }], - meta: { - durationMs: 5, - agentMeta: { sessionId: "s", provider: "p", model: "m" }, - }, - }; -} - -function expectLastEmbeddedProviderModel(provider: string, model: string): void { - const callArgs = vi.mocked(runEmbeddedPiAgent).mock.calls.at(-1)?.[0]; - expect(callArgs?.provider).toBe(provider); - expect(callArgs?.model).toBe(model); -} - -beforeEach(() => { - vi.clearAllMocks(); - clearSessionStoreCacheForTest(); - resetAgentEventsForTest(); - resetAgentRunContextForTest(); - resetPluginRuntimeStateForTest(); - acpManagerTesting.resetAcpSessionManagerForTests(); - configModule.clearRuntimeConfigSnapshot(); - runCliAgentSpy.mockResolvedValue(createDefaultAgentResult() as never); - vi.mocked(runEmbeddedPiAgent).mockResolvedValue(createDefaultAgentResult()); - vi.mocked(loadModelCatalog).mockResolvedValue([]); - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation(() => false); - readConfigFileSnapshotForWriteSpy.mockResolvedValue({ - snapshot: { valid: false, resolved: {} as OpenClawConfig }, - writeOptions: {}, - } as Awaited>); -}); - -describe("agentCommand CLI provider handling", () => { - it("rejects explicit CLI overrides that are outside the models allowlist", async () => { - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation( - (provider) => provider.trim().toLowerCase() === "codex-cli", - ); - try { - await withTempHome(async (home) => { - const store = path.join(home, "sessions.json"); - mockConfig(home, store, { - models: { - "openai/gpt-4.1-mini": {}, - }, - }); - - await expect( - agentCommand( - { - message: "use disallowed cli override", - sessionKey: "agent:main:subagent:cli-override-error", - model: "codex-cli/gpt-5.4", - }, - runtime, - ), - ).rejects.toThrow('Model override "codex-cli/gpt-5.4" is not allowed for agent "main".'); - }); - } finally { - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation(() => false); - } - }); - - it("clears stored CLI overrides when they fall outside the models allowlist", async () => { - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation( - (provider) => provider.trim().toLowerCase() === "codex-cli", - ); - try { - await withTempHome(async (home) => { - const store = path.join(home, "sessions.json"); - writeSessionStoreSeed(store, { - "agent:main:subagent:clear-cli-overrides": { - sessionId: "session-clear-cli-overrides", - updatedAt: Date.now(), - providerOverride: "codex-cli", - modelOverride: "gpt-5.4", - }, - }); - - mockConfig(home, store, { - model: { primary: "openai/gpt-4.1-mini" }, - models: { - "openai/gpt-4.1-mini": {}, - }, - }); - - vi.mocked(loadModelCatalog).mockResolvedValueOnce([ - { id: "gpt-4.1-mini", name: "GPT-4.1 Mini", provider: "openai" }, - { id: "gpt-5.4", name: "GPT-5.4", provider: "codex-cli" }, - ]); - - await agentCommand( - { - message: "hi", - sessionKey: "agent:main:subagent:clear-cli-overrides", - }, - runtime, - ); - - expectLastEmbeddedProviderModel("openai", "gpt-4.1-mini"); - - const saved = readSessionStore<{ - providerOverride?: string; - modelOverride?: string; - }>(store); - expect(saved["agent:main:subagent:clear-cli-overrides"]?.providerOverride).toBeUndefined(); - expect(saved["agent:main:subagent:clear-cli-overrides"]?.modelOverride).toBeUndefined(); - }); - } finally { - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation(() => false); - } - }); - - it("clears stale CLI session IDs before retrying after session expiration", async () => { - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation( - (provider) => provider.trim().toLowerCase() === "codex-cli", - ); - try { - await withTempHome(async (home) => { - const store = path.join(home, "sessions.json"); - const sessionKey = "agent:main:subagent:cli-expired"; - writeSessionStoreSeed(store, { - [sessionKey]: { - sessionId: "session-cli-123", - updatedAt: Date.now(), - providerOverride: "codex-cli", - modelOverride: "gpt-5.4", - cliSessionIds: { "codex-cli": "stale-cli-session" }, - }, - }); - - mockConfig(home, store, { - model: { primary: "codex-cli/gpt-5.4", fallbacks: [] }, - models: { "codex-cli/gpt-5.4": {} }, - }); - - runCliAgentSpy - .mockRejectedValueOnce( - new FailoverError("session expired", { - reason: "session_expired", - provider: "codex-cli", - model: "gpt-5.4", - status: 410, - }), - ) - .mockRejectedValue(new Error("retry failed")); - - await expect(agentCommand({ message: "hi", sessionKey }, runtime)).rejects.toThrow( - "retry failed", - ); - - expect(runCliAgentSpy).toHaveBeenCalledTimes(2); - const firstCall = runCliAgentSpy.mock.calls[0]?.[0] as - | { cliSessionId?: string } - | undefined; - const secondCall = runCliAgentSpy.mock.calls[1]?.[0] as - | { cliSessionId?: string } - | undefined; - expect(firstCall?.cliSessionId).toBe("stale-cli-session"); - expect(secondCall?.cliSessionId).toBeUndefined(); - - const saved = readSessionStore<{ - cliSessionIds?: Record; - }>(store); - expect(saved[sessionKey]?.cliSessionIds?.["codex-cli"]).toBeUndefined(); - }); - } finally { - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation(() => false); - } - }); -}); diff --git a/src/commands/agent.test.ts b/src/commands/agent.test.ts index 1e8a9b768d5..eb84e5b277f 100644 --- a/src/commands/agent.test.ts +++ b/src/commands/agent.test.ts @@ -6,7 +6,6 @@ import "../cron/isolated-agent.mocks.js"; import { __testing as acpManagerTesting } from "../acp/control-plane/manager.js"; import { resolveAgentDir, resolveSessionAgentId } from "../agents/agent-scope.js"; import * as authProfilesModule from "../agents/auth-profiles.js"; -import * as cliRunnerModule from "../agents/cli-runner.js"; import * as sessionStoreModule from "../agents/command/session-store.js"; import { resolveSession } from "../agents/command/session.js"; import { loadModelCatalog } from "../agents/model-catalog.js"; @@ -96,7 +95,6 @@ const runtime: RuntimeEnv = { const configSpy = vi.spyOn(configModule, "loadConfig"); const readConfigFileSnapshotForWriteSpy = vi.spyOn(configModule, "readConfigFileSnapshotForWrite"); -const runCliAgentSpy = vi.spyOn(cliRunnerModule, "runCliAgent"); async function withTempHome(fn: (home: string) => Promise): Promise { return withTempHomeBase(fn, { prefix: "openclaw-agent-" }); @@ -336,7 +334,6 @@ beforeEach(() => { resetPluginRuntimeStateForTest(); acpManagerTesting.resetAcpSessionManagerForTests(); configModule.clearRuntimeConfigSnapshot(); - runCliAgentSpy.mockResolvedValue(createDefaultAgentResult() as never); vi.mocked(runEmbeddedPiAgent).mockResolvedValue(createDefaultAgentResult()); vi.mocked(loadModelCatalog).mockResolvedValue([]); vi.mocked(modelSelectionModule.isCliProvider).mockImplementation(() => false); @@ -568,15 +565,14 @@ describe("agentCommand", () => { }); }); - it("persists explicit session-id-only CLI runs with the synthetic session key", async () => { + it("persists explicit session-id-only runs with the synthetic session key", async () => { await withTempHome(async (home) => { const store = path.join(home, "sessions.json"); mockConfig(home, store, { model: { primary: "codex-cli/gpt-5.4" }, models: { "codex-cli/gpt-5.4": {} }, }); - vi.mocked(modelSelectionModule.isCliProvider).mockImplementation(() => true); - runCliAgentSpy.mockResolvedValue({ + vi.mocked(runEmbeddedPiAgent).mockResolvedValue({ payloads: [{ text: "ok" }], meta: { durationMs: 5, @@ -584,12 +580,9 @@ describe("agentCommand", () => { sessionId: "codex-cli-session-1", provider: "codex-cli", model: "gpt-5.4", - cliSessionBinding: { - sessionId: "codex-cli-session-1", - }, }, }, - } as never); + }); await agentCommand({ message: "resume me", sessionId: "explicit-session-123" }, runtime); diff --git a/src/commands/agent/session-store.test.ts b/src/commands/agent/session-store.test.ts index 1d33dd24c00..3755ff62ef0 100644 --- a/src/commands/agent/session-store.test.ts +++ b/src/commands/agent/session-store.test.ts @@ -126,7 +126,7 @@ describe("updateSessionStoreAfterAgentRun", () => { ); }); - it("stores and reloads CLI bindings for explicit session-id-only runs", async () => { + it("stores and reloads the runtime model for explicit session-id-only runs", async () => { const dir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-session-store-")); const storePath = path.join(dir, "sessions.json"); const cfg = { @@ -165,10 +165,6 @@ describe("updateSessionStoreAfterAgentRun", () => { provider: "codex-cli", model: "gpt-5.4", sessionId: "codex-cli-session-1", - cliSessionBinding: { - sessionId: "codex-cli-session-1", - authEpoch: "auth-epoch-1", - }, }, }, } as never, @@ -180,15 +176,11 @@ describe("updateSessionStoreAfterAgentRun", () => { }); expect(second.sessionKey).toBe(first.sessionKey); - expect(second.sessionEntry?.cliSessionBindings?.["codex-cli"]).toEqual({ - sessionId: "codex-cli-session-1", - authEpoch: "auth-epoch-1", - }); + expect(second.sessionEntry?.modelProvider).toBe("codex-cli"); + expect(second.sessionEntry?.model).toBe("gpt-5.4"); const persisted = loadSessionStore(storePath, { skipCache: true })[first.sessionKey!]; - expect(persisted?.cliSessionBindings?.["codex-cli"]).toEqual({ - sessionId: "codex-cli-session-1", - authEpoch: "auth-epoch-1", - }); + expect(persisted?.modelProvider).toBe("codex-cli"); + expect(persisted?.model).toBe("gpt-5.4"); }); }); diff --git a/src/commands/doctor/shared/preview-warnings.test.ts b/src/commands/doctor/shared/preview-warnings.test.ts index d62e552f9e1..aeb893c7b49 100644 --- a/src/commands/doctor/shared/preview-warnings.test.ts +++ b/src/commands/doctor/shared/preview-warnings.test.ts @@ -10,7 +10,6 @@ function manifest(id: string): PluginManifestRecord { id, channels: [], providers: [], - cliBackends: [], skills: [], hooks: [], origin: "bundled", diff --git a/src/commands/doctor/shared/stale-plugin-config.test.ts b/src/commands/doctor/shared/stale-plugin-config.test.ts index 5ebc4d63490..0ad0006e9ad 100644 --- a/src/commands/doctor/shared/stale-plugin-config.test.ts +++ b/src/commands/doctor/shared/stale-plugin-config.test.ts @@ -13,7 +13,6 @@ function manifest(id: string): PluginManifestRecord { id, channels: [], providers: [], - cliBackends: [], skills: [], hooks: [], origin: "bundled", diff --git a/src/config/io.write-config.test.ts b/src/config/io.write-config.test.ts index a94c7266662..307c5e2fd87 100644 --- a/src/config/io.write-config.test.ts +++ b/src/config/io.write-config.test.ts @@ -37,7 +37,6 @@ describe("config io write", () => { origin: "bundled", channels: ["bluebubbles"], providers: [], - cliBackends: [], skills: [], hooks: [], rootDir: "/virtual/plugins/bluebubbles", @@ -593,7 +592,19 @@ describe("config io write", () => { const snapshot = await io.readConfigFileSnapshot(); expect(snapshot.valid).toBe(true); - const next = structuredClone(snapshot.config); + const next = structuredClone(snapshot.config) as { + agents?: { + defaults?: { + cliBackends?: Record< + string, + { + command?: string; + args?: string[]; + } + >; + }; + }; + }; const codexBackend = next.agents?.defaults?.cliBackends?.codex; const args = Array.isArray(codexBackend?.args) ? codexBackend?.args : []; next.agents = { @@ -611,7 +622,7 @@ describe("config io write", () => { }, }; - await io.writeConfigFile(next); + await io.writeConfigFile(next as OpenClawConfig); const persisted = JSON.parse(await fs.readFile(configPath, "utf-8")) as { agents: { diff --git a/src/config/sessions.cache.test.ts b/src/config/sessions.cache.test.ts index 48927098a37..5b827837be4 100644 --- a/src/config/sessions.cache.test.ts +++ b/src/config/sessions.cache.test.ts @@ -90,7 +90,7 @@ describe("Session Store Cache", () => { it("should not allow cached session mutations to leak across loads", async () => { const testStore = createSingleSessionStore( createSessionEntry({ - cliSessionIds: { openai: "sess-1" }, + origin: { provider: "openai" }, skillsSnapshot: { prompt: "skills", skills: [{ name: "alpha" }], @@ -101,13 +101,13 @@ describe("Session Store Cache", () => { await saveSessionStore(storePath, testStore); const loaded1 = loadSessionStore(storePath); - loaded1["session:1"].cliSessionIds = { openai: "mutated" }; + loaded1["session:1"].origin = { provider: "mutated" }; if (loaded1["session:1"].skillsSnapshot?.skills?.length) { loaded1["session:1"].skillsSnapshot.skills[0].name = "mutated"; } const loaded2 = loadSessionStore(storePath); - expect(loaded2["session:1"].cliSessionIds?.openai).toBe("sess-1"); + expect(loaded2["session:1"].origin?.provider).toBe("openai"); expect(loaded2["session:1"].skillsSnapshot?.skills?.[0]?.name).toBe("alpha"); }); diff --git a/src/gateway/gateway-cli-backend.live.test.ts b/src/gateway/gateway-cli-backend.live.test.ts index ae7ab14ba0e..2ed881285c0 100644 --- a/src/gateway/gateway-cli-backend.live.test.ts +++ b/src/gateway/gateway-cli-backend.live.test.ts @@ -6,7 +6,7 @@ import path from "node:path"; import { describe, expect, it } from "vitest"; import { isLiveTestEnabled } from "../agents/live-test-helpers.js"; import { parseModelRef } from "../agents/model-selection.js"; -import { clearRuntimeConfigSnapshot, loadConfig } from "../config/config.js"; +import { clearRuntimeConfigSnapshot, loadConfig, type OpenClawConfig } from "../config/config.js"; import { isTruthyEnvValue } from "../infra/env.js"; import { getFreePortBlockWithPermissionFallback } from "../test-utils/ports.js"; import { GATEWAY_CLIENT_NAMES } from "../utils/message-channel.js"; @@ -298,7 +298,14 @@ describeLive("gateway live (cli backend)", () => { const cliArgs = baseCliArgs; const cfg = loadConfig(); - const existingBackends = cfg.agents?.defaults?.cliBackends ?? {}; + const cfgWithCliBackends = cfg as OpenClawConfig & { + agents?: { + defaults?: { + cliBackends?: Record>; + }; + }; + }; + const existingBackends = cfgWithCliBackends.agents?.defaults?.cliBackends ?? {}; const nextCfg = { ...cfg, agents: { diff --git a/src/gateway/server-methods/agent.test.ts b/src/gateway/server-methods/agent.test.ts index 4874e4264c2..87bc5ff3602 100644 --- a/src/gateway/server-methods/agent.test.ts +++ b/src/gateway/server-methods/agent.test.ts @@ -146,16 +146,6 @@ function mockMainSessionEntry(entry: Record, cfg: Record | undefined; - mocks.updateSessionStore.mockImplementation(async (_path, updater) => { - const store: Record = {}; - await updater(store); - capturedEntry = store["agent:main:main"] as Record; - }); - return () => capturedEntry; -} - function buildExistingMainStoreEntry(overrides: Record = {}) { return { sessionId: "existing-session-id", @@ -164,17 +154,6 @@ function buildExistingMainStoreEntry(overrides: Record = {}) { }; } -async function runMainAgentAndCaptureEntry(idempotencyKey: string) { - const getCapturedEntry = captureUpdatedMainEntry(); - mocks.agentCommand.mockResolvedValue({ - payloads: [{ text: "ok" }], - meta: { durationMs: 100 }, - }); - await runMainAgent("test", idempotencyKey); - expect(mocks.updateSessionStore).toHaveBeenCalled(); - return getCapturedEntry(); -} - function setupNewYorkTimeConfig(isoDate: string) { vi.useFakeTimers(); vi.setSystemTime(new Date(isoDate)); // Wed Jan 28, 8:30 PM EST @@ -452,18 +431,6 @@ describe("gateway agent handler", () => { ); }); - it("preserves cliSessionIds from existing session entry", async () => { - const existingCliSessionIds = { "codex-cli": "abc-123-def" }; - - mockMainSessionEntry({ - cliSessionIds: existingCliSessionIds, - }); - - const capturedEntry = await runMainAgentAndCaptureEntry("test-idem"); - expect(capturedEntry).toBeDefined(); - expect(capturedEntry?.cliSessionIds).toEqual(existingCliSessionIds); - }); - it("reactivates completed subagent sessions and broadcasts send updates", async () => { const childSessionKey = "agent:main:subagent:followup"; const completedRun = { @@ -893,15 +860,6 @@ describe("gateway agent handler", () => { }); }); - it("handles missing cliSessionIds gracefully", async () => { - mockMainSessionEntry({}); - - const capturedEntry = await runMainAgentAndCaptureEntry("test-idem-2"); - expect(capturedEntry).toBeDefined(); - // Should be undefined, not cause an error - expect(capturedEntry?.cliSessionIds).toBeUndefined(); - }); - it("prunes legacy main alias keys when writing a canonical session entry", async () => { mocks.loadSessionEntry.mockReturnValue({ cfg: { diff --git a/src/gateway/server-methods/chat.ts b/src/gateway/server-methods/chat.ts index b1f231c8dfb..38036494fab 100644 --- a/src/gateway/server-methods/chat.ts +++ b/src/gateway/server-methods/chat.ts @@ -1258,11 +1258,16 @@ export const chatHandlers: GatewayRequestHandlers = { } let thinkingLevel = entry?.thinkingLevel; if (!thinkingLevel) { + const sessionAgentId = resolveSessionAgentId({ + sessionKey, + config: cfg, + }); + const resolvedModel = resolveSessionModelRef(cfg, entry, sessionAgentId); const catalog = await context.loadGatewayModelCatalog(); thinkingLevel = resolveThinkingDefault({ cfg, - provider: resolvedSessionModel.provider, - model: resolvedSessionModel.model, + provider: resolvedModel.provider, + model: resolvedModel.model, catalog, }); } diff --git a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts index 20fc51714ce..4ac12511a29 100644 --- a/src/gateway/server.sessions.gateway-server-sessions-a.test.ts +++ b/src/gateway/server.sessions.gateway-server-sessions-a.test.ts @@ -1350,16 +1350,6 @@ describe("gateway server sessions", () => { execAsk: "on-miss", execNode: "mac-mini", displayName: "Ops Child", - cliSessionIds: { - "codex-cli": "cli-session-123", - }, - cliSessionBindings: { - "codex-cli": { - sessionId: "cli-session-123", - authProfileId: "openai-codex:work", - extraSystemPromptHash: "prompt-hash", - }, - }, deliveryContext: { channel: "discord", to: "discord:child", @@ -1409,16 +1399,6 @@ describe("gateway server sessions", () => { execAsk?: string; execNode?: string; displayName?: string; - cliSessionBindings?: Record< - string, - { - sessionId?: string; - authProfileId?: string; - extraSystemPromptHash?: string; - mcpConfigHash?: string; - } - >; - cliSessionIds?: Record; deliveryContext?: { channel?: string; to?: string; @@ -1463,16 +1443,6 @@ describe("gateway server sessions", () => { expect(reset.payload?.entry.execAsk).toBe("on-miss"); expect(reset.payload?.entry.execNode).toBe("mac-mini"); expect(reset.payload?.entry.displayName).toBe("Ops Child"); - expect(reset.payload?.entry.cliSessionBindings).toEqual({ - "codex-cli": { - sessionId: "cli-session-123", - authProfileId: "openai-codex:work", - extraSystemPromptHash: "prompt-hash", - }, - }); - expect(reset.payload?.entry.cliSessionIds).toEqual({ - "codex-cli": "cli-session-123", - }); expect(reset.payload?.entry.deliveryContext).toEqual({ channel: "discord", to: "discord:child", @@ -1517,16 +1487,6 @@ describe("gateway server sessions", () => { execAsk?: string; execNode?: string; displayName?: string; - cliSessionBindings?: Record< - string, - { - sessionId?: string; - authProfileId?: string; - extraSystemPromptHash?: string; - mcpConfigHash?: string; - } - >; - cliSessionIds?: Record; deliveryContext?: { channel?: string; to?: string; @@ -1569,16 +1529,6 @@ describe("gateway server sessions", () => { expect(store["agent:main:subagent:child"]?.execAsk).toBe("on-miss"); expect(store["agent:main:subagent:child"]?.execNode).toBe("mac-mini"); expect(store["agent:main:subagent:child"]?.displayName).toBe("Ops Child"); - expect(store["agent:main:subagent:child"]?.cliSessionBindings).toEqual({ - "codex-cli": { - sessionId: "cli-session-123", - authProfileId: "openai-codex:work", - extraSystemPromptHash: "prompt-hash", - }, - }); - expect(store["agent:main:subagent:child"]?.cliSessionIds).toEqual({ - "codex-cli": "cli-session-123", - }); expect(store["agent:main:subagent:child"]?.deliveryContext).toEqual({ channel: "discord", to: "discord:child", diff --git a/src/infra/provider-usage.auth.normalizes-keys.test.ts b/src/infra/provider-usage.auth.normalizes-keys.test.ts index e7928071a27..c9e428f35c6 100644 --- a/src/infra/provider-usage.auth.normalizes-keys.test.ts +++ b/src/infra/provider-usage.auth.normalizes-keys.test.ts @@ -478,8 +478,11 @@ describe("resolveProviderAuths key normalization", () => { expectedToken: "plain-google-token", }, ])("$name", async ({ token, expectedToken }) => { + const googleGeminiCliUsageProvider = "google-gemini-cli" as unknown as Parameters< + typeof resolveProviderAuths + >[0]["providers"][number]; await expectResolvedAuthsFromSuiteHome({ - providers: ["google-gemini-cli"], + providers: [googleGeminiCliUsageProvider], setup: async (home) => { await writeAuthProfiles(home, { "google-gemini-cli:default": { @@ -489,7 +492,7 @@ describe("resolveProviderAuths key normalization", () => { }, }); }, - expected: [{ provider: "google-gemini-cli", token: expectedToken }], + expected: [{ provider: googleGeminiCliUsageProvider, token: expectedToken }], }); }); diff --git a/src/infra/provider-usage.fetch.gemini.test.ts b/src/infra/provider-usage.fetch.gemini.test.ts index c21292ebf97..35c113a30cf 100644 --- a/src/infra/provider-usage.fetch.gemini.test.ts +++ b/src/infra/provider-usage.fetch.gemini.test.ts @@ -2,12 +2,14 @@ import { describe, expect, it } from "vitest"; import { createProviderUsageFetch, makeResponse } from "../test-utils/provider-usage-fetch.js"; import { fetchGeminiUsage } from "./provider-usage.fetch.gemini.js"; +const usageProvider = "openai-codex" as const; + describe("fetchGeminiUsage", () => { it("returns HTTP errors for failed requests", async () => { const mockFetch = createProviderUsageFetch(async () => makeResponse(429, { error: "rate_limited" }), ); - const result = await fetchGeminiUsage("token", 5000, mockFetch, "google-gemini-cli"); + const result = await fetchGeminiUsage("token", 5000, mockFetch, usageProvider); expect(result.error).toBe("HTTP 429"); expect(result.windows).toHaveLength(0); @@ -29,7 +31,7 @@ describe("fetchGeminiUsage", () => { }); }); - const result = await fetchGeminiUsage("token", 5000, mockFetch, "google-gemini-cli"); + const result = await fetchGeminiUsage("token", 5000, mockFetch, usageProvider); expect(result.windows).toHaveLength(2); expect(result.windows[0]).toEqual({ label: "Pro", usedPercent: 70 }); @@ -44,11 +46,11 @@ describe("fetchGeminiUsage", () => { }), ); - const result = await fetchGeminiUsage("token", 5000, mockFetch, "google-gemini-cli"); + const result = await fetchGeminiUsage("token", 5000, mockFetch, usageProvider); expect(result).toEqual({ - provider: "google-gemini-cli", - displayName: "Gemini", + provider: usageProvider, + displayName: "Codex", windows: [], }); }); @@ -64,7 +66,7 @@ describe("fetchGeminiUsage", () => { }), ); - const result = await fetchGeminiUsage("token", 5000, mockFetch, "google-gemini-cli"); + const result = await fetchGeminiUsage("token", 5000, mockFetch, usageProvider); expect(result.windows).toEqual([ { label: "Pro", usedPercent: 100 }, diff --git a/src/infra/provider-usage.load.test.ts b/src/infra/provider-usage.load.test.ts index 340ee6297a2..84836801532 100644 --- a/src/infra/provider-usage.load.test.ts +++ b/src/infra/provider-usage.load.test.ts @@ -9,6 +9,7 @@ import { } from "./provider-usage.test-support.js"; type ProviderAuth = ProviderUsageAuth; +const googleGeminiCliProvider = "google-gemini-cli" as unknown as ProviderAuth["provider"]; describe("provider-usage.load", () => { beforeEach(() => { @@ -50,7 +51,7 @@ describe("provider-usage.load", () => { loadProviderUsageSummary, [ { provider: "github-copilot", token: "copilot-token" }, - { provider: "google-gemini-cli", token: "gemini-token" }, + { provider: googleGeminiCliProvider, token: "gemini-token" }, { provider: "openai-codex", token: "codex-token", accountId: "acc-1" }, { provider: "xiaomi", token: "xiaomi-token" }, ], @@ -59,7 +60,7 @@ describe("provider-usage.load", () => { expect(summary.providers.map((provider) => provider.provider)).toEqual([ "github-copilot", - "google-gemini-cli", + googleGeminiCliProvider, "openai-codex", "xiaomi", ]); @@ -67,8 +68,8 @@ describe("provider-usage.load", () => { summary.providers.find((provider) => provider.provider === "github-copilot")?.windows, ).toEqual([{ label: "Chat", usedPercent: 20 }]); expect( - summary.providers.find((provider) => provider.provider === "google-gemini-cli")?.windows[0] - ?.label, + summary.providers.find((provider) => provider.provider === googleGeminiCliProvider) + ?.windows[0]?.label, ).toBe("Pro"); expect( summary.providers.find((provider) => provider.provider === "openai-codex")?.windows[0]?.label, diff --git a/src/plugin-activation-boundary.test.ts b/src/plugin-activation-boundary.test.ts index f317d7150ef..1cfc101103b 100644 --- a/src/plugin-activation-boundary.test.ts +++ b/src/plugin-activation-boundary.test.ts @@ -49,7 +49,6 @@ describe("plugin activation boundary", () => { let browserAmbientImportsPromise: Promise | undefined; function importAmbientModules() { ambientImportsPromise ??= Promise.all([ - import("./agents/cli-session.js"), import("./commands/onboard-custom.js"), import("./commands/opencode-go-model-default.js"), import("./commands/opencode-zen-model-default.js"), diff --git a/src/plugins/bundled-capability-metadata.test.ts b/src/plugins/bundled-capability-metadata.test.ts index 42b4df924db..e48670bf2cf 100644 --- a/src/plugins/bundled-capability-metadata.test.ts +++ b/src/plugins/bundled-capability-metadata.test.ts @@ -25,7 +25,6 @@ describe("bundled capability metadata", () => { const expected = listBundledPluginMetadata() .map(({ manifest }) => ({ pluginId: manifest.id, - cliBackendIds: uniqueStrings(manifest.cliBackends), providerIds: uniqueStrings(manifest.providers), speechProviderIds: uniqueStrings(manifest.contracts?.speechProviders), realtimeTranscriptionProviderIds: uniqueStrings( @@ -43,7 +42,6 @@ describe("bundled capability metadata", () => { })) .filter( (entry) => - entry.cliBackendIds.length > 0 || entry.providerIds.length > 0 || entry.speechProviderIds.length > 0 || entry.realtimeTranscriptionProviderIds.length > 0 || diff --git a/src/plugins/contracts/runtime-seams.contract.test.ts b/src/plugins/contracts/runtime-seams.contract.test.ts index e3d3b5e81d0..f4deca3a12c 100644 --- a/src/plugins/contracts/runtime-seams.contract.test.ts +++ b/src/plugins/contracts/runtime-seams.contract.test.ts @@ -42,7 +42,6 @@ function buildPluginManifestRecord(params: { manifestPath: path.join(params.rootDir, "openclaw.plugin.json"), channels: [params.id], providers: [], - cliBackends: [], skills: [], hooks: [], }; diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index cd7110d2f07..baf0a31cd83 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -2130,21 +2130,6 @@ module.exports = { id: "throws-after-import", register() {} };`, }); }, }, - { - label: "requires cli backend ids", - pluginId: "cli-backend-missing-id", - body: `module.exports = { id: "cli-backend-missing-id", register(api) { - api.registerCliBackend({ id: " ", config: { command: "claude" } }); -} };`, - assert: (registry: ReturnType) => { - expect(registry.cliBackends).toHaveLength(0); - expectRegistryErrorDiagnostic({ - registry, - pluginId: "cli-backend-missing-id", - message: "cli backend registration missing id", - }); - }, - }, ] as const; runSinglePluginRegistryScenarios(scenarios); @@ -2239,22 +2224,6 @@ module.exports = { id: "throws-after-import", register() {} };`, }, assert: expectDuplicateRegistrationResult, }, - { - label: "plugin cli backend ids", - ownerA: "cli-backend-owner-a", - ownerB: "cli-backend-owner-b", - buildBody: (ownerId: string) => `module.exports = { id: "${ownerId}", register(api) { - api.registerCliBackend({ id: "shared-cli-backend", config: { command: "backend-${ownerId}" } }); -} };`, - selectCount: (registry: ReturnType) => - registry.cliBackends?.length ?? 0, - duplicateMessage: - "cli backend already registered: shared-cli-backend (cli-backend-owner-a)", - assertPrimaryOwner: (registry: ReturnType) => { - expect(registry.cliBackends?.[0]?.pluginId).toBe("cli-backend-owner-a"); - }, - assert: expectDuplicateRegistrationResult, - }, ] as const; runRegistryScenarios(scenarios, (scenario) => { diff --git a/src/plugins/manifest-registry.test.ts b/src/plugins/manifest-registry.test.ts index aec328a4cda..9dd911f4a66 100644 --- a/src/plugins/manifest-registry.test.ts +++ b/src/plugins/manifest-registry.test.ts @@ -379,7 +379,6 @@ describe("loadPluginManifestRegistry", () => { id: "openai", enabledByDefault: true, providers: ["openai", "openai-codex"], - cliBackends: ["codex-cli"], providerAuthEnvVars: { openai: ["OPENAI_API_KEY"], }, @@ -405,7 +404,6 @@ describe("loadPluginManifestRegistry", () => { expect(registry.plugins[0]?.providerAuthEnvVars).toEqual({ openai: ["OPENAI_API_KEY"], }); - expect(registry.plugins[0]?.cliBackends).toEqual(["codex-cli"]); expect(registry.plugins[0]?.enabledByDefault).toBe(true); expect(registry.plugins[0]?.providerAuthChoices).toEqual([ { diff --git a/src/plugins/providers.test.ts b/src/plugins/providers.test.ts index 420aeafcce1..75e2c32761c 100644 --- a/src/plugins/providers.test.ts +++ b/src/plugins/providers.test.ts @@ -26,7 +26,6 @@ let setActivePluginRegistry: SetActivePluginRegistry; function createManifestProviderPlugin(params: { id: string; providerIds: string[]; - cliBackends?: string[]; origin?: "bundled" | "workspace"; enabledByDefault?: boolean; modelSupport?: { modelPrefixes?: string[]; modelPatterns?: string[] }; @@ -35,7 +34,6 @@ function createManifestProviderPlugin(params: { id: params.id, enabledByDefault: params.enabledByDefault, channels: [], - cliBackends: params.cliBackends ?? [], providers: params.providerIds, modelSupport: params.modelSupport, skills: [], @@ -63,7 +61,6 @@ function setOwningProviderManifestPlugins() { createManifestProviderPlugin({ id: "openai", providerIds: ["openai", "openai-codex"], - cliBackends: ["codex-cli"], modelSupport: { modelPrefixes: ["gpt-", "o1", "o3", "o4"], }, @@ -87,7 +84,6 @@ function setOwningProviderManifestPluginsWithWorkspace() { createManifestProviderPlugin({ id: "openai", providerIds: ["openai", "openai-codex"], - cliBackends: ["codex-cli"], modelSupport: { modelPrefixes: ["gpt-", "o1", "o3", "o4"], }, diff --git a/src/plugins/runtime.test.ts b/src/plugins/runtime.test.ts index 442b9c03da8..2ee8e2a2d82 100644 --- a/src/plugins/runtime.test.ts +++ b/src/plugins/runtime.test.ts @@ -196,7 +196,6 @@ describe("setActivePluginRegistry", () => { toolNames: [], hookNames: [], channelIds: [], - cliBackendIds: [], providerIds: [], speechProviderIds: [], realtimeTranscriptionProviderIds: [], @@ -226,7 +225,6 @@ describe("setActivePluginRegistry", () => { toolNames: [], hookNames: [], channelIds: [], - cliBackendIds: [], providerIds: [], speechProviderIds: [], realtimeTranscriptionProviderIds: [], diff --git a/src/plugins/status.test-helpers.ts b/src/plugins/status.test-helpers.ts index 2f7fb5b4636..8cf4cedb480 100644 --- a/src/plugins/status.test-helpers.ts +++ b/src/plugins/status.test-helpers.ts @@ -48,7 +48,6 @@ export function createPluginRecord( toolNames: [], hookNames: [], channelIds: [], - cliBackendIds: [], providerIds: [], speechProviderIds: [], realtimeTranscriptionProviderIds: [], diff --git a/src/plugins/status.test.ts b/src/plugins/status.test.ts index 083023a2a99..02c248b3ae2 100644 --- a/src/plugins/status.test.ts +++ b/src/plugins/status.test.ts @@ -581,7 +581,6 @@ describe("plugin status reports", () => { name: "Google", description: "Google provider plugin", origin: "bundled", - cliBackendIds: ["google-gemini-cli"], providerIds: ["google"], mediaUnderstandingProviderIds: ["google"], imageGenerationProviderIds: ["google"], @@ -598,13 +597,7 @@ describe("plugin status reports", () => { expectInspectShape(inspect!, { shape: "hybrid-capability", capabilityMode: "hybrid", - capabilityKinds: [ - "cli-backend", - "text-inference", - "media-understanding", - "image-generation", - "web-search", - ], + capabilityKinds: ["text-inference", "media-understanding", "image-generation", "web-search"], }); expect(inspect?.usesLegacyBeforeAgentStart).toBe(true); expect(inspect?.compatibility).toEqual([ @@ -651,23 +644,23 @@ describe("plugin status reports", () => { expectCapabilityKinds(inspect[1], ["text-inference", "web-search"]); }); - it("treats a CLI-backend-only plugin as a plain capability", () => { + it("treats a CLI-command-only plugin as a non-capability", () => { setSinglePluginLoadResult( createPluginRecord({ id: "openai", name: "OpenAI", - cliBackendIds: ["codex-cli"], + cliCommands: ["openai"], }), ); const inspect = expectInspectReport("openai"); expectInspectShape(inspect, { - shape: "plain-capability", - capabilityMode: "plain", - capabilityKinds: ["cli-backend"], + shape: "non-capability", + capabilityMode: "none", + capabilityKinds: [], }); - expect(inspect.capabilities).toEqual([{ kind: "cli-backend", ids: ["codex-cli"] }]); + expect(inspect.capabilities).toEqual([]); }); it("builds compatibility warnings for legacy compatibility paths", () => { diff --git a/test/helpers/plugins/plugin-api.ts b/test/helpers/plugins/plugin-api.ts index ae2abccc62c..19f38a16396 100644 --- a/test/helpers/plugins/plugin-api.ts +++ b/test/helpers/plugins/plugin-api.ts @@ -17,7 +17,6 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi registerGatewayMethod() {}, registerCli() {}, registerService() {}, - registerCliBackend() {}, registerConfigMigration() {}, registerAutoEnableProbe() {}, registerProvider() {}, diff --git a/test/helpers/plugins/plugin-registration-contract-cases.ts b/test/helpers/plugins/plugin-registration-contract-cases.ts index 9b149a5ee81..ee001ccd74c 100644 --- a/test/helpers/plugins/plugin-registration-contract-cases.ts +++ b/test/helpers/plugins/plugin-registration-contract-cases.ts @@ -47,7 +47,6 @@ export const pluginRegistrationContractCases = { webSearchProviderIds: ["gemini"], mediaUnderstandingProviderIds: ["google"], imageGenerationProviderIds: ["google"], - cliBackendIds: ["google-gemini-cli"], requireDescribeImages: true, requireGenerateImage: true, }, @@ -95,7 +94,6 @@ export const pluginRegistrationContractCases = { realtimeVoiceProviderIds: ["openai"], mediaUnderstandingProviderIds: ["openai", "openai-codex"], imageGenerationProviderIds: ["openai"], - cliBackendIds: ["codex-cli"], requireSpeechVoices: true, requireDescribeImages: true, requireGenerateImage: true, diff --git a/test/helpers/plugins/plugin-registration-contract.ts b/test/helpers/plugins/plugin-registration-contract.ts index d185f264fe6..35f27183c5d 100644 --- a/test/helpers/plugins/plugin-registration-contract.ts +++ b/test/helpers/plugins/plugin-registration-contract.ts @@ -19,7 +19,6 @@ type PluginRegistrationContractParams = { mediaUnderstandingProviderIds?: string[]; imageGenerationProviderIds?: string[]; videoGenerationProviderIds?: string[]; - cliBackendIds?: string[]; toolNames?: string[]; requireSpeechVoices?: boolean; requireDescribeImages?: boolean; @@ -193,12 +192,6 @@ export function describePluginRegistrationContract(params: PluginRegistrationCon }); } - if (params.cliBackendIds) { - it("keeps bundled CLI backend ownership explicit", () => { - expect(findRegistration(params.pluginId).cliBackendIds).toEqual(params.cliBackendIds); - }); - } - if (params.toolNames) { it("keeps bundled tool ownership explicit", () => { expect(findRegistration(params.pluginId).toolNames).toEqual(params.toolNames);