mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-12 09:41:11 +00:00
fix: restore check after CLI seam cleanup
This commit is contained in:
@@ -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 () => {
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -44,7 +44,6 @@ function fakeApi(overrides: Partial<OpenClawPluginApi> = {}): OpenClawPluginApi
|
||||
registerGatewayMethod() {},
|
||||
registerCli() {},
|
||||
registerService() {},
|
||||
registerCliBackend() {},
|
||||
registerConfigMigration() {},
|
||||
registerAutoEnableProbe() {},
|
||||
registerProvider() {},
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
@@ -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: () => [],
|
||||
}));
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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" });
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,6 @@ function manifest(id: string): PluginManifestRecord {
|
||||
id,
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: "bundled",
|
||||
|
||||
@@ -13,7 +13,6 @@ function manifest(id: string): PluginManifestRecord {
|
||||
id,
|
||||
channels: [],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
origin: "bundled",
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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");
|
||||
});
|
||||
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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 }],
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -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 },
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"),
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -42,7 +42,6 @@ function buildPluginManifestRecord(params: {
|
||||
manifestPath: path.join(params.rootDir, "openclaw.plugin.json"),
|
||||
channels: [params.id],
|
||||
providers: [],
|
||||
cliBackends: [],
|
||||
skills: [],
|
||||
hooks: [],
|
||||
};
|
||||
|
||||
@@ -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) => {
|
||||
|
||||
@@ -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([
|
||||
{
|
||||
|
||||
@@ -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"],
|
||||
},
|
||||
|
||||
@@ -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: [],
|
||||
|
||||
@@ -48,7 +48,6 @@ export function createPluginRecord(
|
||||
toolNames: [],
|
||||
hookNames: [],
|
||||
channelIds: [],
|
||||
cliBackendIds: [],
|
||||
providerIds: [],
|
||||
speechProviderIds: [],
|
||||
realtimeTranscriptionProviderIds: [],
|
||||
|
||||
@@ -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", () => {
|
||||
|
||||
@@ -17,7 +17,6 @@ export function createTestPluginApi(api: TestPluginApiInput): OpenClawPluginApi
|
||||
registerGatewayMethod() {},
|
||||
registerCli() {},
|
||||
registerService() {},
|
||||
registerCliBackend() {},
|
||||
registerConfigMigration() {},
|
||||
registerAutoEnableProbe() {},
|
||||
registerProvider() {},
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user