@hiai-gg/hiai-opencode 0.1.4 → 0.1.6
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 -8
- package/AGENTS.md +19 -8
- package/ARCHITECTURE.md +7 -6
- package/LICENSE.md +0 -1
- package/README.md +48 -17
- package/assets/cli/hiai-opencode.mjs +590 -7
- package/assets/mcp/mempalace.mjs +159 -25
- package/config/hiai-opencode.schema.json +82 -148
- package/dist/agents/dynamic-agent-core-sections.d.ts +4 -1
- package/dist/agents/dynamic-agent-prompt-builder.d.ts +1 -1
- package/dist/config/defaults.d.ts +1 -0
- package/dist/config/platform-schema.d.ts +275 -10
- package/dist/config/schema/categories.d.ts +2 -2
- package/dist/config/schema/commands.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +1 -3
- package/dist/config/types.d.ts +22 -5
- package/dist/create-tools.d.ts +2 -0
- package/dist/features/builtin-commands/templates/doctor.d.ts +1 -0
- package/dist/features/builtin-commands/types.d.ts +1 -1
- package/dist/features/builtin-skills/skills/hiai-opencode-setup.d.ts +2 -0
- package/dist/features/builtin-skills/skills/index.d.ts +1 -0
- package/dist/index.js +870 -1711
- package/dist/mcp/types.d.ts +1 -1
- package/dist/plugin/tool-registry.d.ts +2 -0
- package/dist/shared/mcp-static-export.d.ts +22 -0
- package/dist/tools/ast-grep/constants.d.ts +1 -1
- package/dist/tools/ast-grep/environment-check.d.ts +1 -5
- package/dist/tools/ast-grep/language-support.d.ts +0 -1
- package/dist/tools/ast-grep/types.d.ts +1 -2
- package/dist/tools/skill-mcp/tools.d.ts +2 -0
- package/hiai-opencode.json +39 -171
- package/package.json +6 -4
- package/src/agents/bob/default.ts +6 -1
- package/src/agents/bob/gpt-pro.ts +1 -0
- package/src/agents/bob.ts +1 -0
- package/src/agents/coder/gpt-codex.ts +1 -0
- package/src/agents/coder/gpt-pro.ts +1 -0
- package/src/agents/coder/gpt.ts +1 -0
- package/src/agents/dynamic-agent-core-sections.ts +36 -0
- package/src/agents/dynamic-agent-prompt-builder.ts +1 -0
- package/src/config/defaults.ts +171 -28
- package/src/config/loader.test.ts +16 -1
- package/src/config/loader.ts +4 -2
- package/src/config/model-slots-and-export.test.ts +55 -0
- package/src/config/platform-schema.ts +37 -5
- package/src/config/schema/commands.ts +1 -0
- package/src/config/schema/oh-my-opencode-config.ts +0 -3
- package/src/config/types.ts +34 -5
- package/src/create-tools.ts +4 -1
- package/src/features/builtin-commands/commands.ts +7 -0
- package/src/features/builtin-commands/templates/doctor.ts +43 -0
- package/src/features/builtin-commands/types.ts +1 -1
- package/src/features/builtin-skills/skills/hiai-opencode-setup.ts +69 -0
- package/src/features/builtin-skills/skills/index.ts +1 -0
- package/src/features/builtin-skills/skills.ts +10 -1
- package/src/index.ts +4 -38
- package/src/lsp/index.ts +1 -0
- package/src/mcp/registry.ts +6 -1
- package/src/plugin/tool-registry.ts +4 -0
- package/src/shared/mcp-static-export.ts +121 -0
- package/src/tools/ast-grep/constants.ts +1 -1
- package/src/tools/ast-grep/environment-check.ts +2 -32
- package/src/tools/ast-grep/language-support.ts +0 -3
- package/src/tools/ast-grep/types.ts +1 -2
- package/src/tools/skill-mcp/tools.test.ts +44 -0
- package/src/tools/skill-mcp/tools.ts +45 -7
- package/dist/ast-grep-napi.win32-x64-msvc-67c0y8nc.node +0 -0
- package/dist/config/loader.test.d.ts +0 -1
- package/dist/config/models.d.ts +0 -13
- package/dist/internals/plugins/websearch-cited/google.d.ts +0 -38
- package/dist/internals/plugins/websearch-cited/index.d.ts +0 -11
- package/dist/internals/plugins/websearch-cited/openai.d.ts +0 -9
- package/dist/internals/plugins/websearch-cited/openrouter.d.ts +0 -2
- package/dist/internals/plugins/websearch-cited/types.d.ts +0 -5
- package/src/internals/plugins/websearch-cited/LICENSE +0 -214
- package/src/internals/plugins/websearch-cited/codex_prompt.txt +0 -79
- package/src/internals/plugins/websearch-cited/google.ts +0 -749
- package/src/internals/plugins/websearch-cited/index.ts +0 -301
- package/src/internals/plugins/websearch-cited/openai.ts +0 -407
- package/src/internals/plugins/websearch-cited/openrouter.ts +0 -190
- package/src/internals/plugins/websearch-cited/types.ts +0 -7
package/src/config/defaults.ts
CHANGED
|
@@ -1,23 +1,61 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Runtime defaults for hiai-opencode.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - agent models
|
|
7
|
-
* - category models
|
|
8
|
-
* - user-editable runtime defaults
|
|
4
|
+
* The user-facing config owns model choice through 10 primary agent slots.
|
|
5
|
+
* Internal routing derives hidden agents and task categories from those slots.
|
|
9
6
|
*/
|
|
10
7
|
import { existsSync, readFileSync } from "node:fs"
|
|
11
8
|
import { dirname, join, normalize } from "node:path"
|
|
12
|
-
import
|
|
9
|
+
import { createDefaultMcpConfig } from "../mcp/registry.js"
|
|
10
|
+
import type { HiaiOpencodeConfig, LspServerConfig } from "./types.js"
|
|
11
|
+
|
|
12
|
+
const REQUIRED_MODEL_SLOTS = [
|
|
13
|
+
"bob",
|
|
14
|
+
"coder",
|
|
15
|
+
"strategist",
|
|
16
|
+
"guard",
|
|
17
|
+
"critic",
|
|
18
|
+
"designer",
|
|
19
|
+
"researcher",
|
|
20
|
+
"manager",
|
|
21
|
+
"brainstormer",
|
|
22
|
+
"vision",
|
|
23
|
+
] as const
|
|
24
|
+
|
|
25
|
+
type ModelSlot = (typeof REQUIRED_MODEL_SLOTS)[number]
|
|
26
|
+
|
|
27
|
+
const DEFAULT_LSP: Record<string, LspServerConfig> = {
|
|
28
|
+
typescript: {
|
|
29
|
+
enabled: true,
|
|
30
|
+
command: ["typescript-language-server", "--stdio"],
|
|
31
|
+
extensions: [".ts", ".tsx", ".mts", ".cts"],
|
|
32
|
+
},
|
|
33
|
+
svelte: {
|
|
34
|
+
enabled: true,
|
|
35
|
+
command: ["svelteserver", "--stdio"],
|
|
36
|
+
extensions: [".svelte"],
|
|
37
|
+
},
|
|
38
|
+
eslint: {
|
|
39
|
+
enabled: true,
|
|
40
|
+
command: ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "eslint-lsp", "--stdio"],
|
|
41
|
+
extensions: [".js", ".jsx", ".ts", ".tsx", ".mjs", ".cjs", ".svelte"],
|
|
42
|
+
},
|
|
43
|
+
bash: {
|
|
44
|
+
enabled: true,
|
|
45
|
+
command: ["node", "{pluginRoot}/assets/runtime/npm-package-runner.mjs", "bash-language-server", "start"],
|
|
46
|
+
extensions: [".sh", ".bash"],
|
|
47
|
+
},
|
|
48
|
+
pyright: {
|
|
49
|
+
enabled: true,
|
|
50
|
+
command: ["pyright-langserver", "--stdio"],
|
|
51
|
+
extensions: [".py"],
|
|
52
|
+
},
|
|
53
|
+
}
|
|
13
54
|
|
|
14
55
|
function findPluginRoot(): string {
|
|
15
56
|
const candidates = [
|
|
16
|
-
// Source tree: src/config/defaults.ts -> repo root
|
|
17
57
|
join(import.meta.dirname, "..", ".."),
|
|
18
|
-
// Built package: dist/index.js bundle -> package root
|
|
19
58
|
join(import.meta.dirname, ".."),
|
|
20
|
-
// Non-bundled compiled output: dist/config/defaults.js -> package root
|
|
21
59
|
join(import.meta.dirname, "..", ".."),
|
|
22
60
|
dirname(process.argv[1] ?? ""),
|
|
23
61
|
process.cwd(),
|
|
@@ -25,35 +63,135 @@ function findPluginRoot(): string {
|
|
|
25
63
|
|
|
26
64
|
for (const candidate of candidates) {
|
|
27
65
|
const root = normalize(candidate)
|
|
28
|
-
if (existsSync(join(root, "hiai-opencode.json")))
|
|
29
|
-
return root
|
|
30
|
-
}
|
|
66
|
+
if (existsSync(join(root, "hiai-opencode.json"))) return root
|
|
31
67
|
}
|
|
32
68
|
|
|
33
|
-
throw new Error(
|
|
34
|
-
"[hiai-opencode] Cannot find bundled hiai-opencode.json. The package is incomplete.",
|
|
35
|
-
)
|
|
69
|
+
throw new Error("[hiai-opencode] Cannot find bundled hiai-opencode.json. The package is incomplete.")
|
|
36
70
|
}
|
|
37
71
|
|
|
38
72
|
function expandPluginRootPlaceholders(value: unknown, pluginRoot: string): unknown {
|
|
39
|
-
if (typeof value === "string") {
|
|
40
|
-
|
|
73
|
+
if (typeof value === "string") return value.replaceAll("{pluginRoot}", pluginRoot)
|
|
74
|
+
if (Array.isArray(value)) return value.map((item) => expandPluginRootPlaceholders(item, pluginRoot))
|
|
75
|
+
if (value && typeof value === "object") {
|
|
76
|
+
return Object.fromEntries(
|
|
77
|
+
Object.entries(value).map(([key, entry]) => [key, expandPluginRootPlaceholders(entry, pluginRoot)]),
|
|
78
|
+
)
|
|
41
79
|
}
|
|
80
|
+
return value
|
|
81
|
+
}
|
|
42
82
|
|
|
43
|
-
|
|
44
|
-
|
|
83
|
+
function requireModelSlots(config: HiaiOpencodeConfig): Record<ModelSlot, string> {
|
|
84
|
+
const models = config.models ?? {}
|
|
85
|
+
const resolved = Object.fromEntries(
|
|
86
|
+
REQUIRED_MODEL_SLOTS.map((slot) => {
|
|
87
|
+
const value = models[slot]
|
|
88
|
+
const model = typeof value === "string" ? value : value?.model
|
|
89
|
+
return [slot, model?.trim() ?? ""]
|
|
90
|
+
}),
|
|
91
|
+
) as Record<ModelSlot, string>
|
|
92
|
+
|
|
93
|
+
const missing = REQUIRED_MODEL_SLOTS.filter((slot) => !resolved[slot])
|
|
94
|
+
if (missing.length > 0) {
|
|
95
|
+
throw new Error(`[hiai-opencode] Missing required model slot(s) in hiai-opencode.json: ${missing.join(", ")}`)
|
|
45
96
|
}
|
|
97
|
+
return resolved
|
|
98
|
+
}
|
|
46
99
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
100
|
+
function deriveAgents(models: Record<ModelSlot, string>): HiaiOpencodeConfig["agents"] {
|
|
101
|
+
return {
|
|
102
|
+
bob: { model: models.bob },
|
|
103
|
+
coder: { model: models.coder },
|
|
104
|
+
strategist: { model: models.strategist },
|
|
105
|
+
guard: { model: models.guard },
|
|
106
|
+
critic: { model: models.critic },
|
|
107
|
+
designer: { model: models.designer },
|
|
108
|
+
researcher: { model: models.researcher },
|
|
109
|
+
"platform-manager": { model: models.manager },
|
|
110
|
+
brainstormer: { model: models.brainstormer },
|
|
111
|
+
multimodal: { model: models.vision },
|
|
112
|
+
sub: { model: models.coder },
|
|
113
|
+
"quality-guardian": { model: models.critic },
|
|
114
|
+
"agent-skills": { model: models.manager },
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function deriveCategories(models: Record<ModelSlot, string>): HiaiOpencodeConfig["categories"] {
|
|
119
|
+
return {
|
|
120
|
+
"visual-engineering": { model: models.designer, variant: "high" },
|
|
121
|
+
artistry: { model: models.designer, variant: "high" },
|
|
122
|
+
ultrabrain: { model: models.strategist, variant: "xhigh" },
|
|
123
|
+
deep: { model: models.coder, variant: "medium" },
|
|
124
|
+
quick: { model: models.researcher },
|
|
125
|
+
writing: { model: models.brainstormer },
|
|
126
|
+
git: { model: models.manager },
|
|
127
|
+
"unspecified-low": { model: models.coder },
|
|
128
|
+
"unspecified-high": { model: models.bob, variant: "max" },
|
|
54
129
|
}
|
|
130
|
+
}
|
|
55
131
|
|
|
56
|
-
|
|
132
|
+
function deriveMcp(config: HiaiOpencodeConfig): HiaiOpencodeConfig["mcp"] {
|
|
133
|
+
const defaults = createDefaultMcpConfig()
|
|
134
|
+
const userMcp = config.mcp ?? {}
|
|
135
|
+
return Object.fromEntries(
|
|
136
|
+
Object.entries(defaults).map(([name, entry]) => {
|
|
137
|
+
const override = userMcp[name] ?? {}
|
|
138
|
+
const merged = { ...entry, ...override }
|
|
139
|
+
|
|
140
|
+
if (name === "mempalace" && typeof merged.pythonPath === "string" && merged.pythonPath.trim()) {
|
|
141
|
+
merged.environment = {
|
|
142
|
+
...(entry.environment ?? {}),
|
|
143
|
+
...(merged.environment ?? {}),
|
|
144
|
+
MEMPALACE_PYTHON: merged.pythonPath.trim(),
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Internal launcher hint; do not leak unknown keys into final OpenCode MCP server config.
|
|
149
|
+
delete merged.pythonPath
|
|
150
|
+
|
|
151
|
+
return [name, merged]
|
|
152
|
+
}),
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function deriveLsp(config: HiaiOpencodeConfig): HiaiOpencodeConfig["lsp"] {
|
|
157
|
+
const userLsp = config.lsp ?? {}
|
|
158
|
+
return Object.fromEntries(
|
|
159
|
+
Object.entries(DEFAULT_LSP).map(([name, entry]) => {
|
|
160
|
+
const override = userLsp[name] ?? {}
|
|
161
|
+
return [name, { ...entry, ...override }]
|
|
162
|
+
}),
|
|
163
|
+
)
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function materializeConfig(rawConfig: HiaiOpencodeConfig): HiaiOpencodeConfig {
|
|
167
|
+
const models = requireModelSlots(rawConfig)
|
|
168
|
+
return {
|
|
169
|
+
...rawConfig,
|
|
170
|
+
agents: deriveAgents(models),
|
|
171
|
+
agentRequirements: {},
|
|
172
|
+
categories: deriveCategories(models),
|
|
173
|
+
categoryRequirements: {},
|
|
174
|
+
modelFamilies: [],
|
|
175
|
+
mcp: deriveMcp(rawConfig),
|
|
176
|
+
lsp: deriveLsp(rawConfig),
|
|
177
|
+
subtask2: {
|
|
178
|
+
replace_generic: true,
|
|
179
|
+
generic_return: null,
|
|
180
|
+
...(rawConfig.subtask2 ?? {}),
|
|
181
|
+
},
|
|
182
|
+
skills: {
|
|
183
|
+
enabled: true,
|
|
184
|
+
disabled: [],
|
|
185
|
+
...(rawConfig.skills ?? {}),
|
|
186
|
+
},
|
|
187
|
+
permissions: {
|
|
188
|
+
read: { "*": "allow", "*.env": "deny", "*.env.*": "deny", "*.env.example": "allow" },
|
|
189
|
+
edit: { "*": "allow" },
|
|
190
|
+
bash: { "*": "allow" },
|
|
191
|
+
deny_paths: ["**/backup/**", "**/secrets.*", "**/.env", "**/.env.*"],
|
|
192
|
+
...(rawConfig.permissions ?? {}),
|
|
193
|
+
},
|
|
194
|
+
}
|
|
57
195
|
}
|
|
58
196
|
|
|
59
197
|
function loadBundledDefaultConfig(): HiaiOpencodeConfig {
|
|
@@ -61,8 +199,13 @@ function loadBundledDefaultConfig(): HiaiOpencodeConfig {
|
|
|
61
199
|
const configPath = join(pluginRoot, "hiai-opencode.json")
|
|
62
200
|
const raw = readFileSync(configPath, "utf-8")
|
|
63
201
|
const parsed = JSON.parse(raw) as HiaiOpencodeConfig
|
|
202
|
+
const materialized = materializeConfig(parsed)
|
|
203
|
+
|
|
204
|
+
return expandPluginRootPlaceholders(materialized, pluginRoot) as HiaiOpencodeConfig
|
|
205
|
+
}
|
|
64
206
|
|
|
65
|
-
|
|
207
|
+
export function applyModelSlots(config: HiaiOpencodeConfig): HiaiOpencodeConfig {
|
|
208
|
+
return materializeConfig(config)
|
|
66
209
|
}
|
|
67
210
|
|
|
68
211
|
export const defaultConfig: HiaiOpencodeConfig = loadBundledDefaultConfig()
|
|
@@ -28,9 +28,23 @@ function withTempProject(
|
|
|
28
28
|
}
|
|
29
29
|
}
|
|
30
30
|
|
|
31
|
+
const requiredModels = {
|
|
32
|
+
bob: { model: "openrouter/test/bob", recommended: "xhigh" },
|
|
33
|
+
coder: { model: "openrouter/test/coder", recommended: "high" },
|
|
34
|
+
strategist: { model: "openrouter/test/strategist", recommended: "high" },
|
|
35
|
+
guard: "openrouter/test/guard",
|
|
36
|
+
critic: "openrouter/test/critic",
|
|
37
|
+
designer: "openrouter/test/designer",
|
|
38
|
+
researcher: "openrouter/test/researcher",
|
|
39
|
+
manager: "openrouter/test/manager",
|
|
40
|
+
brainstormer: "openrouter/test/brainstormer",
|
|
41
|
+
vision: "openrouter/test/vision",
|
|
42
|
+
};
|
|
43
|
+
|
|
31
44
|
test("loadConfig accepts compact enabled-only LSP entries for builtin servers", () => {
|
|
32
45
|
withTempProject(
|
|
33
46
|
{
|
|
47
|
+
models: requiredModels,
|
|
34
48
|
lsp: {
|
|
35
49
|
typescript: {
|
|
36
50
|
enabled: true,
|
|
@@ -42,7 +56,7 @@ test("loadConfig accepts compact enabled-only LSP entries for builtin servers",
|
|
|
42
56
|
const tsServer = loaded.lsp?.typescript;
|
|
43
57
|
|
|
44
58
|
expect(tsServer).toBeDefined();
|
|
45
|
-
expect(tsServer?.command
|
|
59
|
+
expect(tsServer?.command?.length).toBeGreaterThan(0);
|
|
46
60
|
expect(tsServer?.extensions).toContain(".ts");
|
|
47
61
|
},
|
|
48
62
|
);
|
|
@@ -51,6 +65,7 @@ test("loadConfig accepts compact enabled-only LSP entries for builtin servers",
|
|
|
51
65
|
test("loadConfig ignores unknown compact enabled-only LSP entries", () => {
|
|
52
66
|
withTempProject(
|
|
53
67
|
{
|
|
68
|
+
models: requiredModels,
|
|
54
69
|
lsp: {
|
|
55
70
|
"custom-server": {
|
|
56
71
|
enabled: true,
|
package/src/config/loader.ts
CHANGED
|
@@ -2,7 +2,7 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
2
2
|
import { join } from "node:path";
|
|
3
3
|
import { parse } from "jsonc-parser";
|
|
4
4
|
import { HiaiOpencodeConfigSchema } from "./platform-schema.js";
|
|
5
|
-
import { defaultConfig } from "./defaults.js";
|
|
5
|
+
import { applyModelSlots, defaultConfig } from "./defaults.js";
|
|
6
6
|
import {
|
|
7
7
|
LEGACY_AGENT_ALIAS_TO_CANONICAL,
|
|
8
8
|
type CanonicalAgentName,
|
|
@@ -157,10 +157,12 @@ export function loadConfig(projectDir: string): HiaiOpencodeConfig {
|
|
|
157
157
|
const validated = HiaiOpencodeConfigSchema.parse(normalizedParsed);
|
|
158
158
|
const normalized = normalizeAgentAliases(validated);
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
const merged = deepMerge(
|
|
161
161
|
BASE_CONFIG as unknown as Record<string, unknown>,
|
|
162
162
|
normalized as unknown as Record<string, unknown>,
|
|
163
163
|
) as HiaiOpencodeConfig;
|
|
164
|
+
|
|
165
|
+
return applyModelSlots(merged);
|
|
164
166
|
}
|
|
165
167
|
|
|
166
168
|
export function resolveEnvVars(value: string): string {
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { expect, test } from "bun:test"
|
|
2
|
+
|
|
3
|
+
import { applyModelSlots } from "./defaults"
|
|
4
|
+
import type { HiaiOpencodeConfig } from "./types"
|
|
5
|
+
import { buildStaticMcpJson, MCP_EXPORT_MARKER } from "../shared/mcp-static-export"
|
|
6
|
+
import { buildHiaiIntegrationPrimerSection } from "../agents/dynamic-agent-core-sections"
|
|
7
|
+
|
|
8
|
+
const baseConfig: HiaiOpencodeConfig = {
|
|
9
|
+
models: {
|
|
10
|
+
bob: { model: "openrouter/test/bob", recommended: "xhigh" },
|
|
11
|
+
coder: { model: "openrouter/test/coder", recommended: "high" },
|
|
12
|
+
strategist: { model: "openrouter/test/strategist", recommended: "high" },
|
|
13
|
+
guard: { model: "openrouter/test/guard", recommended: "middle" },
|
|
14
|
+
critic: { model: "openrouter/test/critic", recommended: "high" },
|
|
15
|
+
designer: { model: "openrouter/test/designer", recommended: "design" },
|
|
16
|
+
researcher: { model: "openrouter/test/researcher", recommended: "fast" },
|
|
17
|
+
manager: { model: "openrouter/test/manager", recommended: "fast" },
|
|
18
|
+
brainstormer: { model: "openrouter/test/brainstormer", recommended: "writing" },
|
|
19
|
+
vision: { model: "openrouter/test/vision", recommended: "vision" },
|
|
20
|
+
},
|
|
21
|
+
mcp: {
|
|
22
|
+
mempalace: { enabled: true, pythonPath: "/opt/venv/bin/python" },
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
test("model slots derive canonical agents and categories", () => {
|
|
27
|
+
const resolved = applyModelSlots(baseConfig)
|
|
28
|
+
expect(resolved.agents?.bob?.model).toBe("openrouter/test/bob")
|
|
29
|
+
expect(resolved.agents?.coder?.model).toBe("openrouter/test/coder")
|
|
30
|
+
expect(resolved.agents?.["platform-manager"]?.model).toBe("openrouter/test/manager")
|
|
31
|
+
expect(resolved.agents?.sub?.model).toBe("openrouter/test/coder")
|
|
32
|
+
expect(resolved.categories?.artistry?.model).toBe("openrouter/test/designer")
|
|
33
|
+
expect(resolved.categories?.ultrabrain?.model).toBe("openrouter/test/strategist")
|
|
34
|
+
expect(resolved.categories?.writing?.model).toBe("openrouter/test/brainstormer")
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
test("compact MCP mempalace pythonPath is materialized into environment", () => {
|
|
38
|
+
const resolved = applyModelSlots(baseConfig)
|
|
39
|
+
expect(resolved.mcp?.mempalace?.environment?.MEMPALACE_PYTHON).toBe("/opt/venv/bin/python")
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
test("static MCP export includes marker metadata and servers", () => {
|
|
43
|
+
const resolved = applyModelSlots(baseConfig)
|
|
44
|
+
const exported = buildStaticMcpJson(resolved)
|
|
45
|
+
|
|
46
|
+
expect(exported._meta?.generatedBy).toBe(MCP_EXPORT_MARKER)
|
|
47
|
+
expect(exported._meta?.version).toBe(1)
|
|
48
|
+
expect(exported.mcpServers.playwright).toBeDefined()
|
|
49
|
+
expect(exported.mcpServers.mempalace).toBeDefined()
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test("integration primer does not request model provider API keys", () => {
|
|
53
|
+
const primer = buildHiaiIntegrationPrimerSection()
|
|
54
|
+
expect(primer).toContain("Do not ask for `OPENROUTER_API_KEY`, `OPENAI_API_KEY`, or `ANTHROPIC_API_KEY`")
|
|
55
|
+
})
|
|
@@ -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(),
|
|
@@ -45,11 +76,13 @@ export const McpServerConfigSchema = z.object({
|
|
|
45
76
|
command: z.array(z.string()).optional(),
|
|
46
77
|
timeout: z.number().optional(),
|
|
47
78
|
environment: z.record(z.string(), z.string()).optional(),
|
|
79
|
+
pythonPath: z.string().optional(),
|
|
48
80
|
});
|
|
49
81
|
|
|
50
82
|
export const LspServerConfigSchema = z.object({
|
|
51
|
-
|
|
52
|
-
|
|
83
|
+
enabled: z.boolean().optional(),
|
|
84
|
+
command: z.array(z.string()).optional(),
|
|
85
|
+
extensions: z.array(z.string()).optional(),
|
|
53
86
|
initialization: z.record(z.string(), z.unknown()).optional(),
|
|
54
87
|
});
|
|
55
88
|
|
|
@@ -97,11 +130,9 @@ export const PermissionsConfigSchema = z.object({
|
|
|
97
130
|
});
|
|
98
131
|
|
|
99
132
|
export const AuthKeysSchema = z.object({
|
|
100
|
-
googleSearch: z.string().optional(),
|
|
101
|
-
openai: z.string().optional(),
|
|
102
|
-
openrouter: z.string().optional(),
|
|
103
133
|
stitch: z.string().optional(),
|
|
104
134
|
firecrawl: z.string().optional(),
|
|
135
|
+
context7: z.string().optional(),
|
|
105
136
|
});
|
|
106
137
|
|
|
107
138
|
export const OllamaConfigSchema = z.object({
|
|
@@ -190,6 +221,7 @@ const AgentRequirementsConfigSchema = z.object({
|
|
|
190
221
|
|
|
191
222
|
export const HiaiOpencodeConfigSchema = z.object({
|
|
192
223
|
$schema: z.string().optional(),
|
|
224
|
+
models: ModelSlotsConfigSchema.optional(),
|
|
193
225
|
agents: AgentsConfigSchema.optional(),
|
|
194
226
|
agentRequirements: AgentRequirementsConfigSchema.optional(),
|
|
195
227
|
categories: z.record(z.string(), CategoryConfigSchema).optional(),
|
|
@@ -26,9 +26,6 @@ import { FastApplyConfigSchema } from "./fast-apply"
|
|
|
26
26
|
import { WebsearchConfigSchema } from "./websearch"
|
|
27
27
|
|
|
28
28
|
const AuthConfigSchema = z.object({
|
|
29
|
-
googleSearch: z.string().optional(),
|
|
30
|
-
openai: z.string().optional(),
|
|
31
|
-
openrouter: z.string().optional(),
|
|
32
29
|
stitch: z.string().optional(),
|
|
33
30
|
firecrawl: z.string().optional(),
|
|
34
31
|
context7: z.string().optional(),
|
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",
|
|
@@ -121,11 +150,13 @@ export interface McpServerConfig {
|
|
|
121
150
|
command?: string[];
|
|
122
151
|
timeout?: number;
|
|
123
152
|
environment?: Record<string, string>;
|
|
153
|
+
pythonPath?: string;
|
|
124
154
|
}
|
|
125
155
|
|
|
126
156
|
export interface LspServerConfig {
|
|
127
|
-
|
|
128
|
-
|
|
157
|
+
enabled?: boolean;
|
|
158
|
+
command?: string[];
|
|
159
|
+
extensions?: string[];
|
|
129
160
|
initialization?: Record<string, unknown>;
|
|
130
161
|
}
|
|
131
162
|
|
|
@@ -157,9 +188,6 @@ export interface PermissionsConfig {
|
|
|
157
188
|
}
|
|
158
189
|
|
|
159
190
|
export interface AuthKeys {
|
|
160
|
-
googleSearch?: string;
|
|
161
|
-
openai?: string;
|
|
162
|
-
openrouter?: string;
|
|
163
191
|
stitch?: string;
|
|
164
192
|
firecrawl?: string;
|
|
165
193
|
context7?: string;
|
|
@@ -174,6 +202,7 @@ export interface OllamaConfig {
|
|
|
174
202
|
|
|
175
203
|
export interface HiaiOpencodeConfig {
|
|
176
204
|
$schema?: string;
|
|
205
|
+
models?: ModelSlotsConfig;
|
|
177
206
|
agents?: AgentConfigMap;
|
|
178
207
|
agentRequirements?: AgentRequirementMap;
|
|
179
208
|
categories?: Record<string, CategoryConfig>;
|
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 {
|
|
@@ -9,6 +9,7 @@ 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
11
|
import { MCP_STATUS_TEMPLATE } from "./templates/mcp-status"
|
|
12
|
+
import { DOCTOR_TEMPLATE } from "./templates/doctor"
|
|
12
13
|
|
|
13
14
|
interface LoadBuiltinCommandsOptions {
|
|
14
15
|
useRegisteredAgents?: boolean
|
|
@@ -126,6 +127,12 @@ $ARGUMENTS
|
|
|
126
127
|
description: "(builtin) Show hiai-opencode MCP server status, missing keys, and local runtime availability",
|
|
127
128
|
template: `<command-instruction>
|
|
128
129
|
${MCP_STATUS_TEMPLATE}
|
|
130
|
+
</command-instruction>`,
|
|
131
|
+
},
|
|
132
|
+
doctor: {
|
|
133
|
+
description: "(builtin) Run hiai-opencode install/runtime diagnostics and explain setup issues",
|
|
134
|
+
template: `<command-instruction>
|
|
135
|
+
${DOCTOR_TEMPLATE}
|
|
129
136
|
</command-instruction>`,
|
|
130
137
|
},
|
|
131
138
|
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export const DOCTOR_TEMPLATE = `# Hiai OpenCode Doctor Command
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Use /doctor to run the hiai-opencode install/runtime diagnostic and report actionable setup issues.
|
|
6
|
+
|
|
7
|
+
## Execute
|
|
8
|
+
|
|
9
|
+
Run:
|
|
10
|
+
|
|
11
|
+
\`\`\`bash
|
|
12
|
+
hiai-opencode doctor
|
|
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 doctor
|
|
19
|
+
\`\`\`
|
|
20
|
+
|
|
21
|
+
## Report
|
|
22
|
+
|
|
23
|
+
Summarize:
|
|
24
|
+
|
|
25
|
+
- config path
|
|
26
|
+
- enabled and disabled MCP servers
|
|
27
|
+
- missing env vars by name only
|
|
28
|
+
- static \`.mcp.json\` freshness and whether it is managed by hiai-opencode
|
|
29
|
+
- OpenCode Connect visibility for configured model providers
|
|
30
|
+
- OpenCode plugin registration sanity (including \`plugin: ["list"]\` misconfiguration warning)
|
|
31
|
+
- skill materialization status from skill registry
|
|
32
|
+
- agent count and naming summary
|
|
33
|
+
- LSP runtime availability
|
|
34
|
+
- MemPalace python source and selected interpreter (env/config/auto)
|
|
35
|
+
- MCP tool probes (real connect + tools/list) for stdio and basic endpoint probes for remote MCP
|
|
36
|
+
|
|
37
|
+
Rules:
|
|
38
|
+
|
|
39
|
+
- Do not print API key values.
|
|
40
|
+
- Do not ask for model provider env vars such as OPENROUTER_API_KEY or OPENAI_API_KEY; normal model auth belongs to OpenCode Connect.
|
|
41
|
+
- If \`opencode mcp list\` is empty but doctor/mcp-status sees servers, explain the runtime-vs-static config distinction and run \`hiai-opencode export-mcp .mcp.json\` if the user wants static visibility.
|
|
42
|
+
- Do not run package installs unless the user explicitly asks.
|
|
43
|
+
`
|
|
@@ -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" | "mcp-status"
|
|
3
|
+
export type BuiltinCommandName = "init-deep" | "ralph-loop" | "cancel-ralph" | "ulw-loop" | "refactor" | "start-work" | "stop-continuation" | "handoff" | "remove-ai-slops" | "mcp-status" | "doctor"
|
|
4
4
|
|
|
5
5
|
export interface BuiltinCommandConfig {
|
|
6
6
|
disabled_commands?: BuiltinCommandName[]
|