fix: restore check after CLI seam cleanup

This commit is contained in:
Peter Steinberger
2026-04-05 19:04:35 +01:00
parent a01c4c3a0e
commit 1a47675e6c
39 changed files with 90 additions and 601 deletions

View File

@@ -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 () => {

View File

@@ -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",

View File

@@ -44,7 +44,6 @@ function fakeApi(overrides: Partial<OpenClawPluginApi> = {}): OpenClawPluginApi
registerGatewayMethod() {},
registerCli() {},
registerService() {},
registerCliBackend() {},
registerConfigMigration() {},
registerAutoEnableProbe() {},
registerProvider() {},

View File

@@ -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);

View File

@@ -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");
});
});

View File

@@ -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);
});
});

View File

@@ -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",

View File

@@ -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();
});

View File

@@ -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: () => [],
}));

View File

@@ -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";

View File

@@ -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<typeof import("../../agents/cli-runner.js")>(
"../../agents/cli-runner.js",
);
return {
...actual,
runCliAgent: (params: unknown) => runCliAgentMock(params),
};
});
vi.mock("../../runtime.js", async () => {
const actual = await vi.importActual<typeof import("../../runtime.js")>("../../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" });
});

View File

@@ -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();
});
});

View File

@@ -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 () => {

View File

@@ -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<T>(fn: (home: string) => Promise<T>): Promise<T> {
return withTempHomeBase(fn, { prefix: "openclaw-agent-cli-" });
}
function mockConfig(
home: string,
storePath: string,
agentOverrides?: Partial<NonNullable<NonNullable<OpenClawConfig["agents"]>["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<string, Record<string, unknown>>,
) {
fs.mkdirSync(path.dirname(storePath), { recursive: true });
fs.writeFileSync(storePath, JSON.stringify(sessions, null, 2));
}
function readSessionStore<T>(storePath: string): Record<string, T> {
return JSON.parse(fs.readFileSync(storePath, "utf-8")) as Record<string, T>;
}
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<ReturnType<typeof configModule.readConfigFileSnapshotForWrite>>);
});
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<string, string>;
}>(store);
expect(saved[sessionKey]?.cliSessionIds?.["codex-cli"]).toBeUndefined();
});
} finally {
vi.mocked(modelSelectionModule.isCliProvider).mockImplementation(() => false);
}
});
});

View File

@@ -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<T>(fn: (home: string) => Promise<T>): Promise<T> {
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);

View File

@@ -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");
});
});

View File

@@ -10,7 +10,6 @@ function manifest(id: string): PluginManifestRecord {
id,
channels: [],
providers: [],
cliBackends: [],
skills: [],
hooks: [],
origin: "bundled",

View File

@@ -13,7 +13,6 @@ function manifest(id: string): PluginManifestRecord {
id,
channels: [],
providers: [],
cliBackends: [],
skills: [],
hooks: [],
origin: "bundled",

View File

@@ -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: {

View File

@@ -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");
});

View File

@@ -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<string, Record<string, unknown>>;
};
};
};
const existingBackends = cfgWithCliBackends.agents?.defaults?.cliBackends ?? {};
const nextCfg = {
...cfg,
agents: {

View File

@@ -146,16 +146,6 @@ function mockMainSessionEntry(entry: Record<string, unknown>, cfg: Record<string
});
}
function captureUpdatedMainEntry() {
let capturedEntry: Record<string, unknown> | undefined;
mocks.updateSessionStore.mockImplementation(async (_path, updater) => {
const store: Record<string, unknown> = {};
await updater(store);
capturedEntry = store["agent:main:main"] as Record<string, unknown>;
});
return () => capturedEntry;
}
function buildExistingMainStoreEntry(overrides: Record<string, unknown> = {}) {
return {
sessionId: "existing-session-id",
@@ -164,17 +154,6 @@ function buildExistingMainStoreEntry(overrides: Record<string, unknown> = {}) {
};
}
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: {

View File

@@ -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,
});
}

View File

@@ -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<string, string>;
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<string, string>;
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",

View File

@@ -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 }],
});
});

View File

@@ -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 },

View File

@@ -9,6 +9,7 @@ import {
} from "./provider-usage.test-support.js";
type ProviderAuth = ProviderUsageAuth<typeof loadProviderUsageSummary>;
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,

View File

@@ -49,7 +49,6 @@ describe("plugin activation boundary", () => {
let browserAmbientImportsPromise: Promise<void> | 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"),

View File

@@ -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 ||

View File

@@ -42,7 +42,6 @@ function buildPluginManifestRecord(params: {
manifestPath: path.join(params.rootDir, "openclaw.plugin.json"),
channels: [params.id],
providers: [],
cliBackends: [],
skills: [],
hooks: [],
};

View File

@@ -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<typeof loadOpenClawPlugins>) => {
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<typeof loadOpenClawPlugins>) =>
registry.cliBackends?.length ?? 0,
duplicateMessage:
"cli backend already registered: shared-cli-backend (cli-backend-owner-a)",
assertPrimaryOwner: (registry: ReturnType<typeof loadOpenClawPlugins>) => {
expect(registry.cliBackends?.[0]?.pluginId).toBe("cli-backend-owner-a");
},
assert: expectDuplicateRegistrationResult,
},
] as const;
runRegistryScenarios(scenarios, (scenario) => {

View File

@@ -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([
{

View File

@@ -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"],
},

View File

@@ -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: [],

View File

@@ -48,7 +48,6 @@ export function createPluginRecord(
toolNames: [],
hookNames: [],
channelIds: [],
cliBackendIds: [],
providerIds: [],
speechProviderIds: [],
realtimeTranscriptionProviderIds: [],

View File

@@ -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", () => {

View File

@@ -17,7 +17,6 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi
registerGatewayMethod() {},
registerCli() {},
registerService() {},
registerCliBackend() {},
registerConfigMigration() {},
registerAutoEnableProbe() {},
registerProvider() {},

View File

@@ -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,

View File

@@ -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);