@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.
Files changed (64) hide show
  1. package/.env.example +14 -18
  2. package/AGENTS.md +77 -23
  3. package/ARCHITECTURE.md +15 -17
  4. package/LICENSE.md +1 -0
  5. package/README.md +189 -94
  6. package/assets/cli/hiai-opencode.mjs +276 -0
  7. package/assets/mcp/playwright.mjs +7 -0
  8. package/config/hiai-opencode.schema.json +85 -50
  9. package/dist/config/defaults.d.ts +1 -3
  10. package/dist/config/index.d.ts +0 -1
  11. package/dist/config/platform-schema.d.ts +343 -4
  12. package/dist/config/schema/agent-overrides.d.ts +256 -0
  13. package/dist/config/schema/categories.d.ts +1 -1
  14. package/dist/config/schema/commands.d.ts +1 -0
  15. package/dist/config/schema/index.d.ts +2 -0
  16. package/dist/config/schema/oh-my-opencode-config.d.ts +267 -0
  17. package/dist/config/schema/skill-discovery.d.ts +11 -0
  18. package/dist/config/types.d.ts +33 -3
  19. package/dist/create-tools.d.ts +2 -0
  20. package/dist/features/builtin-commands/templates/mcp-status.d.ts +1 -0
  21. package/dist/features/builtin-commands/types.d.ts +1 -1
  22. package/dist/features/opencode-skill-loader/loader.d.ts +2 -0
  23. package/dist/index.js +744 -358
  24. package/dist/internals/plugins/websearch-cited/index.d.ts +7 -1
  25. package/dist/mcp/types.d.ts +1 -1
  26. package/dist/plugin/skill-discovery-config.d.ts +4 -0
  27. package/dist/plugin/tool-registry.d.ts +2 -0
  28. package/dist/shared/startup-diagnostics.d.ts +6 -0
  29. package/dist/tools/skill-mcp/tools.d.ts +2 -0
  30. package/hiai-opencode.json +55 -33
  31. package/package.json +4 -1
  32. package/src/agents/AGENTS.md +3 -4
  33. package/src/config/defaults.ts +186 -77
  34. package/src/config/index.ts +0 -1
  35. package/src/config/loader.test.ts +16 -1
  36. package/src/config/loader.ts +4 -2
  37. package/src/config/platform-schema.ts +53 -4
  38. package/src/config/schema/agent-overrides.ts +2 -0
  39. package/src/config/schema/commands.ts +1 -0
  40. package/src/config/schema/fast-apply.ts +4 -4
  41. package/src/config/schema/index.ts +2 -0
  42. package/src/config/schema/oh-my-opencode-config.ts +3 -0
  43. package/src/config/schema/skill-discovery.ts +25 -0
  44. package/src/config/types.ts +49 -2
  45. package/src/create-tools.ts +4 -1
  46. package/src/features/builtin-commands/commands.ts +7 -0
  47. package/src/features/builtin-commands/templates/mcp-status.ts +36 -0
  48. package/src/features/builtin-commands/types.ts +1 -1
  49. package/src/features/builtin-skills/skills/playwright.ts +24 -2
  50. package/src/features/opencode-skill-loader/loader.ts +11 -0
  51. package/src/index.ts +53 -14
  52. package/src/internals/plugins/websearch-cited/index.ts +10 -5
  53. package/src/lsp/index.ts +1 -0
  54. package/src/mcp/registry.ts +6 -1
  55. package/src/plugin/hooks/create-tool-guard-hooks.ts +1 -1
  56. package/src/plugin/skill-context.ts +31 -13
  57. package/src/plugin/skill-discovery-config.ts +32 -0
  58. package/src/plugin/tool-registry.ts +4 -0
  59. package/src/plugin-handlers/agent-config-handler.ts +20 -13
  60. package/src/plugin-handlers/command-config-handler.ts +22 -12
  61. package/src/shared/migration/agent-names.ts +5 -5
  62. package/src/shared/startup-diagnostics.ts +77 -0
  63. package/src/tools/skill-mcp/tools.ts +45 -7
  64. 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
- command: z.array(z.string()),
52
- extensions: z.array(z.string()),
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("qwen3.5:4b"),
100
- baseUrl: z.string().default("http://localhost:11434"),
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(),
@@ -9,6 +9,7 @@ export const BuiltinCommandNameSchema = z.enum([
9
9
  "start-work",
10
10
  "stop-continuation",
11
11
  "remove-ai-slops",
12
+ "mcp-status",
12
13
  ])
13
14
 
14
15
  export type BuiltinCommandName = z.infer<typeof BuiltinCommandNameSchema>
@@ -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 (default: http://localhost:11434) */
7
- ollama_url: z.string().optional().default("http://localhost:11434"),
8
- /** Model name for fast-apply (default: qwen3.5:9b) */
9
- model: z.string().optional().default("qwen3.5:9b"),
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>
@@ -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
- command: string[];
124
- extensions: string[];
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;
@@ -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
- // Helper to get key from config or authData
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 = getKey("Google API key", internalConfig.auth?.googleSearch);
240
- const openaiKey = getKey("OpenAI API key", internalConfig.auth?.openai);
241
- const openRouterKey = getKey("OpenRouter API key", internalConfig.auth?.openrouter);
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: Plugin = () => {
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,