Skip to content

Commit 430ae31

Browse files
committed
fix(codex): match namespaced plugin ids
1 parent eb6e070 commit 430ae31

2 files changed

Lines changed: 79 additions & 2 deletions

File tree

extensions/codex/src/app-server/plugin-inventory.test.ts

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { describe, expect, it } from "vitest";
22
import { CodexAppInventoryCache } from "./app-inventory-cache.js";
33
import { CODEX_PLUGINS_MARKETPLACE_NAME } from "./config.js";
4-
import { readCodexPluginInventory } from "./plugin-inventory.js";
4+
import { findOpenAiCuratedPluginSummary, readCodexPluginInventory } from "./plugin-inventory.js";
55
import type { v2 } from "./protocol.js";
66

77
describe("Codex plugin inventory", () => {
@@ -66,6 +66,70 @@ describe("Codex plugin inventory", () => {
6666
expect(calls).toEqual(["plugin/list", "plugin/read"]);
6767
});
6868

69+
it("matches namespaced curated plugin ids by normalized path segment", async () => {
70+
const appCache = new CodexAppInventoryCache();
71+
await appCache.refreshNow({
72+
key: "runtime",
73+
nowMs: 0,
74+
request: async () => ({
75+
data: [appInfo("github-app", true)],
76+
nextCursor: null,
77+
}),
78+
});
79+
80+
const listed = pluginList([
81+
pluginSummary("openai-curated/github", {
82+
name: "GitHub",
83+
installed: true,
84+
enabled: true,
85+
}),
86+
]);
87+
expect(findOpenAiCuratedPluginSummary(listed, "github")?.summary.id).toBe(
88+
"openai-curated/github",
89+
);
90+
91+
const inventory = await readCodexPluginInventory({
92+
pluginConfig: {
93+
codexPlugins: {
94+
enabled: true,
95+
plugins: {
96+
github: {
97+
marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
98+
pluginName: "github",
99+
},
100+
},
101+
},
102+
},
103+
appCache,
104+
appCacheKey: "runtime",
105+
nowMs: 1,
106+
request: async (method, params) => {
107+
if (method === "plugin/list") {
108+
return listed;
109+
}
110+
if (method === "plugin/read") {
111+
expect(params).toMatchObject({
112+
marketplacePath: "/marketplaces/openai-curated",
113+
pluginName: "github",
114+
});
115+
return pluginDetail("github", [appSummary("github-app")]);
116+
}
117+
throw new Error(`unexpected request ${method}`);
118+
},
119+
});
120+
121+
expect(inventory.records).toHaveLength(1);
122+
expect(inventory.records[0]).toMatchObject({
123+
policy: { pluginName: "github" },
124+
summary: { id: "openai-curated/github", installed: true, enabled: true },
125+
appOwnership: "proven",
126+
ownedAppIds: ["github-app"],
127+
});
128+
expect(inventory.diagnostics).not.toContainEqual(
129+
expect.objectContaining({ code: "plugin_missing" }),
130+
);
131+
});
132+
69133
it("fails closed when plugin detail apps are absent from app inventory", async () => {
70134
const appCache = new CodexAppInventoryCache();
71135
await appCache.refreshNow({

extensions/codex/src/app-server/plugin-inventory.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -320,10 +320,23 @@ function findPluginSummary(
320320
(plugin) =>
321321
plugin.name === pluginName ||
322322
plugin.id === pluginName ||
323-
plugin.id === `${pluginName}@${marketplace.name}`,
323+
plugin.id === `${pluginName}@${marketplace.name}` ||
324+
pluginNameFromPluginId(plugin.id, marketplace.name) === pluginName,
324325
);
325326
}
326327

328+
function pluginNameFromPluginId(pluginId: string, marketplaceName: string): string | undefined {
329+
const trimmed = pluginId.trim();
330+
if (!trimmed) {
331+
return undefined;
332+
}
333+
const marketplaceSuffix = `@${marketplaceName}`;
334+
const withoutMarketplaceSuffix = trimmed.endsWith(marketplaceSuffix)
335+
? trimmed.slice(0, -marketplaceSuffix.length)
336+
: trimmed;
337+
return withoutMarketplaceSuffix.split("/").at(-1)?.trim() || undefined;
338+
}
339+
327340
function marketplaceRef(marketplace: v2.PluginMarketplaceEntry): CodexPluginMarketplaceRef {
328341
return {
329342
name: CODEX_PLUGINS_MARKETPLACE_NAME,

0 commit comments

Comments
 (0)