
























@@ -254,7 +254,7 @@ describe("Codex plugin thread config", () => {
254254const request = vi.fn(async (method: string, params?: unknown) => {
255255if (method === "app/list") {
256256appListParams.push(params as v2.AppsListParams);
257-return { data: [appInfo("google-calendar-app", true)], nextCursor: null };
257+return { data: [appInfo("google-calendar-app", true, false)], nextCursor: null };
258258}
259259if (method === "plugin/list") {
260260return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
@@ -317,6 +317,117 @@ describe("Codex plugin thread config", () => {
317317]);
318318});
319319320+it("re-enables an OpenClaw-allowed app even when app/list reports it disabled", async () => {
321+const appCache = new CodexAppInventoryCache();
322+await appCache.refreshNow({
323+key: "runtime",
324+nowMs: 0,
325+request: async () => ({
326+data: [appInfo("google-calendar-app", true, false)],
327+nextCursor: null,
328+}),
329+});
330+331+const config = await buildCodexPluginThreadConfig({
332+pluginConfig: {
333+codexPlugins: {
334+enabled: true,
335+plugins: {
336+"google-calendar": {
337+marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
338+pluginName: "google-calendar",
339+},
340+},
341+},
342+},
343+ appCache,
344+appCacheKey: "runtime",
345+nowMs: 1,
346+request: async (method) => {
347+if (method === "plugin/list") {
348+return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
349+}
350+if (method === "plugin/read") {
351+return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
352+}
353+throw new Error(`unexpected request ${method}`);
354+},
355+});
356+357+expect(config.inventory?.records[0]?.apps).toStrictEqual([
358+{
359+id: "google-calendar-app",
360+name: "google-calendar-app",
361+accessible: true,
362+enabled: false,
363+needsAuth: false,
364+},
365+]);
366+expect(config.configPatch?.apps).toMatchObject({
367+"google-calendar-app": {
368+enabled: true,
369+},
370+});
371+expect(config.diagnostics).toStrictEqual([]);
372+});
373+374+it("refreshes missing app inventory when plugin activation becomes unnecessary", async () => {
375+const appCache = new CodexAppInventoryCache();
376+const appListParams: v2.AppsListParams[] = [];
377+let pluginListCalls = 0;
378+const request = vi.fn(async (method: string, params?: unknown) => {
379+if (method === "plugin/list") {
380+pluginListCalls += 1;
381+const active = pluginListCalls > 1;
382+return pluginList([
383+pluginSummary("google-calendar", { installed: active, enabled: active }),
384+]);
385+}
386+if (method === "plugin/read") {
387+return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
388+}
389+if (method === "app/list") {
390+appListParams.push(params as v2.AppsListParams);
391+return {
392+data: [appInfo("google-calendar-app", true)],
393+nextCursor: null,
394+} satisfies v2.AppsListResponse;
395+}
396+throw new Error(`unexpected request ${method}`);
397+});
398+399+const config = await buildCodexPluginThreadConfig({
400+pluginConfig: {
401+codexPlugins: {
402+enabled: true,
403+plugins: {
404+"google-calendar": {
405+marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
406+pluginName: "google-calendar",
407+},
408+},
409+},
410+},
411+ appCache,
412+appCacheKey: "runtime",
413+ request,
414+});
415+416+expect(config.configPatch?.apps).toMatchObject({
417+"google-calendar-app": {
418+enabled: true,
419+},
420+});
421+expect(request.mock.calls.map(([method]) => method)).not.toContain("plugin/install");
422+expect(appListParams).toEqual([
423+{
424+cursor: undefined,
425+limit: 100,
426+forceRefetch: true,
427+},
428+]);
429+});
430+320431it("does not expose plugin apps missing from the app inventory snapshot", async () => {
321432const appCache = new CodexAppInventoryCache();
322433await appCache.refreshNow({
@@ -375,11 +486,59 @@ describe("Codex plugin thread config", () => {
375486allowDestructiveActions: true,
376487destructiveApprovalMode: "allow",
377488},
378-message: "google-calendar-app is not accessible or enabled for google-calendar.",
489+message: "google-calendar-app is not accessible for google-calendar.",
379490},
380491]);
381492});
382493494+it("does not expose apps for plugins that OpenClaw policy leaves disabled", async () => {
495+const appCache = new CodexAppInventoryCache();
496+await appCache.refreshNow({
497+key: "runtime",
498+nowMs: 0,
499+request: async () => ({
500+data: [appInfo("google-calendar-app", true)],
501+nextCursor: null,
502+}),
503+});
504+505+const config = await buildCodexPluginThreadConfig({
506+pluginConfig: {
507+codexPlugins: {
508+enabled: true,
509+plugins: {
510+"google-calendar": {
511+enabled: false,
512+marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
513+pluginName: "google-calendar",
514+},
515+},
516+},
517+},
518+ appCache,
519+appCacheKey: "runtime",
520+nowMs: 1,
521+request: async (method) => {
522+if (method === "plugin/list") {
523+return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
524+}
525+throw new Error(`unexpected request ${method}`);
526+},
527+});
528+529+expect(config.configPatch).toEqual({
530+apps: {
531+_default: {
532+enabled: false,
533+destructive_enabled: false,
534+open_world_enabled: false,
535+},
536+},
537+});
538+expect(config.policyContext.apps).toStrictEqual({});
539+expect(config.diagnostics).toStrictEqual([]);
540+});
541+383542it("force-refreshes app inventory when proven plugin apps are not ready", async () => {
384543const appCache = new CodexAppInventoryCache();
385544await appCache.refreshNow({
@@ -572,9 +731,7 @@ describe("Codex plugin thread config", () => {
572731let installed = false;
573732const request = vi.fn(async (method: string, params?: unknown) => {
574733if (method === "plugin/list") {
575-return pluginList([
576-pluginSummary("google-calendar", { installed, enabled: installed }),
577-]);
734+return pluginList([pluginSummary("google-calendar", { installed, enabled: installed })]);
578735}
579736if (method === "plugin/read") {
580737return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
@@ -738,6 +895,70 @@ describe("Codex plugin thread config", () => {
738895]);
739896});
740897898+it("fails closed when app inventory entries are malformed", async () => {
899+const appCache = new CodexAppInventoryCache();
900+await appCache.refreshNow({
901+key: "runtime",
902+nowMs: 0,
903+request: async () =>
904+({
905+data: [{ ...appInfo("google-calendar-app", true), id: "" }] as unknown as v2.AppInfo[],
906+nextCursor: null,
907+}) satisfies v2.AppsListResponse,
908+});
909+910+const config = await buildCodexPluginThreadConfig({
911+pluginConfig: {
912+codexPlugins: {
913+enabled: true,
914+plugins: {
915+"google-calendar": {
916+marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
917+pluginName: "google-calendar",
918+},
919+},
920+},
921+},
922+ appCache,
923+appCacheKey: "runtime",
924+nowMs: 1,
925+request: async (method) => {
926+if (method === "plugin/list") {
927+return pluginList([pluginSummary("google-calendar", { installed: true, enabled: true })]);
928+}
929+if (method === "plugin/read") {
930+return pluginDetail("google-calendar", [appSummary("google-calendar-app")]);
931+}
932+throw new Error(`unexpected request ${method}`);
933+},
934+});
935+936+expect(config.configPatch).toEqual({
937+apps: {
938+_default: {
939+enabled: false,
940+destructive_enabled: false,
941+open_world_enabled: false,
942+},
943+},
944+});
945+expect(config.policyContext.apps).toStrictEqual({});
946+expect(config.diagnostics).toStrictEqual([
947+{
948+code: "app_not_ready",
949+plugin: {
950+configKey: "google-calendar",
951+marketplaceName: CODEX_PLUGINS_MARKETPLACE_NAME,
952+pluginName: "google-calendar",
953+enabled: true,
954+allowDestructiveActions: true,
955+destructiveApprovalMode: "allow",
956+},
957+message: "google-calendar-app is not accessible for google-calendar.",
958+},
959+]);
960+});
961+741962it("uses durable policy and app cache key in the cheap input fingerprint", async () => {
742963const appCache = new CodexAppInventoryCache();
743964const first = buildCodexPluginThreadConfigInputFingerprint({
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。