From fce002ad03200e0e17abf79232942217bb016b49 Mon Sep 17 00:00:00 2001 From: Kevin Lin Date: Wed, 3 Jun 2026 15:35:35 -0700 Subject: [PATCH] revert(codex): revert first-party marketplace allowlist Reverts openclaw/openclaw#82219. --- docs/cli/migrate.md | 11 --- docs/gateway/configuration-reference.md | 5 +- docs/plugins/codex-harness-reference.md | 16 ++-- docs/plugins/codex-harness.md | 2 +- docs/plugins/codex-native-plugins.md | 71 +++------------- extensions/codex/openclaw.plugin.json | 2 +- .../codex/src/app-server/config.test.ts | 53 ------------ extensions/codex/src/app-server/config.ts | 33 ++------ .../src/app-server/plugin-activation.test.ts | 78 +----------------- .../codex/src/app-server/plugin-activation.ts | 27 +++---- .../codex/src/app-server/plugin-inventory.ts | 81 +++++++------------ .../src/app-server/session-binding.test.ts | 30 ------- .../codex/src/app-server/session-binding.ts | 5 +- extensions/codex/src/migration/apply.ts | 19 ++--- .../codex/src/migration/provider.test.ts | 11 +-- extensions/sms/src/channel.test.ts | 35 +------- extensions/sms/src/channel.ts | 57 +++---------- extensions/sms/src/inbound.test.ts | 15 ++-- extensions/sms/src/twilio.test.ts | 11 +-- src/agents/tools/skill-workshop-tool.ts | 3 - src/config/sessions.cache.test.ts | 4 +- ...erver.agent.gateway-server-agent-b.test.ts | 3 +- 22 files changed, 101 insertions(+), 471 deletions(-) diff --git a/docs/cli/migrate.md b/docs/cli/migrate.md index 23039edfff86..d80539d972f7 100644 --- a/docs/cli/migrate.md +++ b/docs/cli/migrate.md @@ -218,17 +218,6 @@ Target-side auth-required installs are reported on the affected plugin item with Their explicit config entries are written disabled until you reauthorize and enable them. Other install failures are item-scoped `error` results. -The native Codex plugin config also accepts first-party `openai-bundled` and -`openai-primary-runtime` marketplace identities, but migration does not -auto-discover or install them from source state. - -OpenAI-side app/plugin availability still comes from the signed-in Codex -account and workspace app controls. See -[Using Codex with your ChatGPT plan](https://help.openai.com/en/articles/11369540-using-codex-with-your-chatgpt-plan) -for OpenAI's account and workspace-control overview, then use -[Native Codex plugins](/plugins/codex-native-plugins#manual-first-party-marketplace-entries) -for manual first-party marketplace entries. - If Codex app-server plugin inventory is unavailable during planning, migration falls back to cached bundle advisory items instead of failing the whole migration. diff --git a/docs/gateway/configuration-reference.md b/docs/gateway/configuration-reference.md index d219b7160e4a..cb3c14404a4c 100644 --- a/docs/gateway/configuration-reference.md +++ b/docs/gateway/configuration-reference.md @@ -316,10 +316,7 @@ conversation bindings, or any non-Codex harness. migrated plugin entry when global `codexPlugins.enabled` is also true. Default: `true` for explicit entries. - `plugins.entries.codex.config.codexPlugins.plugins..marketplaceName`: - stable marketplace identity. V1 supports `"openai-curated"`, - `"openai-bundled"`, and `"openai-primary-runtime"`. See - [Native Codex plugins](/plugins/codex-native-plugins#manual-first-party-marketplace-entries) - for manual bundled and primary-runtime examples. + stable marketplace identity. V1 only supports `"openai-curated"`. - `plugins.entries.codex.config.codexPlugins.plugins..pluginName`: stable Codex plugin identity from migration, for example `"google-calendar"`. - `plugins.entries.codex.config.codexPlugins.plugins..allow_destructive_actions`: diff --git a/docs/plugins/codex-harness-reference.md b/docs/plugins/codex-harness-reference.md index 89b0ca8bbfa7..db7025a63d48 100644 --- a/docs/plugins/codex-harness-reference.md +++ b/docs/plugins/codex-harness-reference.md @@ -38,14 +38,14 @@ All Codex harness settings live under `plugins.entries.codex.config`. Supported top-level fields: -| Field | Default | Meaning | -| -------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------------------------------ | -| `discovery` | enabled | Model discovery settings for Codex app-server `model/list`. | -| `appServer` | managed stdio app-server | Transport, command, auth, approval, sandbox, and timeout settings. | -| `codexDynamicToolsLoading` | `"searchable"` | Use `"direct"` to put OpenClaw dynamic tools directly in the initial Codex tool context. | -| `codexDynamicToolsExclude` | `[]` | Additional OpenClaw dynamic tool names to omit from Codex app-server turns. | -| `codexPlugins` | disabled | Native Codex plugin/app support for configured first-party Codex plugins. See [Native Codex plugins](/plugins/codex-native-plugins). | -| `computerUse` | disabled | Codex Computer Use setup. See [Codex Computer Use](/plugins/codex-computer-use). | +| Field | Default | Meaning | +| -------------------------- | ------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------- | +| `discovery` | enabled | Model discovery settings for Codex app-server `model/list`. | +| `appServer` | managed stdio app-server | Transport, command, auth, approval, sandbox, and timeout settings. | +| `codexDynamicToolsLoading` | `"searchable"` | Use `"direct"` to put OpenClaw dynamic tools directly in the initial Codex tool context. | +| `codexDynamicToolsExclude` | `[]` | Additional OpenClaw dynamic tool names to omit from Codex app-server turns. | +| `codexPlugins` | disabled | Native Codex plugin/app support for migrated source-installed curated plugins. See [Native Codex plugins](/plugins/codex-native-plugins). | +| `computerUse` | disabled | Codex Computer Use setup. See [Codex Computer Use](/plugins/codex-computer-use). | ## App-server transport diff --git a/docs/plugins/codex-harness.md b/docs/plugins/codex-harness.md index 180922660499..1e88ba5dc026 100644 --- a/docs/plugins/codex-harness.md +++ b/docs/plugins/codex-harness.md @@ -526,7 +526,7 @@ Supported top-level Codex plugin fields: | -------------------------- | -------------- | ---------------------------------------------------------------------------------------- | | `codexDynamicToolsLoading` | `"searchable"` | Use `"direct"` to put OpenClaw dynamic tools directly in the initial Codex tool context. | | `codexDynamicToolsExclude` | `[]` | Additional OpenClaw dynamic tool names to omit from Codex app-server turns. | -| `codexPlugins` | disabled | Native Codex plugin/app support for configured first-party Codex plugins. | +| `codexPlugins` | disabled | Native Codex plugin/app support for migrated source-installed curated plugins. | Supported `appServer` fields: diff --git a/docs/plugins/codex-native-plugins.md b/docs/plugins/codex-native-plugins.md index 60b95696d3e7..cdaf45874983 100644 --- a/docs/plugins/codex-native-plugins.md +++ b/docs/plugins/codex-native-plugins.md @@ -3,7 +3,7 @@ summary: "Configure migrated native Codex plugins for Codex-mode OpenClaw agents title: "Native Codex plugins" read_when: - You want Codex-mode OpenClaw agents to use native Codex plugins - - You are configuring first-party Codex plugin marketplaces + - You are migrating source-installed openai-curated Codex plugins - You are troubleshooting codexPlugins, app inventory, destructive actions, or plugin app diagnostics --- @@ -22,9 +22,7 @@ Use this page after the base [Codex harness](/plugins/codex-harness) is working. - The selected OpenClaw agent runtime must be the native Codex harness. - `plugins.entries.codex.enabled` must be true. - `plugins.entries.codex.config.codexPlugins.enabled` must be true. -- V1 supports first-party Codex plugin marketplaces: `openai-curated`, - `openai-bundled`, and `openai-primary-runtime`. -- Migration only auto-discovers `openai-curated` plugins that it observed as +- V1 supports only `openai-curated` plugins that migration observed as source-installed in the source Codex home. - The target Codex app-server must be able to see the expected marketplace, plugin, and app inventory. @@ -58,11 +56,9 @@ Apply the migration when the plan looks right: openclaw migrate apply codex --yes ``` -Migration writes explicit `codexPlugins` entries for eligible curated plugins -and calls Codex app-server `plugin/install` for selected plugins. Explicit -config may also reference Codex's bundled and primary-runtime first-party -marketplaces when the target app-server inventory exposes those plugin apps. A -typical migrated config looks like this: +Migration writes explicit `codexPlugins` entries for eligible plugins and calls +Codex app-server `plugin/install` for selected plugins. A typical migrated +config looks like this: ```json5 { @@ -93,49 +89,6 @@ After changing `codexPlugins`, new Codex conversations pick up the updated app set automatically. Use `/new` or `/reset` to refresh the current conversation. A gateway restart is not required for plugin enable or disable changes. -## Manual first-party marketplace entries - -Migration writes `openai-curated` entries for eligible source-installed plugins. -For first-party plugins that live in Codex's bundled or primary-runtime -marketplaces, add explicit entries after confirming the target Codex app-server -inventory exposes that marketplace and plugin. - -Use the same config shape for every first-party marketplace: - -```json5 -{ - plugins: { - entries: { - codex: { - enabled: true, - config: { - codexPlugins: { - enabled: true, - plugins: { - chrome: { - enabled: true, - marketplaceName: "openai-bundled", - pluginName: "chrome", - }, - documents: { - enabled: true, - marketplaceName: "openai-primary-runtime", - pluginName: "documents", - }, - }, - }, - }, - }, - }, - }, -} -``` - -The key under `plugins` is OpenClaw's local config key. `pluginName` and -`marketplaceName` must match the Codex app-server inventory exactly. If the -plugin is not listed in `/codex plugins list` or Codex app diagnostics, OpenClaw -keeps the entry configured but cannot expose its apps to Codex turns. - ## Manage plugins from chat Use `/codex plugins` when you want to inspect or change configured native Codex @@ -197,10 +150,8 @@ up the updated app set. V1 is intentionally narrow: -- Runtime config accepts `openai-curated`, `openai-bundled`, and - `openai-primary-runtime` plugin identities. - Only `openai-curated` plugins that were already installed in the source Codex - app-server inventory are migration-eligible for automatic migration. + app-server inventory are migration-eligible. - App-backed source plugins must pass the migration-time subscription gate. `--verify-plugin-apps` adds the source app-inventory gate. Subscription-gated accounts plus, in verification mode, inaccessible, disabled, missing source @@ -213,9 +164,7 @@ V1 is intentionally narrow: - There is no `plugins["*"]` wildcard and no config key that grants arbitrary install authority. - Unsupported marketplaces, cached plugin bundles, hooks, and Codex config files - are preserved in the migration report for manual review. Bundled and - primary-runtime first-party plugins can still be added manually through - explicit `codexPlugins` config. + are preserved in the migration report for manual review. ## App inventory and ownership @@ -303,10 +252,8 @@ app-server auth or rerun with `--verify-plugin-apps` if you want source app inventory to decide eligibility when account lookup fails. **`marketplace_missing` or `plugin_missing`:** the target Codex app-server -cannot see the expected first-party marketplace or plugin. Rerun migration -against the target runtime, inspect Codex app-server plugin status, or confirm -the explicit `marketplaceName` is one of `openai-curated`, `openai-bundled`, or -`openai-primary-runtime`. +cannot see the expected `openai-curated` marketplace or plugin. Rerun migration +against the target runtime or inspect Codex app-server plugin status. **`app_inventory_missing` or `app_inventory_stale`:** app readiness came from an empty or stale cache. OpenClaw schedules an async refresh and excludes plugin diff --git a/extensions/codex/openclaw.plugin.json b/extensions/codex/openclaw.plugin.json index ce595113bf9e..dc65964ee850 100644 --- a/extensions/codex/openclaw.plugin.json +++ b/extensions/codex/openclaw.plugin.json @@ -114,7 +114,7 @@ }, "marketplaceName": { "type": "string", - "enum": ["openai-curated", "openai-bundled", "openai-primary-runtime"] + "enum": ["openai-curated"] }, "pluginName": { "type": "string" diff --git a/extensions/codex/src/app-server/config.test.ts b/extensions/codex/src/app-server/config.test.ts index 29eda8f92ee9..ff6fceee3212 100644 --- a/extensions/codex/src/app-server/config.test.ts +++ b/extensions/codex/src/app-server/config.test.ts @@ -653,59 +653,6 @@ allowed_sandbox_modes = ["read-only", "workspace-write"] expect(resolveCodexPluginsPolicy(config).pluginPolicies).toStrictEqual([]); }); - it("accepts native plugin identities from every first-party OpenAI marketplace", () => { - // OpenAI ships first-party Codex plugins across three marketplaces: the local - // openai-bundled marketplace shipped with Codex.app (chrome, browser, computer-use, - // latex-tectonic), the remote openai-curated marketplace, and the - // openai-primary-runtime marketplace owned by the Codex primary runtime - // (documents, spreadsheets, presentations). All three should resolve. - const config = readCodexPluginConfig({ - codexPlugins: { - enabled: true, - plugins: { - chrome: { - marketplaceName: "openai-bundled", - pluginName: "chrome", - }, - "google-calendar": { - marketplaceName: "openai-curated", - pluginName: "google-calendar", - }, - documents: { - marketplaceName: "openai-primary-runtime", - pluginName: "documents", - }, - }, - }, - }); - - expect(config.codexPlugins?.enabled).toBe(true); - const policy = resolveCodexPluginsPolicy(config); - expect(policy.pluginPolicies).toEqual([ - { - configKey: "chrome", - marketplaceName: "openai-bundled", - pluginName: "chrome", - enabled: true, - allowDestructiveActions: true, - }, - { - configKey: "documents", - marketplaceName: "openai-primary-runtime", - pluginName: "documents", - enabled: true, - allowDestructiveActions: true, - }, - { - configKey: "google-calendar", - marketplaceName: "openai-curated", - pluginName: "google-calendar", - enabled: true, - allowDestructiveActions: true, - }, - ]); - }); - it("treats configured and environment commands as explicit overrides", () => { expectFields( resolveRuntimeForTest({ diff --git a/extensions/codex/src/app-server/config.ts b/extensions/codex/src/app-server/config.ts index 69ff1a94959f..06ea943c33c6 100644 --- a/extensions/codex/src/app-server/config.ts +++ b/extensions/codex/src/app-server/config.ts @@ -60,30 +60,7 @@ type CodexAppServerCommandSource = "managed" | "resolved-managed" | "config" | " export type CodexDynamicToolsLoading = "searchable" | "direct"; export type CodexPluginDestructivePolicy = boolean; -// OpenAI ships first-party Codex plugins across three marketplaces: -// - openai-curated: remote curated marketplace, fetched via `codex plugin marketplace add` -// - openai-bundled: local marketplace that ships with Codex.app and the Codex CLI -// (browser, chrome, computer-use, latex-tectonic) -// - openai-primary-runtime: marketplace owned by the Codex primary runtime -// (documents, spreadsheets, presentations) -// All three are owned by OpenAI. Allow activating plugins from any of them. -export const CODEX_PLUGINS_MARKETPLACE_NAMES = [ - "openai-curated", - "openai-bundled", - "openai-primary-runtime", -] as const; -export type CodexPluginsMarketplaceName = (typeof CODEX_PLUGINS_MARKETPLACE_NAMES)[number]; - -// Back-compat constant for callers that still reference the curated marketplace by name. -export const CODEX_PLUGINS_MARKETPLACE_NAME: CodexPluginsMarketplaceName = "openai-curated"; - -export function isCodexPluginsMarketplaceName( - name: string | undefined, -): name is CodexPluginsMarketplaceName { - return ( - name !== undefined && (CODEX_PLUGINS_MARKETPLACE_NAMES as readonly string[]).includes(name) - ); -} +export const CODEX_PLUGINS_MARKETPLACE_NAME = "openai-curated"; export type CodexComputerUseConfig = { enabled?: boolean; @@ -126,7 +103,7 @@ export type CodexAppServerExperimentalConfig = { export type ResolvedCodexPluginPolicy = { configKey: string; - marketplaceName: CodexPluginsMarketplaceName; + marketplaceName: typeof CODEX_PLUGINS_MARKETPLACE_NAME; pluginName: string; enabled: boolean; allowDestructiveActions: CodexPluginDestructivePolicy; @@ -278,7 +255,7 @@ const codexAppServerExperimentalSchema = z const codexPluginEntryConfigSchema = z .object({ enabled: z.boolean().optional(), - marketplaceName: z.enum(CODEX_PLUGINS_MARKETPLACE_NAMES).optional(), + marketplaceName: z.literal(CODEX_PLUGINS_MARKETPLACE_NAME).optional(), pluginName: z.string().trim().min(1).optional(), allow_destructive_actions: z.boolean().optional(), }) @@ -388,13 +365,13 @@ export function resolveCodexPluginsPolicy(pluginConfig?: unknown): ResolvedCodex const allowDestructiveActions = config?.allow_destructive_actions ?? true; const pluginPolicies = Object.entries(config?.plugins ?? {}) .flatMap(([configKey, entry]): ResolvedCodexPluginPolicy[] => { - if (!isCodexPluginsMarketplaceName(entry.marketplaceName) || !entry.pluginName) { + if (entry.marketplaceName !== CODEX_PLUGINS_MARKETPLACE_NAME || !entry.pluginName) { return []; } return [ { configKey, - marketplaceName: entry.marketplaceName, + marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME, pluginName: entry.pluginName, enabled: enabled && entry.enabled !== false, allowDestructiveActions: entry.allow_destructive_actions ?? allowDestructiveActions, diff --git a/extensions/codex/src/app-server/plugin-activation.test.ts b/extensions/codex/src/app-server/plugin-activation.test.ts index f05d49277f0d..14a2ed8d1c67 100644 --- a/extensions/codex/src/app-server/plugin-activation.test.ts +++ b/extensions/codex/src/app-server/plugin-activation.test.ts @@ -22,59 +22,6 @@ describe("Codex plugin activation", () => { expect((params as Record | undefined)?.[key]).toBe(expected); } - it("activates plugins from every first-party OpenAI marketplace", async () => { - // chrome ships in openai-bundled (with Codex.app), documents ships in - // openai-primary-runtime (Codex primary runtime). Both should activate the - // same way openai-curated plugins do. - for (const { plugin, marketplace } of [ - { plugin: "chrome", marketplace: "openai-bundled" as const }, - { plugin: "documents", marketplace: "openai-primary-runtime" as const }, - ]) { - const calls: string[] = []; - const result = await ensureCodexPluginActivation({ - identity: identity(plugin, marketplace), - request: async (method) => { - calls.push(method); - if (method === "plugin/list") { - return pluginListFor(marketplace, [ - pluginSummary(plugin, { installed: true, enabled: true }), - ]); - } - throw new Error(`unexpected request ${method}`); - }, - }); - - expectActivationResult(result, { - ok: true, - reason: "already_active", - installAttempted: false, - }); - expect(result.marketplace?.name).toBe(marketplace); - expect(calls).toEqual(["plugin/list"]); - } - }); - - it("rejects activation requests for marketplaces outside the openai allowlist", async () => { - const result = await ensureCodexPluginActivation({ - identity: { - configKey: "rogue", - marketplaceName: "third-party" as never, - pluginName: "rogue", - enabled: true, - allowDestructiveActions: false, - }, - request: async () => { - throw new Error("plugin/list should not be reached when marketplace is rejected"); - }, - }); - - expectActivationResult(result, { - ok: false, - reason: "marketplace_missing", - installAttempted: false, - }); - }); - it("skips plugin/install when the migrated plugin is already active", async () => { const calls: string[] = []; const result = await ensureCodexPluginActivation({ @@ -348,13 +295,10 @@ describe("Codex plugin activation", () => { }); }); -function identity( - pluginName: string, - marketplaceName: ResolvedCodexPluginPolicy["marketplaceName"] = CODEX_PLUGINS_MARKETPLACE_NAME, -): ResolvedCodexPluginPolicy { +function identity(pluginName: string): ResolvedCodexPluginPolicy { return { configKey: pluginName, - marketplaceName, + marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME, pluginName, enabled: true, allowDestructiveActions: false, @@ -376,24 +320,6 @@ function pluginList(plugins: v2.PluginSummary[]): v2.PluginListResponse { }; } -function pluginListFor( - marketplaceName: ResolvedCodexPluginPolicy["marketplaceName"], - plugins: v2.PluginSummary[], -): v2.PluginListResponse { - return { - marketplaces: [ - { - name: marketplaceName, - path: `/marketplaces/${marketplaceName}`, - interface: null, - plugins, - }, - ], - marketplaceLoadErrors: [], - featuredPluginIds: [], - }; -} - function pluginSummary(id: string, overrides: Partial = {}): v2.PluginSummary { return { id, diff --git a/extensions/codex/src/app-server/plugin-activation.ts b/extensions/codex/src/app-server/plugin-activation.ts index a3c5bbb0e8e7..70129db1f152 100644 --- a/extensions/codex/src/app-server/plugin-activation.ts +++ b/extensions/codex/src/app-server/plugin-activation.ts @@ -1,11 +1,7 @@ import fs from "node:fs/promises"; import path from "node:path"; import type { CodexAppInventoryCache, CodexAppInventoryRequest } from "./app-inventory-cache.js"; -import { - CODEX_PLUGINS_MARKETPLACE_NAMES, - isCodexPluginsMarketplaceName, - type ResolvedCodexPluginPolicy, -} from "./config.js"; +import { CODEX_PLUGINS_MARKETPLACE_NAME, type ResolvedCodexPluginPolicy } from "./config.js"; import { findOpenAiCuratedPluginSummary, pluginReadParams, @@ -52,32 +48,27 @@ export type CodexPluginRuntimeRefreshResult = { export async function ensureCodexPluginActivation( params: EnsureCodexPluginActivationParams, ): Promise { - if (!isCodexPluginsMarketplaceName(params.identity.marketplaceName)) { + if (params.identity.marketplaceName !== CODEX_PLUGINS_MARKETPLACE_NAME) { return activationFailure(params.identity, "marketplace_missing", { - message: - "Only " + CODEX_PLUGINS_MARKETPLACE_NAMES.join(" or ") + " plugins can be activated.", + message: "Only openai-curated plugins can be activated.", }); } const listed = (await params.request("plugin/list", { cwds: [], } satisfies v2.PluginListParams)) as v2.PluginListResponse; - const resolved = findOpenAiCuratedPluginSummary( - listed, - params.identity.pluginName, - params.identity.marketplaceName, - ); + const resolved = findOpenAiCuratedPluginSummary(listed, params.identity.pluginName); if (!resolved) { - const hasMarketplace = listed.marketplaces.some( - (marketplace) => marketplace.name === params.identity.marketplaceName, + const hasCuratedMarketplace = listed.marketplaces.some( + (marketplace) => marketplace.name === CODEX_PLUGINS_MARKETPLACE_NAME, ); - if (!hasMarketplace) { + if (!hasCuratedMarketplace) { return activationFailure(params.identity, "marketplace_missing", { - message: `Codex marketplace ${params.identity.marketplaceName} was not found.`, + message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.`, }); } return activationFailure(params.identity, "plugin_missing", { - message: `${params.identity.pluginName} was not found in ${params.identity.marketplaceName}.`, + message: `${params.identity.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.`, }); } diff --git a/extensions/codex/src/app-server/plugin-inventory.ts b/extensions/codex/src/app-server/plugin-inventory.ts index 5591dabdb13b..aaa7a00d35c9 100644 --- a/extensions/codex/src/app-server/plugin-inventory.ts +++ b/extensions/codex/src/app-server/plugin-inventory.ts @@ -6,10 +6,7 @@ import type { } from "./app-inventory-cache.js"; import { CODEX_PLUGINS_MARKETPLACE_NAME, - CODEX_PLUGINS_MARKETPLACE_NAMES, - isCodexPluginsMarketplaceName, resolveCodexPluginsPolicy, - type CodexPluginsMarketplaceName, type ResolvedCodexPluginPolicy, type ResolvedCodexPluginsPolicy, } from "./config.js"; @@ -18,7 +15,7 @@ import type { v2 } from "./protocol.js"; export type CodexPluginRuntimeRequest = (method: string, params?: unknown) => Promise; export type CodexPluginMarketplaceRef = { - name: CodexPluginsMarketplaceName; + name: typeof CODEX_PLUGINS_MARKETPLACE_NAME; path?: string; remoteMarketplaceName?: string; }; @@ -60,6 +57,7 @@ export type CodexPluginInventoryRecord = { export type CodexPluginInventory = { policy: ResolvedCodexPluginsPolicy; + marketplace?: CodexPluginMarketplaceRef; records: CodexPluginInventoryRecord[]; diagnostics: CodexPluginInventoryDiagnostic[]; appInventory?: CodexAppInventoryCacheRead; @@ -97,14 +95,25 @@ export async function readCodexPluginInventory( const listed = (await params.request("plugin/list", { cwds: [], } satisfies v2.PluginListParams)) as v2.PluginListResponse; - // Index the supported marketplaces (curated + bundled) by name so each plugin - // policy is matched to the marketplace its config actually points at. - const marketplaceByName = new Map(); - for (const marketplace of listed.marketplaces) { - if (isCodexPluginsMarketplaceName(marketplace.name)) { - marketplaceByName.set(marketplace.name, marketplace); - } + const marketplaceEntry = listed.marketplaces.find( + (marketplace) => marketplace.name === CODEX_PLUGINS_MARKETPLACE_NAME, + ); + if (!marketplaceEntry) { + return { + policy, + records: [], + diagnostics: policy.pluginPolicies + .filter((pluginPolicy) => pluginPolicy.enabled) + .map((pluginPolicy) => ({ + code: "marketplace_missing", + plugin: pluginPolicy, + message: `Codex marketplace ${CODEX_PLUGINS_MARKETPLACE_NAME} was not found.`, + })), + ...(appInventory ? { appInventory } : {}), + }; } + + const marketplace = marketplaceRef(marketplaceEntry); const diagnostics: CodexPluginInventoryDiagnostic[] = []; const records: CodexPluginInventoryRecord[] = []; if (appInventory?.state === "missing") { @@ -123,22 +132,12 @@ export async function readCodexPluginInventory( if (!pluginPolicy.enabled) { continue; } - const marketplaceEntry = marketplaceByName.get(pluginPolicy.marketplaceName); - if (!marketplaceEntry) { - diagnostics.push({ - code: "marketplace_missing", - plugin: pluginPolicy, - message: `Codex marketplace ${pluginPolicy.marketplaceName} was not found.`, - }); - continue; - } - const marketplace = marketplaceRef(marketplaceEntry); const summary = findPluginSummary(marketplaceEntry, pluginPolicy.pluginName); if (!summary) { diagnostics.push({ code: "plugin_missing", plugin: pluginPolicy, - message: `${pluginPolicy.pluginName} was not found in ${pluginPolicy.marketplaceName}.`, + message: `${pluginPolicy.pluginName} was not found in ${CODEX_PLUGINS_MARKETPLACE_NAME}.`, }); continue; } @@ -188,6 +187,7 @@ export async function readCodexPluginInventory( const inventory = { policy, + marketplace, records, diagnostics, ...(appInventory ? { appInventory } : {}), @@ -198,32 +198,15 @@ export async function readCodexPluginInventory( export function findOpenAiCuratedPluginSummary( listed: v2.PluginListResponse, pluginName: string, - marketplaceName?: CodexPluginsMarketplaceName, ): { marketplace: CodexPluginMarketplaceRef; summary: v2.PluginSummary } | undefined { - if (marketplaceName) { - const marketplaceEntry = listed.marketplaces.find( - (marketplace) => marketplace.name === marketplaceName, - ); - if (!marketplaceEntry) { - return undefined; - } - const summary = findPluginSummary(marketplaceEntry, pluginName); - return summary ? { marketplace: marketplaceRef(marketplaceEntry), summary } : undefined; + const marketplaceEntry = listed.marketplaces.find( + (marketplace) => marketplace.name === CODEX_PLUGINS_MARKETPLACE_NAME, + ); + if (!marketplaceEntry) { + return undefined; } - // No marketplace hint: search every supported marketplace and return the first hit. - for (const allowedName of CODEX_PLUGINS_MARKETPLACE_NAMES) { - const marketplaceEntry = listed.marketplaces.find( - (marketplace) => marketplace.name === allowedName, - ); - if (!marketplaceEntry) { - continue; - } - const summary = findPluginSummary(marketplaceEntry, pluginName); - if (summary) { - return { marketplace: marketplaceRef(marketplaceEntry), summary }; - } - } - return undefined; + const summary = findPluginSummary(marketplaceEntry, pluginName); + return summary ? { marketplace: marketplaceRef(marketplaceEntry), summary } : undefined; } export function pluginReadParams( @@ -366,12 +349,8 @@ function pluginNameFromPluginId(pluginId: string, marketplaceName: string): stri } function marketplaceRef(marketplace: v2.PluginMarketplaceEntry): CodexPluginMarketplaceRef { - // marketplace.name is validated at every call site via isCodexPluginsMarketplaceName. - const name = isCodexPluginsMarketplaceName(marketplace.name) - ? marketplace.name - : CODEX_PLUGINS_MARKETPLACE_NAME; return { - name, + name: CODEX_PLUGINS_MARKETPLACE_NAME, ...(marketplace.path ? { path: marketplace.path } : {}), ...(!marketplace.path ? { remoteMarketplaceName: marketplace.name } : {}), }; diff --git a/extensions/codex/src/app-server/session-binding.test.ts b/extensions/codex/src/app-server/session-binding.test.ts index ecabaab03a02..0e427972c558 100644 --- a/extensions/codex/src/app-server/session-binding.test.ts +++ b/extensions/codex/src/app-server/session-binding.test.ts @@ -107,36 +107,6 @@ describe("codex app-server session binding", () => { expect(binding?.pluginAppPolicyContext).toEqual(pluginAppPolicyContext); }); - it("round-trips plugin app policy context for openai-bundled marketplace plugins", async () => { - // The chrome plugin lives in openai-bundled (ships with Codex.app), so - // its policy must persist across reads/writes the same way curated entries do. - const sessionFile = path.join(tempDir, "session-bundled.json"); - const pluginAppPolicyContext = { - fingerprint: "plugin-policy-bundled-1", - apps: { - "chrome-app": { - configKey: "chrome", - marketplaceName: "openai-bundled" as const, - pluginName: "chrome", - allowDestructiveActions: true, - mcpServerNames: ["chrome"], - }, - }, - pluginAppIds: { - chrome: ["chrome-app"], - }, - }; - await writeCodexAppServerBinding(sessionFile, { - threadId: "thread-bundled", - cwd: tempDir, - pluginAppPolicyContext, - }); - - const binding = await readCodexAppServerBinding(sessionFile); - - expect(binding?.pluginAppPolicyContext).toEqual(pluginAppPolicyContext); - }); - it("round-trips context-engine binding metadata", async () => { const sessionFile = path.join(tempDir, "session.json"); await writeCodexAppServerBinding(sessionFile, { diff --git a/extensions/codex/src/app-server/session-binding.ts b/extensions/codex/src/app-server/session-binding.ts index 80401ca007e6..ff2753c6908c 100644 --- a/extensions/codex/src/app-server/session-binding.ts +++ b/extensions/codex/src/app-server/session-binding.ts @@ -7,7 +7,7 @@ import { type AuthProfileStore, } from "openclaw/plugin-sdk/agent-runtime"; import { - isCodexPluginsMarketplaceName, + CODEX_PLUGINS_MARKETPLACE_NAME, normalizeCodexServiceTier, type CodexAppServerApprovalPolicy, type CodexAppServerSandboxMode, @@ -257,8 +257,7 @@ function readPluginAppPolicyContext(value: unknown): PluginAppPolicyContext | un if ( "appId" in entry || typeof entry.configKey !== "string" || - typeof entry.marketplaceName !== "string" || - !isCodexPluginsMarketplaceName(entry.marketplaceName) || + entry.marketplaceName !== CODEX_PLUGINS_MARKETPLACE_NAME || typeof entry.pluginName !== "string" || typeof entry.allowDestructiveActions !== "boolean" || !Array.isArray(entry.mcpServerNames) || diff --git a/extensions/codex/src/migration/apply.ts b/extensions/codex/src/migration/apply.ts index 3950033454ab..550414a919f5 100644 --- a/extensions/codex/src/migration/apply.ts +++ b/extensions/codex/src/migration/apply.ts @@ -30,7 +30,6 @@ import { } from "../app-server/auth-bridge.js"; import { CODEX_PLUGINS_MARKETPLACE_NAME, - isCodexPluginsMarketplaceName, readCodexPluginConfig, resolveCodexAppServerRuntimeOptions, type ResolvedCodexPluginPolicy, @@ -355,13 +354,12 @@ function hasOpenAiCuratedMarketplace(response: unknown): boolean { const marketplaces = (response as { marketplaces?: unknown }).marketplaces; return ( Array.isArray(marketplaces) && - marketplaces.some((marketplace) => { - if (!marketplace || typeof marketplace !== "object") { - return false; - } - const name = (marketplace as { name?: unknown }).name; - return name === CODEX_PLUGINS_MARKETPLACE_NAME; - }) + marketplaces.some( + (marketplace) => + marketplace && + typeof marketplace === "object" && + (marketplace as { name?: unknown }).name === CODEX_PLUGINS_MARKETPLACE_NAME, + ) ); } @@ -498,15 +496,14 @@ function readCodexPluginPolicy(item: MigrationItem): ResolvedCodexPluginPolicy | const pluginName = item.details?.pluginName; if ( typeof configKey !== "string" || - typeof marketplaceName !== "string" || - !isCodexPluginsMarketplaceName(marketplaceName) || + marketplaceName !== CODEX_PLUGINS_MARKETPLACE_NAME || typeof pluginName !== "string" ) { return undefined; } return { configKey, - marketplaceName, + marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME, pluginName, enabled: true, allowDestructiveActions: true, diff --git a/extensions/codex/src/migration/provider.test.ts b/extensions/codex/src/migration/provider.test.ts index 60ccf6771d35..bfab1128fee0 100644 --- a/extensions/codex/src/migration/provider.test.ts +++ b/extensions/codex/src/migration/provider.test.ts @@ -1462,7 +1462,7 @@ describe("buildCodexMigrationProvider", () => { if (method === "plugin/list" && isTarget) { targetPluginListCalls += 1; if (targetPluginListCalls === 1) { - return pluginList([], "openai-bundled"); + return { marketplaces: [], marketplaceLoadErrors: [], featuredPluginIds: [] }; } return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]); } @@ -2225,15 +2225,12 @@ function createConfigRuntime( } as unknown as MigrationProviderContext["runtime"]; } -function pluginList( - plugins: v2.PluginSummary[], - marketplaceName = CODEX_PLUGINS_MARKETPLACE_NAME, -): v2.PluginListResponse { +function pluginList(plugins: v2.PluginSummary[]): v2.PluginListResponse { return { marketplaces: [ { - name: marketplaceName, - path: `/marketplaces/${marketplaceName}`, + name: CODEX_PLUGINS_MARKETPLACE_NAME, + path: "/marketplaces/openai-curated", interface: null, plugins, }, diff --git a/extensions/sms/src/channel.test.ts b/extensions/sms/src/channel.test.ts index 13ff16c4534f..9d2d5efabf30 100644 --- a/extensions/sms/src/channel.test.ts +++ b/extensions/sms/src/channel.test.ts @@ -1,5 +1,4 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; -import type { ResolvedSmsAccount } from "./types.js"; type ChannelModule = typeof import("./channel.js"); @@ -49,16 +48,13 @@ describe("smsPlugin status", () => { }, }); - expect(snapshot).toMatchObject({ + expect(snapshot).toEqual({ accountId: "support", name: "+15557654321", enabled: true, configured: true, statusState: "configured", - running: false, - webhookPath: "/webhooks/sms", }); - expect(snapshot).not.toHaveProperty("connected"); }); }); @@ -156,33 +152,4 @@ describe("smsPlugin outbound", () => { }), ).toEqual({ ok: true, to: "+15551234567" }); }); - - it("preserves inspected account status fields", async () => { - const cfg = { - channels: { - sms: { - accountSid: "AC123", - authToken: "secret", - fromNumber: "+15557654321", - webhookPath: "/twilio/sms", - }, - }, - }; - const account = smsPlugin.config.inspectAccount?.(cfg); - expect(account).toBeDefined(); - - const snapshot = await smsPlugin.status?.buildAccountSnapshot?.({ - account: account as ResolvedSmsAccount, - cfg, - }); - - expect(snapshot).toMatchObject({ - configured: true, - enabled: true, - statusState: "configured", - tokenStatus: "available", - webhookPath: "/twilio/sms", - }); - expect(snapshot).not.toHaveProperty("connected"); - }); }); diff --git a/extensions/sms/src/channel.ts b/extensions/sms/src/channel.ts index 08c9a212a5e1..a9d6ec00e193 100644 --- a/extensions/sms/src/channel.ts +++ b/extensions/sms/src/channel.ts @@ -35,46 +35,6 @@ import type { ResolvedSmsAccount } from "./types.js"; const CHANNEL_ID = "sms"; -type SmsStatusSnapshotAccount = Partial & { - configured?: boolean; - tokenStatus?: string; - webhookPath?: string; -}; - -function buildSmsAccountSnapshot(params: { - account: ResolvedSmsAccount; - runtime?: { - running?: boolean; - connected?: boolean; - lastConnectedAt?: number | null; - lastError?: string | null; - lastInboundAt?: number | null; - lastOutboundAt?: number | null; - }; -}) { - const account = params.account as SmsStatusSnapshotAccount; - const configured = - typeof account.configured === "boolean" - ? account.configured - : isSmsAccountConfigured(params.account); - return { - accountId: account.accountId ?? "", - name: account.fromNumber || account.messagingServiceSid || "SMS", - enabled: account.enabled, - configured, - statusState: - account.enabled === false ? "disabled" : configured ? "configured" : "unconfigured", - ...(account.tokenStatus ? { tokenStatus: account.tokenStatus } : {}), - ...(account.webhookPath ? { webhookPath: account.webhookPath } : {}), - running: params.runtime?.running ?? false, - ...(params.runtime?.connected !== undefined ? { connected: params.runtime.connected } : {}), - lastConnectedAt: params.runtime?.lastConnectedAt ?? null, - lastError: params.runtime?.lastError ?? null, - lastInboundAt: params.runtime?.lastInboundAt ?? null, - lastOutboundAt: params.runtime?.lastOutboundAt ?? null, - }; -} - const smsConfigAdapter = createHybridChannelConfigAdapter({ sectionKey: CHANNEL_ID, listAccountIds: listSmsAccountIds, @@ -296,17 +256,18 @@ export const smsPlugin: ChannelPlugin = createChat }, }, status: { - defaultRuntime: { - accountId: DEFAULT_ACCOUNT_ID, - running: false, - lastConnectedAt: null, - lastError: null, - lastInboundAt: null, - lastOutboundAt: null, + buildAccountSnapshot: ({ account }) => { + const configured = isSmsAccountConfigured(account); + return { + accountId: account.accountId, + name: account.fromNumber || account.messagingServiceSid || "SMS", + enabled: account.enabled, + configured, + statusState: !account.enabled ? "disabled" : configured ? "configured" : "unconfigured", + }; }, probeAccount: async ({ account, timeoutMs }) => await probeSmsAccount({ account, timeoutMs }), formatCapabilitiesProbe: ({ probe }) => formatSmsProbeLines(probe), - buildAccountSnapshot: buildSmsAccountSnapshot, buildCapabilitiesDiagnostics: async ({ account }) => ({ lines: collectSmsStartupWarnings(account).map((text) => ({ text, tone: "warn" })), }), diff --git a/extensions/sms/src/inbound.test.ts b/extensions/sms/src/inbound.test.ts index 65738d7a739e..207e0823a244 100644 --- a/extensions/sms/src/inbound.test.ts +++ b/extensions/sms/src/inbound.test.ts @@ -109,15 +109,12 @@ describe("dispatchSmsInboundEvent", () => { meta: undefined, }); expect(sendSmsViaTwilio).toHaveBeenCalledOnce(); - const firstSendCall = sendSmsViaTwilio.mock.calls[0]; - expect(firstSendCall).toBeDefined(); - if (!firstSendCall) { - throw new Error("Expected SMS send call"); - } - expect(firstSendCall[0]).toMatchObject({ - to: "+15551234567", - }); - expect(firstSendCall[0].text).toContain("PAIR123"); + expect(sendSmsViaTwilio).toHaveBeenCalledWith( + expect.objectContaining({ + to: "+15551234567", + text: expect.stringContaining("PAIR123"), + }), + ); }); it("uses the canonical routed session key for authorized SMS turns", async () => { diff --git a/extensions/sms/src/twilio.test.ts b/extensions/sms/src/twilio.test.ts index 2ea1a7007946..dbc333ecab0d 100644 --- a/extensions/sms/src/twilio.test.ts +++ b/extensions/sms/src/twilio.test.ts @@ -176,17 +176,8 @@ describe("Twilio SMS helpers", () => { status: "queued", }); - const firstFetchCall = fetchImpl.mock.calls[0]; - expect(firstFetchCall).toBeDefined(); - if (!firstFetchCall) { - throw new Error("Expected Twilio fetch call"); - } - const [url, init] = firstFetchCall; + const [url, init] = fetchImpl.mock.calls[0] ?? []; expect(url).toBe("https://api.twilio.com/2010-04-01/Accounts/AC123/Messages.json"); - expect(init).toBeDefined(); - if (!init) { - throw new Error("Expected Twilio request init"); - } expect(init?.method).toBe("POST"); expect(init?.headers).toMatchObject({ authorization: `Basic ${Buffer.from("AC123:secret").toString("base64")}`, diff --git a/src/agents/tools/skill-workshop-tool.ts b/src/agents/tools/skill-workshop-tool.ts index 512a0fdce1b5..2977bacc7616 100644 --- a/src/agents/tools/skill-workshop-tool.ts +++ b/src/agents/tools/skill-workshop-tool.ts @@ -386,9 +386,6 @@ function listProposalEntries(params: { proposal.skillName, proposal.skillKey, ].some((value) => { - if (typeof value !== "string") { - return false; - } const lower = value.toLowerCase(); return ( lower.includes(query) || diff --git a/src/config/sessions.cache.test.ts b/src/config/sessions.cache.test.ts index ec6219741184..a9bb37568006 100644 --- a/src/config/sessions.cache.test.ts +++ b/src/config/sessions.cache.test.ts @@ -314,8 +314,8 @@ describe("Session Store Cache", () => { if (!entry) { throw new Error("Expected cached entry"); } - expect(entry.polluted).toBeUndefined(); - expect(Object.hasOwn(entry, "__proto__")).toBe(true); + expect(entry?.polluted).toBeUndefined(); + expect(Object.hasOwn(entry as object, "__proto__")).toBe(true); expect(Object.prototype).not.toHaveProperty("polluted"); }); diff --git a/src/gateway/server.agent.gateway-server-agent-b.test.ts b/src/gateway/server.agent.gateway-server-agent-b.test.ts index f8c546a71d16..843a631aa6d0 100644 --- a/src/gateway/server.agent.gateway-server-agent-b.test.ts +++ b/src/gateway/server.agent.gateway-server-agent-b.test.ts @@ -360,10 +360,11 @@ describe("gateway server agent", () => { }); test("agent errors when deliver=true and last channel is webchat", async () => { + testState.allowFrom = ["+1555"]; await writeMainSessionEntry({ sessionId: "sess-main-webchat", lastChannel: "webchat", - lastTo: "webchat-room", + lastTo: "+1555", }); const res = await rpcReq(ws, "agent", { message: "hi",