@hiai-gg/hiai-opencode 0.1.3 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +14 -18
- package/AGENTS.md +75 -23
- package/ARCHITECTURE.md +11 -14
- package/LICENSE.md +1 -0
- package/README.md +177 -94
- package/assets/cli/hiai-opencode.mjs +276 -0
- package/assets/mcp/playwright.mjs +7 -0
- package/config/hiai-opencode.schema.json +113 -1
- package/dist/config/defaults.d.ts +0 -3
- package/dist/config/index.d.ts +0 -1
- package/dist/config/platform-schema.d.ts +70 -0
- package/dist/config/schema/agent-overrides.d.ts +256 -0
- package/dist/config/schema/categories.d.ts +2 -2
- package/dist/config/schema/commands.d.ts +1 -0
- package/dist/config/schema/index.d.ts +2 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +267 -0
- package/dist/config/schema/skill-discovery.d.ts +11 -0
- package/dist/config/types.d.ts +12 -1
- package/dist/features/builtin-commands/templates/mcp-status.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/opencode-skill-loader/loader.d.ts +2 -0
- package/dist/index.js +692 -541
- package/dist/plugin/skill-discovery-config.d.ts +4 -0
- package/dist/shared/startup-diagnostics.d.ts +6 -0
- package/hiai-opencode.json +191 -35
- package/package.json +4 -1
- package/src/agents/AGENTS.md +3 -4
- package/src/config/defaults.ts +60 -81
- package/src/config/index.ts +0 -1
- package/src/config/platform-schema.ts +17 -2
- package/src/config/schema/agent-overrides.ts +2 -0
- package/src/config/schema/commands.ts +1 -0
- package/src/config/schema/fast-apply.ts +4 -4
- package/src/config/schema/index.ts +2 -0
- package/src/config/schema/oh-my-opencode-config.ts +3 -0
- package/src/config/schema/skill-discovery.ts +25 -0
- package/src/config/types.ts +16 -0
- package/src/features/builtin-commands/commands.ts +7 -0
- package/src/features/builtin-commands/templates/mcp-status.ts +36 -0
- package/src/features/builtin-commands/types.ts +1 -1
- package/src/features/builtin-skills/skills/playwright.ts +24 -2
- package/src/features/opencode-skill-loader/loader.ts +11 -0
- package/src/index.ts +14 -13
- package/src/plugin/hooks/create-tool-guard-hooks.ts +1 -1
- package/src/plugin/skill-context.ts +31 -13
- package/src/plugin/skill-discovery-config.ts +32 -0
- package/src/plugin-handlers/agent-config-handler.ts +20 -13
- package/src/plugin-handlers/command-config-handler.ts +22 -12
- package/src/shared/migration/agent-names.ts +5 -5
- package/src/shared/startup-diagnostics.ts +77 -0
- package/src/config/models.ts +0 -32
package/src/config/types.ts
CHANGED
|
@@ -32,6 +32,8 @@ export const LEGACY_AGENT_ALIAS_NAMES = [
|
|
|
32
32
|
"zoe",
|
|
33
33
|
"build",
|
|
34
34
|
"pre-plan",
|
|
35
|
+
"manager",
|
|
36
|
+
"vision",
|
|
35
37
|
"logician",
|
|
36
38
|
"librarian",
|
|
37
39
|
"explore",
|
|
@@ -54,6 +56,8 @@ export const LEGACY_AGENT_ALIAS_TO_CANONICAL: Record<
|
|
|
54
56
|
zoe: "bob",
|
|
55
57
|
build: "bob",
|
|
56
58
|
"pre-plan": "strategist",
|
|
59
|
+
manager: "platform-manager",
|
|
60
|
+
vision: "multimodal",
|
|
57
61
|
logician: "strategist",
|
|
58
62
|
librarian: "researcher",
|
|
59
63
|
explore: "researcher",
|
|
@@ -135,6 +139,16 @@ export interface SkillsConfig {
|
|
|
135
139
|
disabled?: string[];
|
|
136
140
|
}
|
|
137
141
|
|
|
142
|
+
export interface SkillDiscoveryConfig {
|
|
143
|
+
config_sources?: boolean;
|
|
144
|
+
project_opencode?: boolean;
|
|
145
|
+
global_opencode?: boolean;
|
|
146
|
+
project_claude?: boolean;
|
|
147
|
+
global_claude?: boolean;
|
|
148
|
+
project_agents?: boolean;
|
|
149
|
+
global_agents?: boolean;
|
|
150
|
+
}
|
|
151
|
+
|
|
138
152
|
export interface PermissionsConfig {
|
|
139
153
|
read?: Record<string, string>;
|
|
140
154
|
edit?: Record<string, string>;
|
|
@@ -148,6 +162,7 @@ export interface AuthKeys {
|
|
|
148
162
|
openrouter?: string;
|
|
149
163
|
stitch?: string;
|
|
150
164
|
firecrawl?: string;
|
|
165
|
+
context7?: string;
|
|
151
166
|
}
|
|
152
167
|
|
|
153
168
|
export interface OllamaConfig {
|
|
@@ -168,6 +183,7 @@ export interface HiaiOpencodeConfig {
|
|
|
168
183
|
lsp?: Record<string, LspServerConfig>;
|
|
169
184
|
subtask2?: Subtask2Config;
|
|
170
185
|
skills?: SkillsConfig;
|
|
186
|
+
skill_discovery?: SkillDiscoveryConfig;
|
|
171
187
|
permissions?: PermissionsConfig;
|
|
172
188
|
auth?: AuthKeys;
|
|
173
189
|
ollama?: OllamaConfig;
|
|
@@ -8,6 +8,7 @@ import { REFACTOR_TEMPLATE } from "./templates/refactor"
|
|
|
8
8
|
import { START_WORK_TEMPLATE } from "./templates/start-work"
|
|
9
9
|
import { HANDOFF_TEMPLATE } from "./templates/handoff"
|
|
10
10
|
import { REMOVE_AI_SLOPS_TEMPLATE } from "./templates/remove-ai-slops"
|
|
11
|
+
import { MCP_STATUS_TEMPLATE } from "./templates/mcp-status"
|
|
11
12
|
|
|
12
13
|
interface LoadBuiltinCommandsOptions {
|
|
13
14
|
useRegisteredAgents?: boolean
|
|
@@ -121,6 +122,12 @@ $ARGUMENTS
|
|
|
121
122
|
</user-request>`,
|
|
122
123
|
argumentHint: "[goal]",
|
|
123
124
|
},
|
|
125
|
+
"mcp-status": {
|
|
126
|
+
description: "(builtin) Show hiai-opencode MCP server status, missing keys, and local runtime availability",
|
|
127
|
+
template: `<command-instruction>
|
|
128
|
+
${MCP_STATUS_TEMPLATE}
|
|
129
|
+
</command-instruction>`,
|
|
130
|
+
},
|
|
124
131
|
}
|
|
125
132
|
}
|
|
126
133
|
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export const MCP_STATUS_TEMPLATE = `# MCP Status Command
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Use /mcp-status to show the effective hiai-opencode MCP setup without relying on OpenCode's mcp list output.
|
|
6
|
+
|
|
7
|
+
## Execute
|
|
8
|
+
|
|
9
|
+
Run:
|
|
10
|
+
|
|
11
|
+
\`\`\`bash
|
|
12
|
+
hiai-opencode mcp-status
|
|
13
|
+
\`\`\`
|
|
14
|
+
|
|
15
|
+
If the binary is not on PATH, try the package-local fallback:
|
|
16
|
+
|
|
17
|
+
\`\`\`bash
|
|
18
|
+
node ./node_modules/@hiai-gg/hiai-opencode/assets/cli/hiai-opencode.mjs mcp-status
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
## Report
|
|
22
|
+
|
|
23
|
+
Summarize the output in a compact status table:
|
|
24
|
+
|
|
25
|
+
- MCP server name
|
|
26
|
+
- status: ok, warning, error, disabled
|
|
27
|
+
- cause or next action
|
|
28
|
+
|
|
29
|
+
Rules:
|
|
30
|
+
|
|
31
|
+
- Do not print API key values.
|
|
32
|
+
- If a key is missing, name the env var only.
|
|
33
|
+
- If a runtime is missing, give the exact install hint from the command output or the shortest safe next command.
|
|
34
|
+
- Do not edit config unless the user explicitly asks.
|
|
35
|
+
- Do not run package installs unless the user explicitly asks.
|
|
36
|
+
`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { CommandDefinition } from "../claude-code-command-loader"
|
|
2
2
|
|
|
3
|
-
export type BuiltinCommandName = "init-deep" | "ralph-loop" | "cancel-ralph" | "ulw-loop" | "refactor" | "start-work" | "stop-continuation" | "handoff" | "remove-ai-slops"
|
|
3
|
+
export type BuiltinCommandName = "init-deep" | "ralph-loop" | "cancel-ralph" | "ulw-loop" | "refactor" | "start-work" | "stop-continuation" | "handoff" | "remove-ai-slops" | "mcp-status"
|
|
4
4
|
|
|
5
5
|
export interface BuiltinCommandConfig {
|
|
6
6
|
disabled_commands?: BuiltinCommandName[]
|
|
@@ -5,11 +5,33 @@ export const playwrightSkill: BuiltinSkill = {
|
|
|
5
5
|
description: "MUST USE for any browser-related tasks. Browser automation via Playwright MCP - verification, browsing, information gathering, web scraping, testing, screenshots, and all browser interactions.",
|
|
6
6
|
template: `# Playwright Browser Automation
|
|
7
7
|
|
|
8
|
-
This skill provides browser automation capabilities via the Playwright MCP server
|
|
8
|
+
This skill provides browser automation capabilities via the Playwright MCP server.
|
|
9
|
+
|
|
10
|
+
## Required workflow
|
|
11
|
+
|
|
12
|
+
1. Load this skill before calling \`skill_mcp\`.
|
|
13
|
+
2. Use \`skill_mcp\` with \`mcp_name="playwright"\` for browser navigation, interaction, screenshots, and visual verification.
|
|
14
|
+
3. If the host says \`MCP server "playwright" not found\`, do not conclude that Playwright is impossible. First report that the skill was not loaded or the Playwright MCP server was not registered in this session.
|
|
15
|
+
4. If Chromium starts but fails with missing Linux libraries such as \`libnspr4\`, \`libnss3\`, \`libatk-bridge\`, \`libgtk-3\`, or similar, distinguish browser OS dependencies from MCP availability.
|
|
16
|
+
|
|
17
|
+
## Linux dependency fallback
|
|
18
|
+
|
|
19
|
+
Playwright has two dependency layers:
|
|
20
|
+
|
|
21
|
+
- Browser binary: installable without sudo with \`npx playwright install chromium\` or by setting \`HIAI_PLAYWRIGHT_INSTALL_BROWSERS=1\` before OpenCode starts.
|
|
22
|
+
- System libraries: on minimal Linux images these usually require admin rights via \`sudo npx playwright install-deps chromium\` or OS package manager equivalents.
|
|
23
|
+
|
|
24
|
+
If sudo is unavailable, try these alternatives before falling back to curl-only checks:
|
|
25
|
+
|
|
26
|
+
- Use an already installed Chrome/Chromium/Edge by adding Playwright MCP args in \`hiai-opencode.json\`, for example \`--browser chrome\` or \`--browser msedge\`.
|
|
27
|
+
- Use a remote/browser service or CDP-backed browser when available.
|
|
28
|
+
- Switch the browser automation provider to \`agent-browser\` or \`playwright-cli\` if the workspace has those tools installed.
|
|
29
|
+
|
|
30
|
+
Only use \`curl\` as a final degraded check. Clearly say that HTTP checks do not replace interactive browser verification.`,
|
|
9
31
|
mcpConfig: {
|
|
10
32
|
playwright: {
|
|
11
33
|
command: "npx",
|
|
12
|
-
args: ["@playwright/mcp@latest"],
|
|
34
|
+
args: ["-y", "@playwright/mcp@latest"],
|
|
13
35
|
},
|
|
14
36
|
},
|
|
15
37
|
}
|
|
@@ -62,6 +62,12 @@ export async function loadGlobalAgentsSkills(): Promise<Record<string, CommandDe
|
|
|
62
62
|
return skillsToCommandDefinitionRecord(skills)
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
+
export async function loadManagedPluginSkills(): Promise<Record<string, CommandDefinition>> {
|
|
66
|
+
const skillsDir = join(getOpenCodeConfigDir({ binary: "opencode" }), ".hiai", "skills", "plugin")
|
|
67
|
+
const skills = await loadSkillsFromDir({ skillsDir, scope: "builtin" })
|
|
68
|
+
return skillsToCommandDefinitionRecord(skills)
|
|
69
|
+
}
|
|
70
|
+
|
|
65
71
|
export interface DiscoverSkillsOptions {
|
|
66
72
|
includeClaudeCodePaths?: boolean
|
|
67
73
|
directory?: string
|
|
@@ -170,3 +176,8 @@ export async function discoverGlobalAgentsSkills(): Promise<LoadedSkill[]> {
|
|
|
170
176
|
const agentsGlobalDir = join(getAgentsConfigDir(), "skills")
|
|
171
177
|
return loadSkillsFromDir({ skillsDir: agentsGlobalDir, scope: "user" })
|
|
172
178
|
}
|
|
179
|
+
|
|
180
|
+
export async function discoverManagedPluginSkills(): Promise<LoadedSkill[]> {
|
|
181
|
+
const skillsDir = join(getOpenCodeConfigDir({ binary: "opencode" }), ".hiai", "skills", "plugin")
|
|
182
|
+
return loadSkillsFromDir({ skillsDir, scope: "builtin" })
|
|
183
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -18,6 +18,7 @@ import { injectServerAuthIntoClient, log } from "./shared"
|
|
|
18
18
|
import { hydratePluginConfigWithPlatformDefaults } from "./shared/runtime-plugin-config"
|
|
19
19
|
import { detectExternalSkillPlugin, getSkillPluginConflictWarning } from "./shared/external-plugin-detector"
|
|
20
20
|
import { PLUGIN_NAME } from "./shared/plugin-identity"
|
|
21
|
+
import { warnIfListPluginEntry, warnMissingRequiredMcpEnv } from "./shared/startup-diagnostics"
|
|
21
22
|
import { startBackgroundCheck as startTmuxCheck } from "./tools/interactive-bash"
|
|
22
23
|
import { lspManager } from "./tools/lsp/client"
|
|
23
24
|
|
|
@@ -69,6 +70,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
69
70
|
log("[HiaiOpenCodePlugin] ENTRY - plugin loading", {
|
|
70
71
|
directory: ctx.directory,
|
|
71
72
|
})
|
|
73
|
+
warnIfListPluginEntry(ctx.directory)
|
|
72
74
|
|
|
73
75
|
const skillPluginCheck = detectExternalSkillPlugin(ctx.directory)
|
|
74
76
|
if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
|
|
@@ -83,6 +85,10 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
83
85
|
loadPluginConfig(ctx.directory, ctx),
|
|
84
86
|
internalConfig,
|
|
85
87
|
)
|
|
88
|
+
warnMissingRequiredMcpEnv({
|
|
89
|
+
pluginConfig,
|
|
90
|
+
platformConfig: internalConfig,
|
|
91
|
+
})
|
|
86
92
|
|
|
87
93
|
materializeBuiltinSkills(
|
|
88
94
|
createBuiltinSkills({
|
|
@@ -220,29 +226,24 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
220
226
|
auth: {
|
|
221
227
|
provider: "hiai-opencode",
|
|
222
228
|
methods: [
|
|
223
|
-
{ type: "api" as const, label: "Google API key" },
|
|
224
|
-
{ type: "api" as const, label: "OpenAI API key" },
|
|
225
|
-
{ type: "api" as const, label: "OpenRouter API key" },
|
|
229
|
+
{ type: "api" as const, label: "Google Search API key" },
|
|
226
230
|
],
|
|
227
231
|
loader: async (getAuth: any) => {
|
|
228
232
|
const authData = await getAuth();
|
|
229
233
|
const { registerGetAuth, GOOGLE_PROVIDER_ID, OPENAI_PROVIDER_ID, OPENROUTER_PROVIDER_ID } = await import("./internals/plugins/websearch-cited/index");
|
|
230
234
|
|
|
231
|
-
|
|
232
|
-
const getKey = (label: string, configKey?: string) => {
|
|
233
|
-
const fromAuth = authData[label];
|
|
234
|
-
if (fromAuth) return fromAuth;
|
|
235
|
+
const getConfiguredKey = (configKey?: string) => {
|
|
235
236
|
if (configKey) return resolveEnvVars(configKey);
|
|
236
237
|
return undefined;
|
|
237
238
|
};
|
|
238
239
|
|
|
239
|
-
const googleKey =
|
|
240
|
-
const openaiKey =
|
|
241
|
-
const openRouterKey =
|
|
240
|
+
const googleKey = authData["Google Search API key"] || getConfiguredKey(internalConfig.auth?.googleSearch);
|
|
241
|
+
const openaiKey = getConfiguredKey(internalConfig.auth?.openai);
|
|
242
|
+
const openRouterKey = getConfiguredKey(internalConfig.auth?.openrouter);
|
|
242
243
|
|
|
243
|
-
if (googleKey) registerGetAuth(GOOGLE_PROVIDER_ID, () => Promise.resolve(googleKey));
|
|
244
|
-
if (openaiKey) registerGetAuth(OPENAI_PROVIDER_ID, () => Promise.resolve(openaiKey));
|
|
245
|
-
if (openRouterKey) registerGetAuth(OPENROUTER_PROVIDER_ID, () => Promise.resolve(openRouterKey));
|
|
244
|
+
if (googleKey) registerGetAuth(GOOGLE_PROVIDER_ID, () => Promise.resolve({ type: "api", key: googleKey }));
|
|
245
|
+
if (openaiKey) registerGetAuth(OPENAI_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openaiKey }));
|
|
246
|
+
if (openRouterKey) registerGetAuth(OPENROUTER_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openRouterKey }));
|
|
246
247
|
|
|
247
248
|
return {};
|
|
248
249
|
},
|
|
@@ -136,7 +136,7 @@ export function createToolGuardHooks(args: {
|
|
|
136
136
|
: null
|
|
137
137
|
|
|
138
138
|
const fastApply = isHookEnabled("fast-apply")
|
|
139
|
-
? safeHook("fast-apply", () => createFastApplyHook(pluginConfig.fast_apply ?? { enabled: false, ollama_url: "
|
|
139
|
+
? safeHook("fast-apply", () => createFastApplyHook(pluginConfig.fast_apply ?? { enabled: false, ollama_url: "", model: "", timeout: 30000 }))
|
|
140
140
|
: null
|
|
141
141
|
|
|
142
142
|
return {
|
|
@@ -8,6 +8,7 @@ import type {
|
|
|
8
8
|
|
|
9
9
|
import {
|
|
10
10
|
discoverConfigSourceSkills,
|
|
11
|
+
discoverManagedPluginSkills,
|
|
11
12
|
discoverUserClaudeSkills,
|
|
12
13
|
discoverProjectClaudeSkills,
|
|
13
14
|
discoverOpencodeGlobalSkills,
|
|
@@ -18,6 +19,7 @@ import {
|
|
|
18
19
|
} from "../features/opencode-skill-loader"
|
|
19
20
|
import { createBuiltinSkills } from "../features/builtin-skills"
|
|
20
21
|
import { getSystemMcpServerNames } from "../features/claude-code-mcp-loader"
|
|
22
|
+
import { resolveSkillDiscoveryConfig } from "./skill-discovery-config"
|
|
21
23
|
|
|
22
24
|
export type SkillContext = {
|
|
23
25
|
mergedSkills: LoadedSkill[]
|
|
@@ -71,21 +73,37 @@ export async function createSkillContext(args: {
|
|
|
71
73
|
return true
|
|
72
74
|
})
|
|
73
75
|
|
|
74
|
-
const
|
|
75
|
-
const [
|
|
76
|
+
const discovery = resolveSkillDiscoveryConfig(pluginConfig)
|
|
77
|
+
const [
|
|
78
|
+
managedPluginSkills,
|
|
79
|
+
configSourceSkills,
|
|
80
|
+
userSkills,
|
|
81
|
+
globalSkills,
|
|
82
|
+
projectSkills,
|
|
83
|
+
opencodeProjectSkills,
|
|
84
|
+
agentsProjectSkills,
|
|
85
|
+
agentsGlobalSkills,
|
|
86
|
+
] =
|
|
76
87
|
await Promise.all([
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
88
|
+
discoverManagedPluginSkills(),
|
|
89
|
+
discovery.config_sources
|
|
90
|
+
? discoverConfigSourceSkills({
|
|
91
|
+
config: pluginConfig.skills,
|
|
92
|
+
configDir: directory,
|
|
93
|
+
})
|
|
94
|
+
: Promise.resolve([]),
|
|
95
|
+
discovery.global_claude ? discoverUserClaudeSkills() : Promise.resolve([]),
|
|
96
|
+
discovery.global_opencode ? discoverOpencodeGlobalSkills() : Promise.resolve([]),
|
|
97
|
+
discovery.project_claude ? discoverProjectClaudeSkills(directory) : Promise.resolve([]),
|
|
98
|
+
discovery.project_opencode ? discoverOpencodeProjectSkills(directory) : Promise.resolve([]),
|
|
99
|
+
discovery.project_agents ? discoverProjectAgentsSkills(directory) : Promise.resolve([]),
|
|
100
|
+
discovery.global_agents ? discoverGlobalAgentsSkills() : Promise.resolve([]),
|
|
87
101
|
])
|
|
88
102
|
|
|
103
|
+
const filteredManagedPluginSkills = filterProviderGatedSkills(
|
|
104
|
+
managedPluginSkills,
|
|
105
|
+
browserProvider,
|
|
106
|
+
)
|
|
89
107
|
const filteredConfigSourceSkills = filterProviderGatedSkills(
|
|
90
108
|
configSourceSkills,
|
|
91
109
|
browserProvider,
|
|
@@ -109,7 +127,7 @@ export async function createSkillContext(args: {
|
|
|
109
127
|
const mergedSkills = mergeSkills(
|
|
110
128
|
builtinSkills,
|
|
111
129
|
pluginConfig.skills,
|
|
112
|
-
filteredConfigSourceSkills,
|
|
130
|
+
[...filteredManagedPluginSkills, ...filteredConfigSourceSkills],
|
|
113
131
|
[...filteredUserSkills, ...filteredAgentsGlobalSkills],
|
|
114
132
|
filteredGlobalSkills,
|
|
115
133
|
[...filteredProjectSkills, ...filteredAgentsProjectSkills],
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { HiaiOpenCodeConfig } from "../config"
|
|
2
|
+
import type { SkillDiscoveryConfig } from "../config/schema"
|
|
3
|
+
|
|
4
|
+
export type ResolvedSkillDiscoveryConfig = Required<SkillDiscoveryConfig>
|
|
5
|
+
|
|
6
|
+
const DEFAULT_SKILL_DISCOVERY: ResolvedSkillDiscoveryConfig = {
|
|
7
|
+
config_sources: true,
|
|
8
|
+
project_opencode: true,
|
|
9
|
+
global_opencode: false,
|
|
10
|
+
project_claude: false,
|
|
11
|
+
global_claude: false,
|
|
12
|
+
project_agents: false,
|
|
13
|
+
global_agents: false,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function resolveSkillDiscoveryConfig(
|
|
17
|
+
pluginConfig: HiaiOpenCodeConfig,
|
|
18
|
+
): ResolvedSkillDiscoveryConfig {
|
|
19
|
+
const resolved: ResolvedSkillDiscoveryConfig = {
|
|
20
|
+
...DEFAULT_SKILL_DISCOVERY,
|
|
21
|
+
...(pluginConfig.skill_discovery ?? {}),
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Compatibility switch: historically this only controlled Claude-style skills.
|
|
25
|
+
// Keep that meaning, but make it stronger for those two sources.
|
|
26
|
+
if (pluginConfig.claude_code?.skills === false) {
|
|
27
|
+
resolved.project_claude = false
|
|
28
|
+
resolved.global_claude = false
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return resolved
|
|
32
|
+
}
|
|
@@ -7,6 +7,7 @@ import { AGENT_NAME_MAP } from "../shared/migration";
|
|
|
7
7
|
import { registerAgentName } from "../features/claude-code-session-state";
|
|
8
8
|
import {
|
|
9
9
|
discoverConfigSourceSkills,
|
|
10
|
+
discoverManagedPluginSkills,
|
|
10
11
|
deduplicateSkillsByName,
|
|
11
12
|
discoverGlobalAgentsSkills,
|
|
12
13
|
discoverOpencodeGlobalSkills,
|
|
@@ -31,6 +32,7 @@ import {
|
|
|
31
32
|
filterProtectedAgentOverrides,
|
|
32
33
|
} from "./agent-override-protection";
|
|
33
34
|
import { buildStrategistAgentConfig } from "./strategist-agent-config-builder";
|
|
35
|
+
import { resolveSkillDiscoveryConfig } from "../plugin/skill-discovery-config";
|
|
34
36
|
|
|
35
37
|
type AgentConfigRecord = Record<string, Record<string, unknown> | undefined> & {
|
|
36
38
|
build?: Record<string, unknown>;
|
|
@@ -130,8 +132,9 @@ export async function applyAgentConfig(params: {
|
|
|
130
132
|
},
|
|
131
133
|
) as typeof params.pluginConfig.disabled_agents;
|
|
132
134
|
|
|
133
|
-
const
|
|
135
|
+
const discovery = resolveSkillDiscoveryConfig(params.pluginConfig);
|
|
134
136
|
const [
|
|
137
|
+
discoveredManagedPluginSkills,
|
|
135
138
|
discoveredConfigSourceSkills,
|
|
136
139
|
discoveredUserSkills,
|
|
137
140
|
discoveredProjectSkills,
|
|
@@ -140,23 +143,27 @@ export async function applyAgentConfig(params: {
|
|
|
140
143
|
discoveredOpencodeProjectSkills,
|
|
141
144
|
discoveredGlobalAgentsSkills,
|
|
142
145
|
] = await Promise.all([
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
146
|
+
discoverManagedPluginSkills(),
|
|
147
|
+
discovery.config_sources
|
|
148
|
+
? discoverConfigSourceSkills({
|
|
149
|
+
config: params.pluginConfig.skills,
|
|
150
|
+
configDir: params.ctx.directory,
|
|
151
|
+
})
|
|
152
|
+
: Promise.resolve([]),
|
|
153
|
+
discovery.global_claude ? discoverUserClaudeSkills() : Promise.resolve([]),
|
|
154
|
+
discovery.project_claude
|
|
155
|
+
? discoverProjectClaudeSkills(params.ctx.directory)
|
|
156
|
+
: Promise.resolve([]),
|
|
157
|
+
discovery.project_agents
|
|
152
158
|
? discoverProjectAgentsSkills(params.ctx.directory)
|
|
153
159
|
: Promise.resolve([]),
|
|
154
|
-
discoverOpencodeGlobalSkills(),
|
|
155
|
-
discoverOpencodeProjectSkills(params.ctx.directory),
|
|
156
|
-
|
|
160
|
+
discovery.global_opencode ? discoverOpencodeGlobalSkills() : Promise.resolve([]),
|
|
161
|
+
discovery.project_opencode ? discoverOpencodeProjectSkills(params.ctx.directory) : Promise.resolve([]),
|
|
162
|
+
discovery.global_agents ? discoverGlobalAgentsSkills() : Promise.resolve([]),
|
|
157
163
|
]);
|
|
158
164
|
|
|
159
165
|
const allDiscoveredSkills = [
|
|
166
|
+
...discoveredManagedPluginSkills,
|
|
160
167
|
...discoveredConfigSourceSkills,
|
|
161
168
|
...discoveredOpencodeProjectSkills,
|
|
162
169
|
...discoveredProjectSkills,
|
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
import { loadBuiltinCommands } from "../features/builtin-commands";
|
|
13
13
|
import {
|
|
14
14
|
discoverConfigSourceSkills,
|
|
15
|
+
loadManagedPluginSkills,
|
|
15
16
|
loadGlobalAgentsSkills,
|
|
16
17
|
loadProjectAgentsSkills,
|
|
17
18
|
loadUserSkills,
|
|
@@ -26,6 +27,7 @@ import {
|
|
|
26
27
|
log,
|
|
27
28
|
} from "../shared";
|
|
28
29
|
import type { PluginComponents } from "./plugin-components-loader";
|
|
30
|
+
import { resolveSkillDiscoveryConfig } from "../plugin/skill-discovery-config";
|
|
29
31
|
|
|
30
32
|
export async function applyCommandConfig(params: {
|
|
31
33
|
config: Record<string, unknown>;
|
|
@@ -39,10 +41,13 @@ export async function applyCommandConfig(params: {
|
|
|
39
41
|
const systemCommands = (params.config.command as Record<string, unknown>) ?? {};
|
|
40
42
|
|
|
41
43
|
const includeClaudeCommands = params.pluginConfig.claude_code?.commands ?? true;
|
|
42
|
-
const
|
|
44
|
+
const discovery = resolveSkillDiscoveryConfig(params.pluginConfig);
|
|
43
45
|
|
|
44
46
|
const externalSkillPlugin = detectExternalSkillPlugin(params.ctx.directory);
|
|
45
|
-
if (
|
|
47
|
+
if (
|
|
48
|
+
(discovery.project_claude || discovery.global_claude || discovery.global_opencode) &&
|
|
49
|
+
externalSkillPlugin.detected
|
|
50
|
+
) {
|
|
46
51
|
log(getSkillPluginConflictWarning(externalSkillPlugin.pluginName!));
|
|
47
52
|
}
|
|
48
53
|
|
|
@@ -52,6 +57,7 @@ export async function applyCommandConfig(params: {
|
|
|
52
57
|
projectCommands,
|
|
53
58
|
opencodeGlobalCommands,
|
|
54
59
|
opencodeProjectCommands,
|
|
60
|
+
managedPluginSkills,
|
|
55
61
|
userSkills,
|
|
56
62
|
globalAgentsSkills,
|
|
57
63
|
projectSkills,
|
|
@@ -59,24 +65,28 @@ export async function applyCommandConfig(params: {
|
|
|
59
65
|
opencodeGlobalSkills,
|
|
60
66
|
opencodeProjectSkills,
|
|
61
67
|
] = await Promise.all([
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
68
|
+
discovery.config_sources
|
|
69
|
+
? discoverConfigSourceSkills({
|
|
70
|
+
config: params.pluginConfig.skills,
|
|
71
|
+
configDir: params.ctx.directory,
|
|
72
|
+
})
|
|
73
|
+
: Promise.resolve([]),
|
|
66
74
|
includeClaudeCommands ? loadUserCommands() : Promise.resolve({}),
|
|
67
75
|
includeClaudeCommands ? loadProjectCommands(params.ctx.directory) : Promise.resolve({}),
|
|
68
76
|
loadOpencodeGlobalCommands(),
|
|
69
77
|
loadOpencodeProjectCommands(params.ctx.directory),
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
78
|
+
loadManagedPluginSkills(),
|
|
79
|
+
discovery.global_claude ? loadUserSkills() : Promise.resolve({}),
|
|
80
|
+
discovery.global_agents ? loadGlobalAgentsSkills() : Promise.resolve({}),
|
|
81
|
+
discovery.project_claude ? loadProjectSkills(params.ctx.directory) : Promise.resolve({}),
|
|
82
|
+
discovery.project_agents ? loadProjectAgentsSkills(params.ctx.directory) : Promise.resolve({}),
|
|
83
|
+
discovery.global_opencode ? loadOpencodeGlobalSkills() : Promise.resolve({}),
|
|
84
|
+
discovery.project_opencode ? loadOpencodeProjectSkills(params.ctx.directory) : Promise.resolve({}),
|
|
76
85
|
]);
|
|
77
86
|
|
|
78
87
|
params.config.command = {
|
|
79
88
|
...builtinCommands,
|
|
89
|
+
...managedPluginSkills,
|
|
80
90
|
...skillsToCommandDefinitionRecord(configSourceSkills),
|
|
81
91
|
...userCommands,
|
|
82
92
|
...userSkills,
|
|
@@ -64,10 +64,10 @@ export const AGENT_NAME_MAP: Record<string, string> = {
|
|
|
64
64
|
// Designer
|
|
65
65
|
designer: "designer",
|
|
66
66
|
|
|
67
|
-
// Multimodal
|
|
68
|
-
ui: "
|
|
69
|
-
vision: "
|
|
70
|
-
multimodal: "
|
|
67
|
+
// Multimodal / Vision
|
|
68
|
+
ui: "multimodal",
|
|
69
|
+
vision: "multimodal",
|
|
70
|
+
multimodal: "multimodal",
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
export const BUILTIN_AGENT_NAMES = new Set([
|
|
@@ -77,7 +77,7 @@ export const BUILTIN_AGENT_NAMES = new Set([
|
|
|
77
77
|
"critic",
|
|
78
78
|
"designer",
|
|
79
79
|
"researcher",
|
|
80
|
-
"
|
|
80
|
+
"multimodal",
|
|
81
81
|
"platform-manager",
|
|
82
82
|
"guard",
|
|
83
83
|
])
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
2
|
+
import { join } from "node:path"
|
|
3
|
+
|
|
4
|
+
import type { HiaiOpenCodeConfig, HiaiOpencodeConfig } from "../config"
|
|
5
|
+
import { HIAI_MCP_REGISTRY } from "../mcp/registry"
|
|
6
|
+
import { parseJsoncSafe } from "./jsonc-parser"
|
|
7
|
+
import { getOpenCodeConfigPaths } from "./opencode-config-dir"
|
|
8
|
+
import { PLUGIN_NAME } from "./plugin-identity"
|
|
9
|
+
|
|
10
|
+
interface OpenCodeConfig {
|
|
11
|
+
plugin?: Array<string | [string, ...unknown[]]>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function readPlugins(configPath: string): string[] {
|
|
15
|
+
if (!existsSync(configPath)) return []
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const content = readFileSync(configPath, "utf-8")
|
|
19
|
+
const parsed = parseJsoncSafe<OpenCodeConfig>(content)
|
|
20
|
+
return (parsed.data?.plugin ?? [])
|
|
21
|
+
.map((entry) => typeof entry === "string" ? entry : Array.isArray(entry) ? entry[0] : "")
|
|
22
|
+
.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
|
|
23
|
+
} catch {
|
|
24
|
+
return []
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function warnIfListPluginEntry(directory: string): void {
|
|
29
|
+
const globalPaths = getOpenCodeConfigPaths({ binary: "opencode", version: null })
|
|
30
|
+
const candidates = [
|
|
31
|
+
join(directory, ".opencode", "opencode.json"),
|
|
32
|
+
join(directory, ".opencode", "opencode.jsonc"),
|
|
33
|
+
globalPaths.configJson,
|
|
34
|
+
globalPaths.configJsonc,
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
for (const configPath of candidates) {
|
|
38
|
+
const plugins = readPlugins(configPath)
|
|
39
|
+
if (!plugins.includes("list")) continue
|
|
40
|
+
|
|
41
|
+
console.warn(`[hiai-opencode] WARNING: ${configPath} contains plugin: ["list"].`)
|
|
42
|
+
console.warn("[hiai-opencode] This can prevent hiai-opencode MCP servers from loading from that config scope.")
|
|
43
|
+
console.warn(`[hiai-opencode] Update it to: plugin: ["${PLUGIN_NAME}"]`)
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function hasConfigAuthFallback(pluginConfig: HiaiOpenCodeConfig, envName: string): boolean {
|
|
48
|
+
if (envName === "FIRECRAWL_API_KEY") return !!pluginConfig.auth?.firecrawl?.trim()
|
|
49
|
+
if (envName === "STITCH_AI_API_KEY") return !!pluginConfig.auth?.stitch?.trim()
|
|
50
|
+
if (envName === "CONTEXT7_API_KEY") return !!pluginConfig.auth?.context7?.trim()
|
|
51
|
+
return false
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function warnMissingRequiredMcpEnv(args: {
|
|
55
|
+
pluginConfig: HiaiOpenCodeConfig
|
|
56
|
+
platformConfig: HiaiOpencodeConfig
|
|
57
|
+
}): void {
|
|
58
|
+
const disabled = new Set(args.pluginConfig.disabled_mcps ?? [])
|
|
59
|
+
const mcpConfig = args.platformConfig.mcp ?? {}
|
|
60
|
+
|
|
61
|
+
for (const [name, entry] of Object.entries(HIAI_MCP_REGISTRY)) {
|
|
62
|
+
if (disabled.has(name)) continue
|
|
63
|
+
if (mcpConfig[name]?.enabled === false) continue
|
|
64
|
+
if (!entry.requiredEnv || entry.requiredEnv.length === 0) continue
|
|
65
|
+
|
|
66
|
+
const missing = entry.requiredEnv.filter((envName) =>
|
|
67
|
+
!process.env[envName]?.trim() && !hasConfigAuthFallback(args.pluginConfig, envName)
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
if (missing.length === 0) continue
|
|
71
|
+
|
|
72
|
+
console.warn(
|
|
73
|
+
`[hiai-opencode] MCP "${name}" is enabled but missing required env: ${missing.join(", ")}.`
|
|
74
|
+
+ " The plugin will continue to load; set the key or disable this MCP in hiai-opencode.json.",
|
|
75
|
+
)
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/config/models.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
export const MODEL_ROLE_GUIDE = [
|
|
2
|
-
"fast: cheap/default for bounded helpers, researcher-style scans, platform chores",
|
|
3
|
-
"mid: balanced default for steady execution/review work",
|
|
4
|
-
"high: stronger general-purpose model for primary implementation and planning",
|
|
5
|
-
"ultrahigh: highest-cost/high-accuracy slot for hard architecture or critical decisions",
|
|
6
|
-
"vision: preferred for UI/media/multimodal interpretation and visual work",
|
|
7
|
-
"reasoning: preferred for deeper multi-step reasoning when latency/cost are acceptable",
|
|
8
|
-
] as const;
|
|
9
|
-
|
|
10
|
-
export const PROVIDER_MODEL_RULES = [
|
|
11
|
-
"openai: use `openai/<model>` for direct OpenAI calls, e.g. `openai/gpt-5` or `openai/o1`",
|
|
12
|
-
"anthropic: use `anthropic/<model>`, e.g. `anthropic/claude-3.5-sonnet`",
|
|
13
|
-
"deepseek: use `deepseek/<model>` when connected directly",
|
|
14
|
-
"glm: use `z-ai/<model>` or the provider id exposed by your gateway/client",
|
|
15
|
-
"minimax: use `minimax/<model>`",
|
|
16
|
-
"qwen: use `qwen/<model>`",
|
|
17
|
-
"ollama: use the native local model id in Ollama config, e.g. `qwen3.5:4b`",
|
|
18
|
-
"openrouter: use `openrouter/<vendor>/<model>`, e.g. `openrouter/anthropic/claude-3.5-sonnet`",
|
|
19
|
-
"rule: store fully qualified model ids in config; avoid local aliases like `fast`, `sonnet`, or provider-less ids",
|
|
20
|
-
] as const;
|
|
21
|
-
|
|
22
|
-
export const MODEL_PRESETS = {
|
|
23
|
-
fast: "openrouter/google/gemini-2.0-flash",
|
|
24
|
-
mid: "openrouter/anthropic/claude-3.5-sonnet",
|
|
25
|
-
high: "openrouter/anthropic/claude-3.5-opus",
|
|
26
|
-
ultrahigh: "openrouter/openai/gpt-4o",
|
|
27
|
-
vision: "openrouter/google/gemini-2.0-pro-exp-02-05",
|
|
28
|
-
reasoning: "openrouter/openai/o1",
|
|
29
|
-
strategist: "openrouter/z-ai/glm-5.1",
|
|
30
|
-
critic: "openrouter/qwen/qwen2.5-72b-instruct",
|
|
31
|
-
writing: "openrouter/kimi/kimi-latest",
|
|
32
|
-
} as const;
|