// Codex tests cover plugin activation plugin behavior. import { describe, expect, it, vi } from "vitest"; import { CodexAppInventoryCache } from "./app-inventory-cache.js"; import { CODEX_PLUGINS_MARKETPLACE_NAME, CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, type ResolvedCodexPluginPolicy, } from "./config.js"; import { ensureCodexPluginActivation } from "./plugin-activation.js"; import { CodexPluginMetadataCache } from "./plugin-metadata-cache.js"; import type { v2 } from "./protocol.js"; describe("Codex plugin activation", () => { function expectActivationResult( result: Awaited>, expected: { ok: boolean; reason: string; installAttempted: boolean }, ) { expect(result.ok).toBe(expected.ok); expect(result.reason).toBe(expected.reason); expect(result.installAttempted).toBe(expected.installAttempted); } function expectBooleanParam(params: unknown, key: string, expected: boolean) { expect((params as Record | undefined)?.[key]).toBe(expected); } it("skips plugin/install when the migrated plugin is already active", async () => { const calls: string[] = []; const result = await ensureCodexPluginActivation({ identity: identity("google-calendar"), request: async (method) => { calls.push(method); if (method === "plugin/list") { return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]); } throw new Error(`unexpected request ${method}`); }, }); expectActivationResult(result, { ok: true, reason: "already_active", installAttempted: false, }); expect(calls).toEqual(["plugin/list"]); }); it("can reinstall an already active plugin when migration explicitly applies it", async () => { const calls: string[] = []; const result = await ensureCodexPluginActivation({ identity: identity("google-calendar"), installEvenIfActive: true, request: async (method, params) => { calls.push(method); if (method === "plugin/list") { return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]); } if (method === "plugin/install") { expect(params).toEqual({ marketplacePath: "/marketplaces/openai-curated", pluginName: "google-calendar", }); return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse; } if (method === "skills/list") { return { data: [] } satisfies v2.SkillsListResponse; } if (method === "hooks/list") { return { data: [] } satisfies v2.HooksListResponse; } if (method === "config/mcpServer/reload") { return {}; } throw new Error(`unexpected request ${method}`); }, }); expectActivationResult(result, { ok: true, reason: "already_active", installAttempted: true, }); expect(calls).toEqual([ "plugin/list", "plugin/install", "plugin/list", "skills/list", "hooks/list", "config/mcpServer/reload", ]); }); it("installs a migration-authorized local curated plugin and refreshes runtime state", async () => { const calls: Array<{ method: string; params: unknown }> = []; const appCache = new CodexAppInventoryCache(); const metadataCache = new CodexPluginMetadataCache(); let pluginListCalls = 0; const result = await ensureCodexPluginActivation({ identity: identity("google-calendar"), appCache, appCacheKey: "runtime", metadataCache, request: async (method, params) => { calls.push({ method, params }); if (method === "plugin/list") { pluginListCalls += 1; expect(params).toEqual({}); return pluginList([ pluginSummary("google-calendar", { installed: pluginListCalls > 1, enabled: pluginListCalls > 1, }), ]); } if (method === "plugin/install") { expect(params).toEqual({ marketplacePath: "/marketplaces/openai-curated", pluginName: "google-calendar", }); return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse; } if (method === "skills/list") { expectBooleanParam(params, "forceReload", true); return { data: [] } satisfies v2.SkillsListResponse; } if (method === "hooks/list") { return { data: [] } satisfies v2.HooksListResponse; } if (method === "config/mcpServer/reload") { return {}; } if (method === "app/list") { expectBooleanParam(params, "forceRefetch", true); return { data: [], nextCursor: null } satisfies v2.AppsListResponse; } throw new Error(`unexpected request ${method}`); }, }); expectActivationResult(result, { ok: true, reason: "installed", installAttempted: true, }); expect(calls.map((call) => call.method)).toEqual([ "plugin/list", "plugin/install", "plugin/list", "skills/list", "hooks/list", "config/mcpServer/reload", "app/list", ]); expect(pluginListCalls).toBe(2); expect( metadataCache.read("runtime", "curated-global")?.response.marketplaces[0]?.plugins[0], ).toMatchObject({ installed: true, enabled: true }); expect(appCache.getRevision()).toBeGreaterThan(0); }); it("keeps activation fail-closed when post-install app inventory refresh fails", async () => { const appCache = new CodexAppInventoryCache(); const result = await ensureCodexPluginActivation({ identity: identity("google-calendar"), appCache, appCacheKey: "runtime", request: async (method) => { if (method === "plugin/list") { return pluginList([ pluginSummary("google-calendar", { installed: false, enabled: false }), ]); } if (method === "plugin/install") { return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse; } if (method === "skills/list") { return { data: [] } satisfies v2.SkillsListResponse; } if (method === "hooks/list") { return { data: [] } satisfies v2.HooksListResponse; } if (method === "config/mcpServer/reload") { return {}; } if (method === "app/list") { throw new Error("app/list unavailable"); } throw new Error(`unexpected request ${method}`); }, }); expectActivationResult(result, { ok: true, reason: "installed", installAttempted: true, }); expect(result.diagnostics).toEqual([ { message: "Codex app inventory refresh skipped: app/list unavailable", }, ]); expect(appCache.getRevision()).toBeGreaterThan(0); }); it("reports post-install runtime refresh failures without hiding the install attempt", async () => { const result = await ensureCodexPluginActivation({ identity: identity("google-calendar"), request: async (method) => { if (method === "plugin/list") { return pluginList([ pluginSummary("google-calendar", { installed: false, enabled: false }), ]); } if (method === "plugin/install") { return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse; } if (method === "skills/list") { throw new Error("skills/list unavailable"); } throw new Error(`unexpected request ${method}`); }, }); expectActivationResult(result, { ok: false, reason: "refresh_failed", installAttempted: true, }); expect(result.diagnostics).toEqual([ { message: "Codex plugin runtime refresh failed after install: skills/list unavailable", }, ]); }); it("installs a disabled remote curated plugin by its resolved remote id", async () => { const calls: Array<{ method: string; params: unknown }> = []; const remoteSummary = pluginSummary("google-calendar@openai-curated-remote", { name: "google-calendar", remotePluginId: "plugin_connector_google_calendar", installed: false, enabled: false, }); const result = await ensureCodexPluginActivation({ identity: identity("google-calendar"), request: async (method, params) => { calls.push({ method, params }); if (method === "plugin/list") { return { ...pluginList([remoteSummary]), marketplaces: [ { name: CODEX_PLUGINS_MARKETPLACE_NAME, path: "/marketplaces/openai-curated", interface: null, plugins: [pluginSummary("github")], }, { name: "openai-curated-remote", path: null, interface: null, plugins: [remoteSummary], }, ], } satisfies v2.PluginListResponse; } if (method === "plugin/install") { expect(params).toEqual({ remoteMarketplaceName: "openai-curated-remote", pluginName: "plugin_connector_google_calendar", }); return { authPolicy: "ON_USE", appsNeedingAuth: [] } satisfies v2.PluginInstallResponse; } if (method === "skills/list") { return { data: [] } satisfies v2.SkillsListResponse; } if (method === "hooks/list") { return { data: [] } satisfies v2.HooksListResponse; } if (method === "config/mcpServer/reload") { return {}; } throw new Error(`unexpected request ${method}`); }, }); expectActivationResult(result, { ok: true, reason: "installed", installAttempted: true, }); expect(calls.map((call) => call.method)).toEqual([ "plugin/list", "plugin/install", "plugin/list", "skills/list", "hooks/list", "config/mcpServer/reload", ]); }); it("settles a missing plugin from the remote curated marketplace snapshot", async () => { const metadataCache = new CodexPluginMetadataCache(); const request = vi.fn(async (_method: string, params: unknown) => { expect(params).toEqual({}); return { marketplaces: [ { name: "openai-curated-remote", path: null, interface: null, plugins: [], }, ], marketplaceLoadErrors: [], featuredPluginIds: [], } satisfies v2.PluginListResponse; }); const activationParams = { identity: identity("google-calendar"), request, metadataCache, appCacheKey: "runtime", }; const first = await ensureCodexPluginActivation(activationParams); const second = await ensureCodexPluginActivation(activationParams); expect(first.reason).toBe("plugin_missing"); expect(second.reason).toBe("plugin_missing"); expect(request).toHaveBeenCalledTimes(1); }); it("requires workspace-directory plugins to be activated outside OpenClaw", async () => { const request = vi.fn(async () => { throw new Error("workspace activation must not call app-server"); }); const result = await ensureCodexPluginActivation({ identity: { ...identity("workspace-data@workspace-directory"), marketplaceName: CODEX_PLUGINS_WORKSPACE_MARKETPLACE_NAME, }, request, }); expectActivationResult(result, { ok: false, reason: "disabled", installAttempted: false, }); expect(result.diagnostics[0]?.message).toContain("installed and enabled outside OpenClaw"); expect(request).not.toHaveBeenCalled(); }); }); function identity(pluginName: string): ResolvedCodexPluginPolicy { return { configKey: pluginName, marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME, pluginName, enabled: true, allowDestructiveActions: false, destructiveApprovalMode: "deny", }; } function pluginList(plugins: v2.PluginSummary[]): v2.PluginListResponse { return { marketplaces: [ { name: CODEX_PLUGINS_MARKETPLACE_NAME, path: "/marketplaces/openai-curated", interface: null, plugins, }, ], marketplaceLoadErrors: [], featuredPluginIds: [], }; } function pluginSummary(id: string, overrides: Partial = {}): v2.PluginSummary { return { id, name: id, source: { type: "remote" }, installed: false, enabled: false, installPolicy: "AVAILABLE", authPolicy: "ON_USE", availability: "AVAILABLE", interface: null, ...overrides, }; }