Skip to content

Commit e6becbe

Browse files
muwenyan521code-yeongyu
authored andcommitted
fix(plugin): register builtin skill commands
1 parent 5173b91 commit e6becbe

2 files changed

Lines changed: 176 additions & 15 deletions

File tree

packages/omo-opencode/src/plugin-handlers/command-config-handler.test.ts

Lines changed: 155 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
11
/// <reference types="bun-types" />
22

33
import { afterEach, beforeEach, describe, expect, spyOn, test } from "bun:test";
4+
import type { OhMyOpenCodeConfig } from "../config";
45
import * as builtinCommands from "../features/builtin-commands";
56
import * as commandLoader from "../features/claude-code-command-loader";
67
import * as skillLoader from "../features/opencode-skill-loader";
7-
import type { OhMyOpenCodeConfig } from "../config";
8-
import type { PluginComponents } from "./plugin-components-loader";
9-
import { applyCommandConfig } from "./command-config-handler";
108
import {
119
getAgentDisplayName,
1210
getAgentListDisplayName,
1311
} from "../shared/agent-display-names";
12+
import { applyCommandConfig } from "./command-config-handler";
13+
import type { PluginComponents } from "./plugin-components-loader";
1414

1515
function createPluginComponents(): PluginComponents {
1616
return {
@@ -24,13 +24,14 @@ function createPluginComponents(): PluginComponents {
2424
};
2525
}
2626

27-
function createPluginConfig(): OhMyOpenCodeConfig {
27+
function createPluginConfig(overrides: Partial<OhMyOpenCodeConfig> = {}): OhMyOpenCodeConfig {
2828
return {
2929
git_master: {
3030
commit_footer: true,
3131
include_co_authored_by: true,
3232
git_env_prefix: "GIT_MASTER=1",
3333
},
34+
...overrides,
3435
};
3536
}
3637

@@ -78,6 +79,156 @@ describe("applyCommandConfig", () => {
7879
loadGlobalAgentsSkillsSpy.mockRestore();
7980
});
8081

82+
test("includes builtin skills in command config", async () => {
83+
// given
84+
const config: Record<string, unknown> = { command: {} };
85+
86+
// when
87+
await applyCommandConfig({
88+
config,
89+
pluginConfig: createPluginConfig(),
90+
ctx: { directory: "/tmp" },
91+
pluginComponents: createPluginComponents(),
92+
});
93+
94+
// then
95+
const commandConfig = config.command as Record<string, { description?: string; template?: string }>;
96+
expect(commandConfig["init-deep"]?.description).toContain("hierarchical AGENTS.md");
97+
expect(commandConfig["init-deep"]?.template).toContain("Generate hierarchical AGENTS.md files");
98+
});
99+
100+
test("excludes disabled builtin skills from command config", async () => {
101+
// given
102+
const config: Record<string, unknown> = { command: {} };
103+
104+
// when
105+
await applyCommandConfig({
106+
config,
107+
pluginConfig: createPluginConfig({ disabled_skills: ["init-deep"] }),
108+
ctx: { directory: "/tmp" },
109+
pluginComponents: createPluginComponents(),
110+
});
111+
112+
// then
113+
const commandConfig = config.command as Record<string, unknown>;
114+
expect(commandConfig["init-deep"]).toBeUndefined();
115+
});
116+
117+
test("keeps builtin command precedence over same-name builtin skills", async () => {
118+
// given
119+
loadBuiltinCommandsSpy.mockReturnValue({
120+
"remove-ai-slops": {
121+
name: "remove-ai-slops",
122+
description: "Builtin command wins",
123+
template: "command template",
124+
},
125+
});
126+
const config: Record<string, unknown> = { command: {} };
127+
128+
// when
129+
await applyCommandConfig({
130+
config,
131+
pluginConfig: createPluginConfig(),
132+
ctx: { directory: "/tmp" },
133+
pluginComponents: createPluginComponents(),
134+
});
135+
136+
// then
137+
const commandConfig = config.command as Record<string, { description?: string; template?: string }>;
138+
expect(commandConfig["remove-ai-slops"]?.description).toBe("Builtin command wins");
139+
expect(commandConfig["remove-ai-slops"]?.template).toBe("command template");
140+
});
141+
142+
test("allows higher-precedence project skills to override builtin skill commands", async () => {
143+
// given
144+
loadProjectSkillsSpy.mockResolvedValue({
145+
"init-deep": {
146+
description: "Project init-deep skill",
147+
template: "project template",
148+
},
149+
});
150+
const config: Record<string, unknown> = { command: {} };
151+
152+
// when
153+
await applyCommandConfig({
154+
config,
155+
pluginConfig: createPluginConfig(),
156+
ctx: { directory: "/tmp" },
157+
pluginComponents: createPluginComponents(),
158+
});
159+
160+
// then
161+
const commandConfig = config.command as Record<string, { description?: string; template?: string }>;
162+
expect(commandConfig["init-deep"]?.description).toBe("Project init-deep skill");
163+
expect(commandConfig["init-deep"]?.template).toBe("project template");
164+
});
165+
166+
test("uses browser provider gating for builtin skill commands", async () => {
167+
// given
168+
const defaultConfig: Record<string, unknown> = { command: {} };
169+
const agentBrowserConfig: Record<string, unknown> = { command: {} };
170+
171+
// when
172+
await applyCommandConfig({
173+
config: defaultConfig,
174+
pluginConfig: createPluginConfig(),
175+
ctx: { directory: "/tmp" },
176+
pluginComponents: createPluginComponents(),
177+
});
178+
await applyCommandConfig({
179+
config: agentBrowserConfig,
180+
pluginConfig: createPluginConfig({ browser_automation_engine: { provider: "agent-browser" } }),
181+
ctx: { directory: "/tmp" },
182+
pluginComponents: createPluginComponents(),
183+
});
184+
185+
// then
186+
const defaultCommands = defaultConfig.command as Record<string, unknown>;
187+
const agentBrowserCommands = agentBrowserConfig.command as Record<string, unknown>;
188+
expect(defaultCommands.playwright).toBeDefined();
189+
expect(defaultCommands["agent-browser"]).toBeUndefined();
190+
expect(agentBrowserCommands["agent-browser"]).toBeDefined();
191+
expect(agentBrowserCommands.playwright).toBeUndefined();
192+
});
193+
194+
test("uses team-mode gating for builtin skill commands", async () => {
195+
// given
196+
const defaultConfig: Record<string, unknown> = { command: {} };
197+
const teamModeConfig: Record<string, unknown> = { command: {} };
198+
const disabledTeamModeConfig: Record<string, unknown> = { command: {} };
199+
200+
// when
201+
await applyCommandConfig({
202+
config: defaultConfig,
203+
pluginConfig: createPluginConfig(),
204+
ctx: { directory: "/tmp" },
205+
pluginComponents: createPluginComponents(),
206+
});
207+
await applyCommandConfig({
208+
config: teamModeConfig,
209+
pluginConfig: createPluginConfig({ team_mode: { enabled: true } }),
210+
ctx: { directory: "/tmp" },
211+
pluginComponents: createPluginComponents(),
212+
});
213+
await applyCommandConfig({
214+
config: disabledTeamModeConfig,
215+
pluginConfig: createPluginConfig({
216+
disabled_skills: ["team-mode"],
217+
team_mode: { enabled: true },
218+
}),
219+
ctx: { directory: "/tmp" },
220+
pluginComponents: createPluginComponents(),
221+
});
222+
223+
// then
224+
const defaultCommands = defaultConfig.command as Record<string, unknown>;
225+
const teamModeCommands = teamModeConfig.command as Record<string, unknown>;
226+
const disabledTeamModeCommands = disabledTeamModeConfig.command as Record<string, unknown>;
227+
expect(defaultCommands["team-mode"]).toBeUndefined();
228+
expect(teamModeCommands["team-mode"]).toBeDefined();
229+
expect(disabledTeamModeCommands["team-mode"]).toBeUndefined();
230+
});
231+
81232
test("includes .agents skills in command config", async () => {
82233
// given
83234
loadProjectAgentsSkillsSpy.mockResolvedValue({

packages/omo-opencode/src/plugin-handlers/command-config-handler.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,34 @@
11
import type { OhMyOpenCodeConfig } from "../config";
2+
import { loadBuiltinCommands } from "../features/builtin-commands";
3+
import { createBuiltinSkills } from "../features/builtin-skills";
24
import {
3-
getAgentConfigKey,
4-
getAgentListDisplayName,
5-
} from "../shared/agent-display-names";
6-
import {
7-
loadUserCommands,
8-
loadProjectCommands,
95
loadOpencodeGlobalCommands,
106
loadOpencodeProjectCommands,
7+
loadProjectCommands,
8+
loadUserCommands,
119
} from "../features/claude-code-command-loader";
12-
import { loadBuiltinCommands } from "../features/builtin-commands";
1310
import {
1411
discoverConfigSourceSkills,
1512
loadGlobalAgentsSkills,
16-
loadProjectAgentsSkills,
17-
loadUserSkills,
18-
loadProjectSkills,
1913
loadOpencodeGlobalSkills,
2014
loadOpencodeProjectSkills,
15+
loadProjectAgentsSkills,
16+
loadProjectSkills,
17+
loadUserSkills,
2118
skillsToCommandDefinitionRecord,
2219
} from "../features/opencode-skill-loader";
20+
import { builtinToLoadedSkill } from "../features/opencode-skill-loader/merger/builtin-skill-converter";
2321
import {
2422
detectExternalSkillPlugin,
2523
getSkillPluginConflictWarning,
2624
log,
2725
} from "../shared";
28-
import type { PluginComponents } from "./plugin-components-loader";
26+
import {
27+
getAgentConfigKey,
28+
getAgentListDisplayName,
29+
} from "../shared/agent-display-names";
2930
import { adaptHostSkillConfig } from "../shared/host-skill-config";
31+
import type { PluginComponents } from "./plugin-components-loader";
3032

3133
export async function applyCommandConfig(params: {
3234
config: Record<string, unknown>;
@@ -38,6 +40,13 @@ export async function applyCommandConfig(params: {
3840
useRegisteredAgents: true,
3941
teamModeEnabled: params.pluginConfig.team_mode?.enabled ?? false,
4042
});
43+
const builtinSkillCommands = skillsToCommandDefinitionRecord(
44+
createBuiltinSkills({
45+
browserProvider: params.pluginConfig.browser_automation_engine?.provider ?? "playwright",
46+
disabledSkills: new Set(params.pluginConfig.disabled_skills ?? []),
47+
teamModeEnabled: params.pluginConfig.team_mode?.enabled ?? false,
48+
}).map(builtinToLoadedSkill),
49+
);
4150
const systemCommands = (params.config.command as Record<string, unknown>) ?? {};
4251

4352
const includeClaudeCommands = params.pluginConfig.claude_code?.commands ?? true;
@@ -84,6 +93,7 @@ export async function applyCommandConfig(params: {
8493
]);
8594

8695
params.config.command = {
96+
...builtinSkillCommands,
8797
...builtinCommands,
8898
...skillsToCommandDefinitionRecord(configSourceSkills),
8999
...skillsToCommandDefinitionRecord(hostConfigSkills),

0 commit comments

Comments
 (0)