惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
GbyAI
GbyAI
P
Proofpoint News Feed
Engineering at Meta
Engineering at Meta
Recent Announcements
Recent Announcements
L
LangChain Blog
B
Blog
阮一峰的网络日志
阮一峰的网络日志
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
M
MIT News - Artificial intelligence
D
Docker
WordPress大学
WordPress大学
J
Java Code Geeks
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
The GitHub Blog
The GitHub Blog
博客园 - 叶小钗
Last Week in AI
Last Week in AI
Stack Overflow Blog
Stack Overflow Blog
有赞技术团队
有赞技术团队
MyScale Blog
MyScale Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
MongoDB | Blog
MongoDB | Blog
博客园 - Franky

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
feat(codex): add plugin list enable disable commands (#83...
kevinslin · 2026-05-20 · via Recent Commits to openclaw:main

@@ -0,0 +1,172 @@

1+

import type { PluginCommandContext } from "openclaw/plugin-sdk/plugin-entry";

2+

import { describe, expect, it } from "vitest";

3+

import {

4+

handleCodexPluginsSubcommand,

5+

type CodexPluginsConfigBlock,

6+

type CodexPluginConfigEntry,

7+

type CodexPluginsManagementIO,

8+

} from "./command-plugins-management.js";

9+10+

function inMemoryIO(

11+

initial: Record<string, CodexPluginConfigEntry> = {},

12+

options: { enabled?: boolean } = { enabled: true },

13+

): CodexPluginsManagementIO & {

14+

current: () => Record<string, CodexPluginConfigEntry>;

15+

currentConfig: () => CodexPluginsConfigBlock;

16+

} {

17+

const store: CodexPluginsConfigBlock = {

18+

enabled: options.enabled,

19+

plugins: JSON.parse(JSON.stringify(initial)),

20+

};

21+

return {

22+

current: () => JSON.parse(JSON.stringify(store.plugins ?? {})),

23+

currentConfig: () => JSON.parse(JSON.stringify(store)),

24+

readConfig: () => Promise.resolve(JSON.parse(JSON.stringify(store))),

25+

mutate: async (update) => {

26+

update(store);

27+

},

28+

};

29+

}

30+31+

const fakeCtx: PluginCommandContext = {

32+

args: "",

33+

config: {},

34+

channel: "test",

35+

isAuthorizedSender: true,

36+

senderIsOwner: true,

37+

commandBody: "/codex plugins",

38+

requestConversationBinding: async () => ({ status: "error", message: "unused" }),

39+

detachConversationBinding: async () => ({ removed: false }),

40+

getCurrentConversationBinding: async () => null,

41+

};

42+43+

describe("Codex /codex plugins subcommand", () => {

44+

it("lists a configured plugin with its enabled marker and explains the underlying file", async () => {

45+

const io = inMemoryIO({

46+

"google-calendar": {

47+

enabled: true,

48+

marketplaceName: "openai-curated",

49+

pluginName: "google-calendar",

50+

},

51+

});

52+53+

const result = await handleCodexPluginsSubcommand(fakeCtx, ["list"], io);

54+

expect(result.text).toContain("ON google-calendar");

55+

expect(result.text).toContain("openclaw.json");

56+

});

57+58+

it("lists effective disabled status when the global plugin switch is off", async () => {

59+

const io = inMemoryIO(

60+

{

61+

"google-calendar": {

62+

enabled: true,

63+

marketplaceName: "openai-curated",

64+

pluginName: "google-calendar",

65+

},

66+

},

67+

{ enabled: false },

68+

);

69+70+

const result = await handleCodexPluginsSubcommand(fakeCtx, ["list"], io);

71+

expect(result.text).toContain("OFF google-calendar");

72+

expect(result.text).toContain("Global codexPlugins.enabled is off");

73+

});

74+75+

it("enables and disables a configured plugin and reflects the change in subsequent reads", async () => {

76+

const io = inMemoryIO({

77+

"google-calendar": {

78+

enabled: true,

79+

marketplaceName: "openai-curated",

80+

pluginName: "google-calendar",

81+

},

82+

});

83+84+

const disabled = await handleCodexPluginsSubcommand(

85+

fakeCtx,

86+

["disable", "google-calendar"],

87+

io,

88+

);

89+

expect(disabled.text).toContain("disabled");

90+

expect(io.current()["google-calendar"]?.enabled).toBe(false);

91+92+

const enabled = await handleCodexPluginsSubcommand(fakeCtx, ["enable", "google-calendar"], io);

93+

expect(enabled.text).toContain("enabled");

94+

expect(io.currentConfig().enabled).toBe(true);

95+

expect(io.current()["google-calendar"]?.enabled).toBe(true);

96+

});

97+98+

it("rejects enable and disable from non-owner non-admin callers", async () => {

99+

const io = inMemoryIO({

100+

"google-calendar": {

101+

enabled: true,

102+

marketplaceName: "openai-curated",

103+

pluginName: "google-calendar",

104+

},

105+

});

106+

const ctx = { ...fakeCtx, senderIsOwner: false, gatewayClientScopes: ["operator.write"] };

107+108+

const result = await handleCodexPluginsSubcommand(ctx, ["disable", "google-calendar"], io);

109+

expect(result.text).toContain("Only an owner or operator.admin");

110+

expect(io.current()["google-calendar"]?.enabled).toBe(true);

111+

});

112+113+

it("allows operator.admin gateway callers to enable and disable", async () => {

114+

const io = inMemoryIO({

115+

"google-calendar": {

116+

enabled: true,

117+

marketplaceName: "openai-curated",

118+

pluginName: "google-calendar",

119+

},

120+

});

121+

const ctx = { ...fakeCtx, senderIsOwner: false, gatewayClientScopes: ["operator.admin"] };

122+123+

const result = await handleCodexPluginsSubcommand(ctx, ["disable", "google-calendar"], io);

124+

expect(result.text).toContain("disabled");

125+

expect(io.current()["google-calendar"]?.enabled).toBe(false);

126+

});

127+128+

it("escapes configured plugin fields before listing them in chat", async () => {

129+

const io = inMemoryIO({

130+

"google-calendar": {

131+

enabled: true,

132+

marketplaceName: "openai-curated",

133+

pluginName: "google-calendar_@team_*name*",

134+

},

135+

});

136+137+

const result = await handleCodexPluginsSubcommand(fakeCtx, ["list"], io);

138+

expect(result.text).toContain("google-calendar");

139+

expect(result.text).toContain("google-calendar_@team_∗name∗");

140+

expect(result.text).not.toContain("@team");

141+

expect(result.text).not.toContain("*name*");

142+

});

143+144+

it("reports when a target plugin is not configured rather than silently no-oping", async () => {

145+

const io = inMemoryIO();

146+

const result = await handleCodexPluginsSubcommand(fakeCtx, ["disable", "chrome_@ops"], io);

147+

expect(result.text).toContain("not configured");

148+

expect(result.text).toContain("chrome_@ops");

149+

expect(result.text).not.toContain("@ops");

150+

});

151+152+

it("returns usage when list, enable, or disable receives the wrong arity", async () => {

153+

const io = inMemoryIO();

154+

const listResult = await handleCodexPluginsSubcommand(fakeCtx, ["list", "chrome"], io);

155+

expect(listResult.text).toContain("Usage: /codex plugins list");

156+157+

const result = await handleCodexPluginsSubcommand(fakeCtx, ["disable"], io);

158+

expect(result.text).toContain("Usage: /codex plugins disable <name>");

159+

expect(result.presentation).toBeUndefined();

160+161+

const enableResult = await handleCodexPluginsSubcommand(fakeCtx, ["enable"], io);

162+

expect(enableResult.text).toContain("Usage: /codex plugins enable <name>");

163+

expect(enableResult.presentation).toBeUndefined();

164+165+

const extraResult = await handleCodexPluginsSubcommand(

166+

fakeCtx,

167+

["enable", "google-calendar", "extra"],

168+

io,

169+

);

170+

expect(extraResult.text).toContain("Usage: /codex plugins enable <name>");

171+

});

172+

});