@hiai-gg/hiai-opencode 0.1.3 → 0.1.5
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 +77 -23
- package/ARCHITECTURE.md +15 -17
- package/LICENSE.md +1 -0
- package/README.md +189 -94
- package/assets/cli/hiai-opencode.mjs +276 -0
- package/assets/mcp/playwright.mjs +7 -0
- package/config/hiai-opencode.schema.json +85 -50
- package/dist/config/defaults.d.ts +1 -3
- package/dist/config/index.d.ts +0 -1
- package/dist/config/platform-schema.d.ts +343 -4
- package/dist/config/schema/agent-overrides.d.ts +256 -0
- package/dist/config/schema/categories.d.ts +1 -1
- 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 +33 -3
- package/dist/create-tools.d.ts +2 -0
- 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 +744 -358
- package/dist/internals/plugins/websearch-cited/index.d.ts +7 -1
- package/dist/mcp/types.d.ts +1 -1
- package/dist/plugin/skill-discovery-config.d.ts +4 -0
- package/dist/plugin/tool-registry.d.ts +2 -0
- package/dist/shared/startup-diagnostics.d.ts +6 -0
- package/dist/tools/skill-mcp/tools.d.ts +2 -0
- package/hiai-opencode.json +55 -33
- package/package.json +4 -1
- package/src/agents/AGENTS.md +3 -4
- package/src/config/defaults.ts +186 -77
- package/src/config/index.ts +0 -1
- package/src/config/loader.test.ts +16 -1
- package/src/config/loader.ts +4 -2
- package/src/config/platform-schema.ts +53 -4
- 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 +49 -2
- package/src/create-tools.ts +4 -1
- 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 +53 -14
- package/src/internals/plugins/websearch-cited/index.ts +10 -5
- package/src/lsp/index.ts +1 -0
- package/src/mcp/registry.ts +6 -1
- 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/tool-registry.ts +4 -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/tools/skill-mcp/tools.ts +45 -7
- package/src/config/models.ts +0 -32
|
@@ -8,6 +8,37 @@ export const AgentConfigSchema = z.object({
|
|
|
8
8
|
description: z.string().optional(),
|
|
9
9
|
});
|
|
10
10
|
|
|
11
|
+
export const ModelRecommendationSchema = z.enum([
|
|
12
|
+
"xhigh",
|
|
13
|
+
"high",
|
|
14
|
+
"middle",
|
|
15
|
+
"fast",
|
|
16
|
+
"vision",
|
|
17
|
+
"writing",
|
|
18
|
+
"design",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export const ModelSlotConfigSchema = z.union([
|
|
22
|
+
z.string(),
|
|
23
|
+
z.object({
|
|
24
|
+
model: z.string(),
|
|
25
|
+
recommended: ModelRecommendationSchema.optional(),
|
|
26
|
+
}),
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
export const ModelSlotsConfigSchema = z.object({
|
|
30
|
+
bob: ModelSlotConfigSchema.optional(),
|
|
31
|
+
coder: ModelSlotConfigSchema.optional(),
|
|
32
|
+
strategist: ModelSlotConfigSchema.optional(),
|
|
33
|
+
guard: ModelSlotConfigSchema.optional(),
|
|
34
|
+
critic: ModelSlotConfigSchema.optional(),
|
|
35
|
+
designer: ModelSlotConfigSchema.optional(),
|
|
36
|
+
researcher: ModelSlotConfigSchema.optional(),
|
|
37
|
+
manager: ModelSlotConfigSchema.optional(),
|
|
38
|
+
brainstormer: ModelSlotConfigSchema.optional(),
|
|
39
|
+
vision: ModelSlotConfigSchema.optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
11
42
|
export const FallbackEntrySchema = z.object({
|
|
12
43
|
providers: z.array(z.string()),
|
|
13
44
|
model: z.string(),
|
|
@@ -48,8 +79,9 @@ export const McpServerConfigSchema = z.object({
|
|
|
48
79
|
});
|
|
49
80
|
|
|
50
81
|
export const LspServerConfigSchema = z.object({
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
enabled: z.boolean().optional(),
|
|
83
|
+
command: z.array(z.string()).optional(),
|
|
84
|
+
extensions: z.array(z.string()).optional(),
|
|
53
85
|
initialization: z.record(z.string(), z.unknown()).optional(),
|
|
54
86
|
});
|
|
55
87
|
|
|
@@ -63,6 +95,16 @@ export const SkillsConfigSchema = z.object({
|
|
|
63
95
|
disabled: z.array(z.string()).optional(),
|
|
64
96
|
});
|
|
65
97
|
|
|
98
|
+
export const SkillDiscoveryConfigSchema = z.object({
|
|
99
|
+
config_sources: z.boolean().optional(),
|
|
100
|
+
project_opencode: z.boolean().optional(),
|
|
101
|
+
global_opencode: z.boolean().optional(),
|
|
102
|
+
project_claude: z.boolean().optional(),
|
|
103
|
+
global_claude: z.boolean().optional(),
|
|
104
|
+
project_agents: z.boolean().optional(),
|
|
105
|
+
global_agents: z.boolean().optional(),
|
|
106
|
+
});
|
|
107
|
+
|
|
66
108
|
export const PermissionsConfigSchema = z.object({
|
|
67
109
|
read: z.record(z.string(), z.string()).optional(),
|
|
68
110
|
edit: z.record(z.string(), z.string()).optional(),
|
|
@@ -92,12 +134,13 @@ export const AuthKeysSchema = z.object({
|
|
|
92
134
|
openrouter: z.string().optional(),
|
|
93
135
|
stitch: z.string().optional(),
|
|
94
136
|
firecrawl: z.string().optional(),
|
|
137
|
+
context7: z.string().optional(),
|
|
95
138
|
});
|
|
96
139
|
|
|
97
140
|
export const OllamaConfigSchema = z.object({
|
|
98
141
|
enabled: z.boolean().default(false),
|
|
99
|
-
model: z.string().default("
|
|
100
|
-
baseUrl: z.string().default("
|
|
142
|
+
model: z.string().default(""),
|
|
143
|
+
baseUrl: z.string().default(""),
|
|
101
144
|
purpose: z.enum(["verification", "helper", "fallback"]).default("helper"),
|
|
102
145
|
});
|
|
103
146
|
|
|
@@ -130,6 +173,8 @@ const AgentsConfigSchema = z.object({
|
|
|
130
173
|
zoe: AgentConfigSchema.optional(),
|
|
131
174
|
build: AgentConfigSchema.optional(),
|
|
132
175
|
"pre-plan": AgentConfigSchema.optional(),
|
|
176
|
+
manager: AgentConfigSchema.optional(),
|
|
177
|
+
vision: AgentConfigSchema.optional(),
|
|
133
178
|
logician: AgentConfigSchema.optional(),
|
|
134
179
|
librarian: AgentConfigSchema.optional(),
|
|
135
180
|
explore: AgentConfigSchema.optional(),
|
|
@@ -162,6 +207,8 @@ const AgentRequirementsConfigSchema = z.object({
|
|
|
162
207
|
zoe: ModelRequirementSchema.optional(),
|
|
163
208
|
build: ModelRequirementSchema.optional(),
|
|
164
209
|
"pre-plan": ModelRequirementSchema.optional(),
|
|
210
|
+
manager: ModelRequirementSchema.optional(),
|
|
211
|
+
vision: ModelRequirementSchema.optional(),
|
|
165
212
|
logician: ModelRequirementSchema.optional(),
|
|
166
213
|
librarian: ModelRequirementSchema.optional(),
|
|
167
214
|
explore: ModelRequirementSchema.optional(),
|
|
@@ -176,6 +223,7 @@ const AgentRequirementsConfigSchema = z.object({
|
|
|
176
223
|
|
|
177
224
|
export const HiaiOpencodeConfigSchema = z.object({
|
|
178
225
|
$schema: z.string().optional(),
|
|
226
|
+
models: ModelSlotsConfigSchema.optional(),
|
|
179
227
|
agents: AgentsConfigSchema.optional(),
|
|
180
228
|
agentRequirements: AgentRequirementsConfigSchema.optional(),
|
|
181
229
|
categories: z.record(z.string(), CategoryConfigSchema).optional(),
|
|
@@ -185,6 +233,7 @@ export const HiaiOpencodeConfigSchema = z.object({
|
|
|
185
233
|
lsp: z.record(z.string(), LspServerConfigSchema).optional(),
|
|
186
234
|
subtask2: Subtask2ConfigSchema.optional(),
|
|
187
235
|
skills: SkillsConfigSchema.optional(),
|
|
236
|
+
skill_discovery: SkillDiscoveryConfigSchema.optional(),
|
|
188
237
|
permissions: PermissionsConfigSchema.optional(),
|
|
189
238
|
auth: AuthKeysSchema.optional(),
|
|
190
239
|
ollama: OllamaConfigSchema.optional(),
|
|
@@ -79,6 +79,8 @@ export const AgentOverridesSchema = z.object({
|
|
|
79
79
|
general: AgentOverrideConfigSchema.optional(),
|
|
80
80
|
zoe: AgentOverrideConfigSchema.optional(),
|
|
81
81
|
"pre-plan": AgentOverrideConfigSchema.optional(),
|
|
82
|
+
manager: AgentOverrideConfigSchema.optional(),
|
|
83
|
+
vision: AgentOverrideConfigSchema.optional(),
|
|
82
84
|
"logician": AgentOverrideConfigSchema.optional(),
|
|
83
85
|
librarian: AgentOverrideConfigSchema.optional(),
|
|
84
86
|
explore: AgentOverrideConfigSchema.optional(),
|
|
@@ -3,10 +3,10 @@ import { z } from "zod"
|
|
|
3
3
|
export const FastApplyConfigSchema = z.object({
|
|
4
4
|
/** Enable fast-apply via Ollama (default: false) */
|
|
5
5
|
enabled: z.boolean().optional().default(false),
|
|
6
|
-
/** Ollama API URL
|
|
7
|
-
ollama_url: z.string().optional().default("
|
|
8
|
-
/** Model name for fast-apply
|
|
9
|
-
model: z.string().optional().default("
|
|
6
|
+
/** Ollama API URL. Configure in hiai-opencode.json. */
|
|
7
|
+
ollama_url: z.string().optional().default(""),
|
|
8
|
+
/** Model name for fast-apply. Configure in hiai-opencode.json. */
|
|
9
|
+
model: z.string().optional().default(""),
|
|
10
10
|
/** Timeout in milliseconds for Ollama request (default: 30000) */
|
|
11
11
|
timeout: z.number().int().positive().optional().default(30000),
|
|
12
12
|
})
|
|
@@ -44,6 +44,8 @@ export { BobAgentConfigSchema } from "./bob-agent"
|
|
|
44
44
|
export type { BobAgentConfig } from "./bob-agent"
|
|
45
45
|
export { SkillsConfigSchema } from "./skills"
|
|
46
46
|
export type { SkillsConfig, SkillDefinition } from "./skills"
|
|
47
|
+
export { SkillDiscoveryConfigSchema } from "./skill-discovery"
|
|
48
|
+
export type { SkillDiscoveryConfig } from "./skill-discovery"
|
|
47
49
|
export { StartWorkConfigSchema } from "./start-work"
|
|
48
50
|
export type { StartWorkConfig } from "./start-work"
|
|
49
51
|
export { TmuxConfigSchema, TmuxLayoutSchema, TmuxIsolationSchema } from "./tmux"
|
|
@@ -17,6 +17,7 @@ import { ModelCapabilitiesConfigSchema } from "./model-capabilities"
|
|
|
17
17
|
import { RalphLoopConfigSchema } from "./ralph-loop"
|
|
18
18
|
import { RuntimeFallbackConfigSchema } from "./runtime-fallback"
|
|
19
19
|
import { SkillsConfigSchema } from "./skills"
|
|
20
|
+
import { SkillDiscoveryConfigSchema } from "./skill-discovery"
|
|
20
21
|
import { BobConfigSchema } from "./bob"
|
|
21
22
|
import { BobAgentConfigSchema } from "./bob-agent"
|
|
22
23
|
import { TmuxConfigSchema } from "./tmux"
|
|
@@ -30,6 +31,7 @@ const AuthConfigSchema = z.object({
|
|
|
30
31
|
openrouter: z.string().optional(),
|
|
31
32
|
stitch: z.string().optional(),
|
|
32
33
|
firecrawl: z.string().optional(),
|
|
34
|
+
context7: z.string().optional(),
|
|
33
35
|
}).optional()
|
|
34
36
|
|
|
35
37
|
export const HiaiOpenCodeConfigSchema = z.object({
|
|
@@ -60,6 +62,7 @@ export const HiaiOpenCodeConfigSchema = z.object({
|
|
|
60
62
|
experimental: ExperimentalConfigSchema.optional(),
|
|
61
63
|
auto_update: z.boolean().optional(),
|
|
62
64
|
skills: SkillsConfigSchema.optional(),
|
|
65
|
+
skill_discovery: SkillDiscoveryConfigSchema.optional(),
|
|
63
66
|
ralph_loop: RalphLoopConfigSchema.optional(),
|
|
64
67
|
/**
|
|
65
68
|
* Enable runtime fallback (default: false)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { z } from "zod"
|
|
2
|
+
|
|
3
|
+
export const SkillDiscoveryConfigSchema = z.object({
|
|
4
|
+
/**
|
|
5
|
+
* Explicit skills.sources entries from hiai-opencode config.
|
|
6
|
+
* Safe by default because the user intentionally configured them.
|
|
7
|
+
*/
|
|
8
|
+
config_sources: z.boolean().default(true),
|
|
9
|
+
/**
|
|
10
|
+
* Project-local OpenCode skills: .opencode/skills and .opencode/skill.
|
|
11
|
+
* Kept on by default because they are part of the current project contract.
|
|
12
|
+
*/
|
|
13
|
+
project_opencode: z.boolean().default(true),
|
|
14
|
+
/**
|
|
15
|
+
* Global OpenCode skills from the user's OpenCode config directory.
|
|
16
|
+
* Off by default to keep clean installs deterministic.
|
|
17
|
+
*/
|
|
18
|
+
global_opencode: z.boolean().default(false),
|
|
19
|
+
project_claude: z.boolean().default(false),
|
|
20
|
+
global_claude: z.boolean().default(false),
|
|
21
|
+
project_agents: z.boolean().default(false),
|
|
22
|
+
global_agents: z.boolean().default(false),
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
export type SkillDiscoveryConfig = z.infer<typeof SkillDiscoveryConfigSchema>
|
package/src/config/types.ts
CHANGED
|
@@ -7,6 +7,35 @@ export interface AgentConfig {
|
|
|
7
7
|
description?: string;
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export type ModelRecommendation =
|
|
11
|
+
| "xhigh"
|
|
12
|
+
| "high"
|
|
13
|
+
| "middle"
|
|
14
|
+
| "fast"
|
|
15
|
+
| "vision"
|
|
16
|
+
| "writing"
|
|
17
|
+
| "design";
|
|
18
|
+
|
|
19
|
+
export type ModelSlotConfig =
|
|
20
|
+
| string
|
|
21
|
+
| {
|
|
22
|
+
model: string;
|
|
23
|
+
recommended?: ModelRecommendation;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export interface ModelSlotsConfig {
|
|
27
|
+
bob?: ModelSlotConfig;
|
|
28
|
+
coder?: ModelSlotConfig;
|
|
29
|
+
strategist?: ModelSlotConfig;
|
|
30
|
+
guard?: ModelSlotConfig;
|
|
31
|
+
critic?: ModelSlotConfig;
|
|
32
|
+
designer?: ModelSlotConfig;
|
|
33
|
+
researcher?: ModelSlotConfig;
|
|
34
|
+
manager?: ModelSlotConfig;
|
|
35
|
+
brainstormer?: ModelSlotConfig;
|
|
36
|
+
vision?: ModelSlotConfig;
|
|
37
|
+
}
|
|
38
|
+
|
|
10
39
|
// Canonical 12-agent model exposed by schema/default config.
|
|
11
40
|
export const CANONICAL_AGENT_NAMES = [
|
|
12
41
|
"bob",
|
|
@@ -32,6 +61,8 @@ export const LEGACY_AGENT_ALIAS_NAMES = [
|
|
|
32
61
|
"zoe",
|
|
33
62
|
"build",
|
|
34
63
|
"pre-plan",
|
|
64
|
+
"manager",
|
|
65
|
+
"vision",
|
|
35
66
|
"logician",
|
|
36
67
|
"librarian",
|
|
37
68
|
"explore",
|
|
@@ -54,6 +85,8 @@ export const LEGACY_AGENT_ALIAS_TO_CANONICAL: Record<
|
|
|
54
85
|
zoe: "bob",
|
|
55
86
|
build: "bob",
|
|
56
87
|
"pre-plan": "strategist",
|
|
88
|
+
manager: "platform-manager",
|
|
89
|
+
vision: "multimodal",
|
|
57
90
|
logician: "strategist",
|
|
58
91
|
librarian: "researcher",
|
|
59
92
|
explore: "researcher",
|
|
@@ -120,8 +153,9 @@ export interface McpServerConfig {
|
|
|
120
153
|
}
|
|
121
154
|
|
|
122
155
|
export interface LspServerConfig {
|
|
123
|
-
|
|
124
|
-
|
|
156
|
+
enabled?: boolean;
|
|
157
|
+
command?: string[];
|
|
158
|
+
extensions?: string[];
|
|
125
159
|
initialization?: Record<string, unknown>;
|
|
126
160
|
}
|
|
127
161
|
|
|
@@ -135,6 +169,16 @@ export interface SkillsConfig {
|
|
|
135
169
|
disabled?: string[];
|
|
136
170
|
}
|
|
137
171
|
|
|
172
|
+
export interface SkillDiscoveryConfig {
|
|
173
|
+
config_sources?: boolean;
|
|
174
|
+
project_opencode?: boolean;
|
|
175
|
+
global_opencode?: boolean;
|
|
176
|
+
project_claude?: boolean;
|
|
177
|
+
global_claude?: boolean;
|
|
178
|
+
project_agents?: boolean;
|
|
179
|
+
global_agents?: boolean;
|
|
180
|
+
}
|
|
181
|
+
|
|
138
182
|
export interface PermissionsConfig {
|
|
139
183
|
read?: Record<string, string>;
|
|
140
184
|
edit?: Record<string, string>;
|
|
@@ -148,6 +192,7 @@ export interface AuthKeys {
|
|
|
148
192
|
openrouter?: string;
|
|
149
193
|
stitch?: string;
|
|
150
194
|
firecrawl?: string;
|
|
195
|
+
context7?: string;
|
|
151
196
|
}
|
|
152
197
|
|
|
153
198
|
export interface OllamaConfig {
|
|
@@ -159,6 +204,7 @@ export interface OllamaConfig {
|
|
|
159
204
|
|
|
160
205
|
export interface HiaiOpencodeConfig {
|
|
161
206
|
$schema?: string;
|
|
207
|
+
models?: ModelSlotsConfig;
|
|
162
208
|
agents?: AgentConfigMap;
|
|
163
209
|
agentRequirements?: AgentRequirementMap;
|
|
164
210
|
categories?: Record<string, CategoryConfig>;
|
|
@@ -168,6 +214,7 @@ export interface HiaiOpencodeConfig {
|
|
|
168
214
|
lsp?: Record<string, LspServerConfig>;
|
|
169
215
|
subtask2?: Subtask2Config;
|
|
170
216
|
skills?: SkillsConfig;
|
|
217
|
+
skill_discovery?: SkillDiscoveryConfig;
|
|
171
218
|
permissions?: PermissionsConfig;
|
|
172
219
|
auth?: AuthKeys;
|
|
173
220
|
ollama?: OllamaConfig;
|
package/src/create-tools.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AvailableCategory, AvailableSkill } from "./agents/dynamic-agent-prompt-builder"
|
|
2
2
|
import type { HiaiOpenCodeConfig } from "./config"
|
|
3
|
+
import type { HiaiOpencodeConfig } from "./config/types"
|
|
3
4
|
import type { BrowserAutomationProvider } from "./config/schema/browser-automation"
|
|
4
5
|
import type { LoadedSkill } from "./features/opencode-skill-loader/types"
|
|
5
6
|
import type { PluginContext, ToolsRecord } from "./plugin/types"
|
|
@@ -22,9 +23,10 @@ type CreateToolsResult = {
|
|
|
22
23
|
export async function createTools(args: {
|
|
23
24
|
ctx: PluginContext
|
|
24
25
|
pluginConfig: HiaiOpenCodeConfig
|
|
26
|
+
platformConfig?: HiaiOpencodeConfig
|
|
25
27
|
managers: Pick<Managers, "backgroundManager" | "tmuxSessionManager" | "skillMcpManager">
|
|
26
28
|
}): Promise<CreateToolsResult> {
|
|
27
|
-
const { ctx, pluginConfig, managers } = args
|
|
29
|
+
const { ctx, pluginConfig, platformConfig, managers } = args
|
|
28
30
|
|
|
29
31
|
const skillContext = await createSkillContext({
|
|
30
32
|
directory: ctx.directory,
|
|
@@ -39,6 +41,7 @@ export async function createTools(args: {
|
|
|
39
41
|
managers,
|
|
40
42
|
skillContext,
|
|
41
43
|
availableCategories,
|
|
44
|
+
builtinMcp: platformConfig?.mcp,
|
|
42
45
|
})
|
|
43
46
|
|
|
44
47
|
return {
|
|
@@ -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
|
|
|
@@ -26,9 +27,13 @@ import type { HiaiOpencodeConfig } from "./config/types"
|
|
|
26
27
|
|
|
27
28
|
import { createPlugin as createSubtask2Plugin } from "./internals/plugins/subtask2/core/plugin"
|
|
28
29
|
import WebsearchCitedPlugin, {
|
|
30
|
+
GOOGLE_PROVIDER_ID,
|
|
31
|
+
OPENAI_PROVIDER_ID,
|
|
32
|
+
OPENROUTER_PROVIDER_ID,
|
|
29
33
|
WebsearchCitedGooglePlugin,
|
|
30
34
|
WebsearchCitedOpenAIPlugin
|
|
31
35
|
} from "./internals/plugins/websearch-cited/index"
|
|
36
|
+
import type { WebsearchCitedFallback } from "./internals/plugins/websearch-cited/index"
|
|
32
37
|
import { createBuiltinSkills } from "./features/builtin-skills"
|
|
33
38
|
import {
|
|
34
39
|
materializeBuiltinSkills,
|
|
@@ -37,6 +42,39 @@ import {
|
|
|
37
42
|
|
|
38
43
|
let activePluginDispose: PluginDispose | null = null
|
|
39
44
|
|
|
45
|
+
function createWebsearchFallback(config: HiaiOpencodeConfig): WebsearchCitedFallback | undefined {
|
|
46
|
+
const model = config.agents?.researcher?.model?.trim()
|
|
47
|
+
if (!model) {
|
|
48
|
+
return undefined
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (model.startsWith("openrouter/")) {
|
|
52
|
+
return {
|
|
53
|
+
providerID: OPENROUTER_PROVIDER_ID,
|
|
54
|
+
model: model.slice("openrouter/".length),
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
if (model.startsWith("openai/")) {
|
|
59
|
+
return {
|
|
60
|
+
providerID: OPENAI_PROVIDER_ID,
|
|
61
|
+
model: model.slice("openai/".length),
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (model.startsWith("google/")) {
|
|
66
|
+
return {
|
|
67
|
+
providerID: GOOGLE_PROVIDER_ID,
|
|
68
|
+
model: model.slice("google/".length),
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
providerID: OPENROUTER_PROVIDER_ID,
|
|
74
|
+
model,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
40
78
|
function configureBundledBunPtyLibrary(): void {
|
|
41
79
|
if (process.env.BUN_PTY_LIB?.trim()) {
|
|
42
80
|
return
|
|
@@ -69,6 +107,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
69
107
|
log("[HiaiOpenCodePlugin] ENTRY - plugin loading", {
|
|
70
108
|
directory: ctx.directory,
|
|
71
109
|
})
|
|
110
|
+
warnIfListPluginEntry(ctx.directory)
|
|
72
111
|
|
|
73
112
|
const skillPluginCheck = detectExternalSkillPlugin(ctx.directory)
|
|
74
113
|
if (skillPluginCheck.detected && skillPluginCheck.pluginName) {
|
|
@@ -83,6 +122,10 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
83
122
|
loadPluginConfig(ctx.directory, ctx),
|
|
84
123
|
internalConfig,
|
|
85
124
|
)
|
|
125
|
+
warnMissingRequiredMcpEnv({
|
|
126
|
+
pluginConfig,
|
|
127
|
+
platformConfig: internalConfig,
|
|
128
|
+
})
|
|
86
129
|
|
|
87
130
|
materializeBuiltinSkills(
|
|
88
131
|
createBuiltinSkills({
|
|
@@ -123,6 +166,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
123
166
|
const toolsResult = await createTools({
|
|
124
167
|
ctx,
|
|
125
168
|
pluginConfig,
|
|
169
|
+
platformConfig: internalConfig,
|
|
126
170
|
managers,
|
|
127
171
|
})
|
|
128
172
|
|
|
@@ -163,7 +207,7 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
163
207
|
} catch (err) {
|
|
164
208
|
console.error("[hiai-opencode] PTYPlugin failed to load:", err);
|
|
165
209
|
}
|
|
166
|
-
const websearchResult = await WebsearchCitedPlugin(ctx)
|
|
210
|
+
const websearchResult = await WebsearchCitedPlugin(ctx, createWebsearchFallback(internalConfig))
|
|
167
211
|
const websearchGoogleResult = await WebsearchCitedGooglePlugin(ctx)
|
|
168
212
|
const websearchOpenAIResult = await WebsearchCitedOpenAIPlugin(ctx)
|
|
169
213
|
|
|
@@ -220,29 +264,24 @@ const HiaiOpenCodePlugin: Plugin = async (ctx) => {
|
|
|
220
264
|
auth: {
|
|
221
265
|
provider: "hiai-opencode",
|
|
222
266
|
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" },
|
|
267
|
+
{ type: "api" as const, label: "Google Search API key" },
|
|
226
268
|
],
|
|
227
269
|
loader: async (getAuth: any) => {
|
|
228
270
|
const authData = await getAuth();
|
|
229
271
|
const { registerGetAuth, GOOGLE_PROVIDER_ID, OPENAI_PROVIDER_ID, OPENROUTER_PROVIDER_ID } = await import("./internals/plugins/websearch-cited/index");
|
|
230
272
|
|
|
231
|
-
|
|
232
|
-
const getKey = (label: string, configKey?: string) => {
|
|
233
|
-
const fromAuth = authData[label];
|
|
234
|
-
if (fromAuth) return fromAuth;
|
|
273
|
+
const getConfiguredKey = (configKey?: string) => {
|
|
235
274
|
if (configKey) return resolveEnvVars(configKey);
|
|
236
275
|
return undefined;
|
|
237
276
|
};
|
|
238
277
|
|
|
239
|
-
const googleKey =
|
|
240
|
-
const openaiKey =
|
|
241
|
-
const openRouterKey =
|
|
278
|
+
const googleKey = authData["Google Search API key"] || getConfiguredKey(internalConfig.auth?.googleSearch);
|
|
279
|
+
const openaiKey = getConfiguredKey(internalConfig.auth?.openai);
|
|
280
|
+
const openRouterKey = getConfiguredKey(internalConfig.auth?.openrouter);
|
|
242
281
|
|
|
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));
|
|
282
|
+
if (googleKey) registerGetAuth(GOOGLE_PROVIDER_ID, () => Promise.resolve({ type: "api", key: googleKey }));
|
|
283
|
+
if (openaiKey) registerGetAuth(OPENAI_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openaiKey }));
|
|
284
|
+
if (openRouterKey) registerGetAuth(OPENROUTER_PROVIDER_ID, () => Promise.resolve({ type: "api", key: openRouterKey }));
|
|
246
285
|
|
|
247
286
|
return {};
|
|
248
287
|
},
|
|
@@ -44,6 +44,8 @@ type SelectedWebsearchConfig = {
|
|
|
44
44
|
model: string;
|
|
45
45
|
};
|
|
46
46
|
|
|
47
|
+
export type WebsearchCitedFallback = SelectedWebsearchConfig;
|
|
48
|
+
|
|
47
49
|
type WebsearchCitedSelection = {
|
|
48
50
|
selected?: SelectedWebsearchConfig;
|
|
49
51
|
error?: string;
|
|
@@ -164,16 +166,16 @@ function parseOpenAIOptions(providerConfig: unknown, model: string | undefined):
|
|
|
164
166
|
return result;
|
|
165
167
|
}
|
|
166
168
|
|
|
167
|
-
const WebsearchCitedPlugin
|
|
168
|
-
let selectedProvider: SelectedProviderID | undefined;
|
|
169
|
-
let selectedModel: string | undefined;
|
|
169
|
+
const WebsearchCitedPlugin = (_ctx?: unknown, fallback?: WebsearchCitedFallback): Promise<any> => {
|
|
170
|
+
let selectedProvider: SelectedProviderID | undefined = fallback?.providerID;
|
|
171
|
+
let selectedModel: string | undefined = fallback?.model;
|
|
170
172
|
let openaiConfig: OpenAIWebsearchConfig = {};
|
|
171
173
|
let configError: string | undefined;
|
|
172
174
|
|
|
173
175
|
return Promise.resolve({
|
|
174
176
|
auth: {
|
|
175
177
|
provider: OPENROUTER_PROVIDER_ID,
|
|
176
|
-
loader(getAuth) {
|
|
178
|
+
loader(getAuth: GetAuth) {
|
|
177
179
|
registerGetAuth(OPENROUTER_PROVIDER_ID, getAuth);
|
|
178
180
|
return Promise.resolve({});
|
|
179
181
|
},
|
|
@@ -184,7 +186,7 @@ const WebsearchCitedPlugin: Plugin = () => {
|
|
|
184
186
|
},
|
|
185
187
|
],
|
|
186
188
|
},
|
|
187
|
-
config: (config) => {
|
|
189
|
+
config: (config: Config) => {
|
|
188
190
|
const { selected, error } = findFirstWebsearchCitedConfig(config as any);
|
|
189
191
|
|
|
190
192
|
selectedProvider = undefined;
|
|
@@ -199,6 +201,9 @@ const WebsearchCitedPlugin: Plugin = () => {
|
|
|
199
201
|
const openaiProvider = config.provider?.openai;
|
|
200
202
|
openaiConfig = parseOpenAIOptions(openaiProvider, selectedModel);
|
|
201
203
|
}
|
|
204
|
+
} else if (!error && fallback) {
|
|
205
|
+
selectedProvider = fallback.providerID;
|
|
206
|
+
selectedModel = fallback.model;
|
|
202
207
|
}
|
|
203
208
|
|
|
204
209
|
return Promise.resolve();
|
package/src/lsp/index.ts
CHANGED
|
@@ -4,6 +4,7 @@ export function buildLspConfig(lsp: Record<string, LspServerConfig>): Record<str
|
|
|
4
4
|
const result: Record<string, unknown> = {};
|
|
5
5
|
|
|
6
6
|
for (const [name, server] of Object.entries(lsp)) {
|
|
7
|
+
if (server.enabled === false) continue;
|
|
7
8
|
result[name] = {
|
|
8
9
|
command: server.command,
|
|
9
10
|
extensions: server.extensions,
|