@hiai-gg/hiai-opencode 0.1.5 → 0.1.7

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 (180) hide show
  1. package/.env.example +21 -8
  2. package/AGENTS.md +60 -6
  3. package/ARCHITECTURE.md +6 -3
  4. package/LICENSE.md +0 -1
  5. package/README.md +113 -33
  6. package/assets/cli/hiai-opencode.mjs +668 -7
  7. package/assets/mcp/mempalace.mjs +159 -25
  8. package/config/hiai-opencode.schema.json +29 -3
  9. package/dist/agents/agent-skills.d.ts +7 -0
  10. package/dist/agents/bob/default.d.ts +1 -0
  11. package/dist/agents/bob/gemini.d.ts +1 -0
  12. package/dist/agents/bob/gpt-pro.d.ts +1 -0
  13. package/dist/agents/brainstormer.d.ts +7 -0
  14. package/dist/agents/coder/gpt-codex.d.ts +1 -1
  15. package/dist/agents/coder/gpt-pro.d.ts +1 -0
  16. package/dist/agents/coder/gpt.d.ts +2 -1
  17. package/dist/agents/designer.d.ts +7 -0
  18. package/dist/agents/dynamic-agent-core-sections.d.ts +4 -1
  19. package/dist/agents/dynamic-agent-prompt-builder.d.ts +1 -1
  20. package/dist/agents/strategist/gemini.d.ts +1 -0
  21. package/dist/agents/strategist/gpt.d.ts +1 -0
  22. package/dist/agents/types.d.ts +3 -1
  23. package/dist/config/index.d.ts +0 -1
  24. package/dist/config/platform-schema.d.ts +34 -6
  25. package/dist/config/schema/commands.d.ts +1 -0
  26. package/dist/config/schema/hooks.d.ts +0 -2
  27. package/dist/config/schema/index.d.ts +0 -2
  28. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -9
  29. package/dist/config/types.d.ts +4 -4
  30. package/dist/create-hooks.d.ts +0 -2
  31. package/dist/features/builtin-commands/templates/doctor.d.ts +1 -0
  32. package/dist/features/builtin-commands/types.d.ts +1 -1
  33. package/dist/features/builtin-skills/skills/hiai-opencode-setup.d.ts +2 -0
  34. package/dist/features/builtin-skills/skills/index.d.ts +2 -0
  35. package/dist/features/builtin-skills/skills/website-copywriting.d.ts +2 -0
  36. package/dist/hooks/agent-usage-reminder/constants.d.ts +1 -1
  37. package/dist/hooks/index.d.ts +0 -2
  38. package/dist/hooks/keyword-detector/ultrawork/default.d.ts +1 -1
  39. package/dist/hooks/keyword-detector/ultrawork/gemini.d.ts +1 -1
  40. package/dist/hooks/keyword-detector/ultrawork/gpt.d.ts +1 -1
  41. package/dist/hooks/keyword-detector/ultrawork/planner.d.ts +1 -1
  42. package/dist/index.js +7719 -153698
  43. package/dist/mcp/index.d.ts +0 -1
  44. package/dist/mcp/registry.d.ts +1 -1
  45. package/dist/plugin/hooks/create-core-hooks.d.ts +0 -2
  46. package/dist/plugin/hooks/create-session-hooks.d.ts +1 -3
  47. package/dist/plugin/startup-diagnostics.d.ts +1 -0
  48. package/dist/shared/logger.d.ts +2 -0
  49. package/dist/shared/mcp-static-export.d.ts +22 -0
  50. package/dist/shared/mode-routing.d.ts +6 -0
  51. package/dist/tools/ast-grep/constants.d.ts +1 -1
  52. package/dist/tools/ast-grep/environment-check.d.ts +1 -5
  53. package/dist/tools/ast-grep/language-support.d.ts +0 -1
  54. package/dist/tools/ast-grep/types.d.ts +1 -2
  55. package/dist/tools/delegate-task/git-categories.d.ts +2 -0
  56. package/dist/tools/delegate-task/sub-agent.d.ts +2 -0
  57. package/dist/tools/skill-mcp/constants.d.ts +1 -1
  58. package/hiai-opencode.json +50 -19
  59. package/package.json +10 -5
  60. package/src/agents/agent-skills.ts +70 -0
  61. package/src/agents/bob/default.ts +7 -1
  62. package/src/agents/bob/gemini.ts +1 -0
  63. package/src/agents/bob/gpt-pro.ts +3 -1
  64. package/src/agents/bob.ts +3 -0
  65. package/src/agents/brainstormer.ts +72 -0
  66. package/src/agents/builtin-agents.ts +59 -3
  67. package/src/agents/coder/gpt-codex.ts +5 -3
  68. package/src/agents/coder/gpt-pro.ts +4 -2
  69. package/src/agents/coder/gpt.ts +3 -1
  70. package/src/agents/critic/agent.ts +1 -0
  71. package/src/agents/designer.ts +70 -0
  72. package/src/agents/dynamic-agent-category-skills-guide.ts +6 -0
  73. package/src/agents/dynamic-agent-core-sections.ts +36 -0
  74. package/src/agents/dynamic-agent-prompt-builder.ts +1 -0
  75. package/src/agents/guard/default.ts +1 -0
  76. package/src/agents/guard/gemini.ts +1 -0
  77. package/src/agents/guard/gpt.ts +1 -0
  78. package/src/agents/platform-manager.ts +17 -1
  79. package/src/agents/prompt-library/platform.ts +34 -0
  80. package/src/agents/researcher.ts +1 -0
  81. package/src/agents/strategist/gemini.ts +1 -0
  82. package/src/agents/strategist/gpt.ts +1 -0
  83. package/src/agents/types.ts +4 -1
  84. package/src/agents/ui.ts +1 -0
  85. package/src/config/defaults.ts +45 -13
  86. package/src/config/index.ts +0 -1
  87. package/src/config/model-slots-and-export.test.ts +73 -0
  88. package/src/config/platform-schema.ts +3 -3
  89. package/src/config/schema/commands.ts +1 -0
  90. package/src/config/schema/hooks.ts +0 -2
  91. package/src/config/schema/index.ts +0 -2
  92. package/src/config/schema/oh-my-opencode-config.ts +0 -5
  93. package/src/config/types.ts +4 -4
  94. package/src/features/builtin-commands/commands.ts +7 -0
  95. package/src/features/builtin-commands/templates/doctor.ts +43 -0
  96. package/src/features/builtin-commands/types.ts +1 -1
  97. package/src/features/builtin-skills/skills/hiai-opencode-setup.ts +69 -0
  98. package/src/features/builtin-skills/skills/index.ts +2 -0
  99. package/src/features/builtin-skills/skills/website-copywriting.ts +41 -0
  100. package/src/features/builtin-skills/skills.test.ts +8 -0
  101. package/src/features/builtin-skills/skills.ts +12 -1
  102. package/src/features/skill-mcp-manager/AGENTS.md +1 -1
  103. package/src/hooks/agent-usage-reminder/constants.ts +4 -4
  104. package/src/hooks/index.ts +0 -2
  105. package/src/hooks/keyword-detector/ultrawork/default.ts +18 -18
  106. package/src/hooks/keyword-detector/ultrawork/gemini.ts +21 -21
  107. package/src/hooks/keyword-detector/ultrawork/gpt.ts +6 -8
  108. package/src/hooks/keyword-detector/ultrawork/planner.ts +5 -5
  109. package/src/index.ts +8 -78
  110. package/src/internals/plugins/subtask2/commands/manifest.ts +2 -6
  111. package/src/internals/plugins/subtask2/hooks/command-hooks.ts +2 -2
  112. package/src/internals/plugins/subtask2/hooks/message-hooks.ts +1 -1
  113. package/src/internals/plugins/subtask2/parsing/parallel.ts +13 -10
  114. package/src/mcp/index.ts +0 -1
  115. package/src/mcp/registry.ts +27 -0
  116. package/src/plugin/chat-message.ts +0 -2
  117. package/src/plugin/hooks/create-session-hooks.ts +0 -17
  118. package/src/plugin/startup-diagnostics.ts +27 -0
  119. package/src/plugin-handlers/agent-config-handler.ts +3 -2
  120. package/src/plugin-handlers/mcp-config-handler.test.ts +63 -0
  121. package/src/plugin-handlers/mcp-config-handler.ts +29 -14
  122. package/src/plugin-handlers/strategist-agent-config-builder.ts +1 -1
  123. package/src/shared/agent-display-names.test.ts +9 -0
  124. package/src/shared/agent-display-names.ts +5 -0
  125. package/src/shared/log-legacy-plugin-startup-warning.ts +6 -8
  126. package/src/shared/logger.ts +8 -0
  127. package/src/shared/mcp-static-export.ts +119 -0
  128. package/src/shared/migration/agent-names.ts +8 -0
  129. package/src/shared/migration/hook-names.ts +1 -1
  130. package/src/shared/mode-routing.test.ts +88 -0
  131. package/src/shared/mode-routing.ts +30 -0
  132. package/src/shared/startup-diagnostics.ts +6 -7
  133. package/src/tools/ast-grep/constants.ts +1 -1
  134. package/src/tools/ast-grep/environment-check.ts +2 -32
  135. package/src/tools/ast-grep/language-support.ts +0 -3
  136. package/src/tools/ast-grep/types.ts +1 -2
  137. package/src/tools/call-omo-agent/tools.ts +11 -4
  138. package/src/tools/delegate-task/anthropic-categories.ts +3 -3
  139. package/src/tools/delegate-task/builtin-categories.ts +2 -0
  140. package/src/tools/delegate-task/categories.test.ts +87 -0
  141. package/src/tools/delegate-task/category-resolver.ts +8 -9
  142. package/src/tools/delegate-task/git-categories.ts +30 -0
  143. package/src/tools/delegate-task/model-string-parser.test.ts +90 -0
  144. package/src/tools/delegate-task/openai-categories.ts +26 -22
  145. package/src/tools/delegate-task/sub-agent.ts +10 -0
  146. package/src/tools/delegate-task/subagent-discovery.test.ts +123 -0
  147. package/src/tools/delegate-task/subagent-resolver.ts +18 -1
  148. package/src/tools/skill-mcp/constants.ts +1 -1
  149. package/src/tools/skill-mcp/tools.test.ts +44 -0
  150. package/dist/ast-grep-napi.win32-x64-msvc-67c0y8nc.node +0 -0
  151. package/dist/config/loader.test.d.ts +0 -1
  152. package/dist/config/models.d.ts +0 -13
  153. package/dist/config/schema/websearch.d.ts +0 -13
  154. package/dist/hooks/no-bob-gpt/hook.d.ts +0 -16
  155. package/dist/hooks/no-bob-gpt/index.d.ts +0 -1
  156. package/dist/hooks/no-coder-non-gpt/hook.d.ts +0 -20
  157. package/dist/hooks/no-coder-non-gpt/index.d.ts +0 -1
  158. package/dist/internals/plugins/websearch-cited/google.d.ts +0 -38
  159. package/dist/internals/plugins/websearch-cited/index.d.ts +0 -17
  160. package/dist/internals/plugins/websearch-cited/openai.d.ts +0 -9
  161. package/dist/internals/plugins/websearch-cited/openrouter.d.ts +0 -2
  162. package/dist/internals/plugins/websearch-cited/types.d.ts +0 -5
  163. package/dist/mcp/grep-app.d.ts +0 -6
  164. package/dist/mcp/omo-mcp-index.d.ts +0 -10
  165. package/dist/mcp/websearch.d.ts +0 -11
  166. package/src/config/schema/websearch.ts +0 -15
  167. package/src/hooks/no-bob-gpt/hook.ts +0 -56
  168. package/src/hooks/no-bob-gpt/index.ts +0 -1
  169. package/src/hooks/no-coder-non-gpt/hook.ts +0 -67
  170. package/src/hooks/no-coder-non-gpt/index.ts +0 -1
  171. package/src/internals/plugins/websearch-cited/LICENSE +0 -214
  172. package/src/internals/plugins/websearch-cited/codex_prompt.txt +0 -79
  173. package/src/internals/plugins/websearch-cited/google.ts +0 -749
  174. package/src/internals/plugins/websearch-cited/index.ts +0 -306
  175. package/src/internals/plugins/websearch-cited/openai.ts +0 -407
  176. package/src/internals/plugins/websearch-cited/openrouter.ts +0 -190
  177. package/src/internals/plugins/websearch-cited/types.ts +0 -7
  178. package/src/mcp/grep-app.ts +0 -6
  179. package/src/mcp/omo-mcp-index.ts +0 -30
  180. package/src/mcp/websearch.ts +0 -44
@@ -0,0 +1,90 @@
1
+ import { expect, test, describe } from "bun:test"
2
+ import { parseModelString, parseVariantFromModelID } from "./model-string-parser"
3
+
4
+ describe("parseModelString", () => {
5
+ test("parses model with variant", () => {
6
+ const result = parseModelString("openai/gpt-5.3-codex medium")
7
+ expect(result?.modelID).toBe("gpt-5.3-codex")
8
+ expect(result?.providerID).toBe("openai")
9
+ expect(result?.variant).toBe("medium")
10
+ })
11
+
12
+ test("parses model without variant", () => {
13
+ const result = parseModelString("openai/gpt-5.3-codex")
14
+ expect(result?.modelID).toBe("gpt-5.3-codex")
15
+ expect(result?.providerID).toBe("openai")
16
+ expect(result?.variant).toBeUndefined()
17
+ })
18
+
19
+ test("handles whitespace trimming", () => {
20
+ const result = parseModelString(" openai/gpt-5.3-codex high ")
21
+ expect(result?.modelID).toBe("gpt-5.3-codex")
22
+ expect(result?.variant).toBe("high")
23
+ })
24
+
25
+ test("handles empty string", () => {
26
+ const result = parseModelString("")
27
+ expect(result).toBeUndefined()
28
+ })
29
+
30
+ test("handles model with multiple spaces before variant", () => {
31
+ const result = parseModelString("anthropic/claude-3.5-sonnet high")
32
+ expect(result?.modelID).toBe("claude-3.5-sonnet")
33
+ expect(result?.variant).toBe("high")
34
+ })
35
+
36
+ test("handles provider/model with multiple slashes", () => {
37
+ const result = parseModelString("openrouter/anthropic/claude-3.5-sonnet ultra")
38
+ expect(result?.providerID).toBe("openrouter")
39
+ expect(result?.modelID).toBe("anthropic/claude-3.5-sonnet ultra")
40
+ })
41
+
42
+ test("returns model when no variant", () => {
43
+ const result = parseModelString("openrouter/google/gemini-2.0-flash")
44
+ expect(result?.modelID).toBe("google/gemini-2.0-flash")
45
+ expect(result?.providerID).toBe("openrouter")
46
+ expect(result?.variant).toBeUndefined()
47
+ })
48
+
49
+ test("single slash model returns undefined", () => {
50
+ const result = parseModelString("claude-medium")
51
+ expect(result).toBeUndefined()
52
+ })
53
+
54
+ test("model with xhigh variant", () => {
55
+ const result = parseModelString("openai/gpt-5.3-codex xhigh")
56
+ expect(result?.modelID).toBe("gpt-5.3-codex")
57
+ expect(result?.variant).toBe("xhigh")
58
+ })
59
+
60
+ test("model with max variant", () => {
61
+ const result = parseModelString("anthropic/claude-3.5-opus max")
62
+ expect(result?.modelID).toBe("claude-3.5-opus")
63
+ expect(result?.variant).toBe("max")
64
+ })
65
+ })
66
+
67
+ describe("parseVariantFromModelID", () => {
68
+ test("extracts parenthesized variant", () => {
69
+ const result = parseVariantFromModelID("gpt-5.3-codex (medium)")
70
+ expect(result.modelID).toBe("gpt-5.3-codex")
71
+ expect(result.variant).toBe("medium")
72
+ })
73
+
74
+ test("extracts space variant", () => {
75
+ const result = parseVariantFromModelID("gpt-5.3-codex medium")
76
+ expect(result.modelID).toBe("gpt-5.3-codex")
77
+ expect(result.variant).toBe("medium")
78
+ })
79
+
80
+ test("no variant when not recognized", () => {
81
+ const result = parseVariantFromModelID("gpt-5.3-codex unknownvariant")
82
+ expect(result.modelID).toBe("gpt-5.3-codex unknownvariant")
83
+ expect(result.variant).toBeUndefined()
84
+ })
85
+
86
+ test("handles empty string", () => {
87
+ const result = parseVariantFromModelID("")
88
+ expect(result.modelID).toBe("")
89
+ })
90
+ })
@@ -1,29 +1,33 @@
1
1
  import type { BuiltinCategoryDefinition } from "./builtin-category-definition"
2
2
 
3
3
  const ULTRABRAIN_CATEGORY_PROMPT_APPEND = `<Category_Context>
4
- You are working on DEEP LOGICAL REASONING / COMPLEX ARCHITECTURE tasks.
4
+ You are working on DEEP LOGICAL REASONING / ARCHITECTURE / STRATEGY tasks.
5
5
 
6
6
  <Routing_Policy>
7
- Executor contour: coder (deep execution). Do not use this category for bounded quick edits.
7
+ Executor contour: strategist (plan + analysis, read-only). This category produces a structured plan, NOT implementation. The caller is responsible for routing the implementation step to \`deep\`, \`bounded\`, or \`cross-module\` mode based on your plan.
8
8
  </Routing_Policy>
9
9
 
10
- **CRITICAL - CODE STYLE REQUIREMENTS (NON-NEGOTIABLE)**:
11
- 1. BEFORE writing ANY code, SEARCH the existing codebase to find similar patterns/styles
12
- 2. Your code MUST match the project's existing conventions - blend in seamlessly
13
- 3. Write READABLE code that humans can easily understand - no clever tricks
14
- 4. If unsure about style, explore more files until you find the pattern
15
-
16
- Strategic advisor mindset:
17
- - Bias toward simplicity: least complex solution that fulfills requirements
18
- - Leverage existing code/patterns over new components
19
- - Prioritize developer experience and maintainability
20
- - One clear recommendation with effort estimate (Quick/Short/Medium/Large)
21
- - Signal when advanced approach warranted
22
-
23
- Response format:
24
- - Bottom line (2-3 sentences)
25
- - Action plan (numbered steps)
26
- - Risks and mitigations (if relevant)
10
+ <Output_Contract>
11
+ Your output MUST contain:
12
+ 1. **Decomposition** - concrete tasks with bounded scope (each task small enough that one executor agent can complete it without further planning).
13
+ 2. **Dependency graph** - which tasks block which; identify parallel-safe tasks explicitly.
14
+ 3. **Tradeoffs** - at least 2 alternative approaches with cost/risk for each; recommend one and justify briefly.
15
+ 4. **Recommended next step** - explicit \`task(category=...)\` call the caller should make to start implementation.
16
+ 5. **Open questions** - assumptions the caller should confirm before kickoff (if any).
17
+
18
+ You MUST NOT:
19
+ - Write or edit code (you have no write/edit tools).
20
+ - Mark task as done yourself; the caller verifies the plan and routes implementation.
21
+ - Defer planning by asking clarifying questions when assumptions can be stated and surfaced as Open questions.
22
+ </Output_Contract>
23
+
24
+ <Reasoning_Discipline>
25
+ - Bias toward simplicity: least complex solution that fulfills requirements.
26
+ - Leverage existing code/patterns over new components - reference concrete files when relevant.
27
+ - Surface assumptions explicitly as Open questions rather than guessing silently.
28
+ - For unfamiliar domains, recommend a \`task(subagent_type="researcher", run_in_background=true, ...)\` exploration step before planning.
29
+ - One clear recommendation with effort estimate (Quick / Short / Medium / Large).
30
+ </Reasoning_Discipline>
27
31
  </Category_Context>`
28
32
 
29
33
  const DEEP_CATEGORY_PROMPT_APPEND = `<Category_Context>
@@ -55,7 +59,7 @@ const QUICK_CATEGORY_PROMPT_APPEND = `<Category_Context>
55
59
  You are working on SMALL / QUICK tasks.
56
60
 
57
61
  <Routing_Policy>
58
- Executor contour: coder (fast bounded execution). Keep the implementation narrow and escalate only when the task truly becomes deep or cross-system.
62
+ Executor contour: sub (cheap fast-tier executor). Keep the implementation narrow and escalate only when the task truly becomes deep or cross-system.
59
63
  </Routing_Policy>
60
64
 
61
65
  Efficient execution mindset:
@@ -110,7 +114,7 @@ export const OPENAI_CATEGORIES: BuiltinCategoryDefinition[] = [
110
114
  {
111
115
  name: "ultrabrain",
112
116
  config: {},
113
- description: "Hard logic and architecture tasks. Uses coder execution contour; avoid for quick bounded edits.",
117
+ description: "Hard logic, architecture, strategy. Returns structured plan (decomposition + dependency graph + tradeoffs), NOT implementation. Follow up with deep/bounded/cross-module mode for build.",
114
118
  promptAppend: ULTRABRAIN_CATEGORY_PROMPT_APPEND,
115
119
  },
116
120
  {
@@ -122,7 +126,7 @@ export const OPENAI_CATEGORIES: BuiltinCategoryDefinition[] = [
122
126
  {
123
127
  name: "quick",
124
128
  config: {},
125
- description: "Fast bounded execution: single-file fixes, typos, and simple modifications. Uses coder execution contour.",
129
+ description: "Fast bounded execution: single-file fixes, typos, and simple modifications. Uses sub (cheap fast-tier) execution contour.",
126
130
  promptAppend: QUICK_CATEGORY_PROMPT_APPEND,
127
131
  },
128
132
  ]
@@ -21,6 +21,7 @@ export const CANONICAL_DELEGATE_AGENT_KEYS = [
21
21
  ] as const
22
22
 
23
23
  type CanonicalDelegateAgentKey = (typeof CANONICAL_DELEGATE_AGENT_KEYS)[number]
24
+ export type { CanonicalDelegateAgentKey }
24
25
  const CANONICAL_DELEGATE_AGENT_KEY_SET = new Set<string>(CANONICAL_DELEGATE_AGENT_KEYS)
25
26
 
26
27
  const LEGACY_DELEGATE_AGENT_ALIASES: Record<string, CanonicalDelegateAgentKey> = {
@@ -44,8 +45,17 @@ const LEGACY_DELEGATE_AGENT_ALIASES: Record<string, CanonicalDelegateAgentKey> =
44
45
  "project-initializer": "platform-manager",
45
46
  mindmodel: "platform-manager",
46
47
  ui: "multimodal",
48
+ writer: "brainstormer",
49
+ copywriter: "brainstormer",
50
+ "content-writer": "brainstormer",
47
51
  subagent: "sub",
48
52
  "bob-junior": "sub",
53
+ oracle: "strategist",
54
+ hephaestus: "coder",
55
+ metis: "strategist",
56
+ momus: "critic",
57
+ "sisyphus-junior": "sub",
58
+ "multimodal-looker": "multimodal",
49
59
  }
50
60
 
51
61
  export function resolveCanonicalDelegateAgentKey(agentName: string): string {
@@ -0,0 +1,123 @@
1
+ import { expect, test, describe } from "bun:test"
2
+ import { sanitizeSubagentType, findPrimaryAgentMatch, findCallableAgentMatch, listCallableAgentNames, isTaskCallableAgentMode } from "./subagent-discovery"
3
+
4
+ describe("sanitizeSubagentType", () => {
5
+ test("trims whitespace", () => {
6
+ expect(sanitizeSubagentType(" researcher ")).toBe("researcher")
7
+ })
8
+
9
+ test("removes surrounding quotes", () => {
10
+ expect(sanitizeSubagentType('"researcher"')).toBe("researcher")
11
+ expect(sanitizeSubagentType("'researcher'")).toBe("researcher")
12
+ })
13
+
14
+ test("removes surrounding slashes", () => {
15
+ expect(sanitizeSubagentType("/researcher/")).toBe("researcher")
16
+ })
17
+
18
+ test("removes backslashes", () => {
19
+ expect(sanitizeSubagentType("\\researcher\\")).toBe("researcher")
20
+ })
21
+
22
+ test("handles mixed characters", () => {
23
+ expect(sanitizeSubagentType(' /"researcher"\\ ')).toBe("researcher")
24
+ })
25
+ })
26
+
27
+ describe("isTaskCallableAgentMode", () => {
28
+ test("returns true for subagent mode", () => {
29
+ expect(isTaskCallableAgentMode("subagent")).toBe(true)
30
+ })
31
+
32
+ test("returns true for all mode", () => {
33
+ expect(isTaskCallableAgentMode("all")).toBe(true)
34
+ })
35
+
36
+ test("returns false for primary mode", () => {
37
+ expect(isTaskCallableAgentMode("primary")).toBe(false)
38
+ })
39
+
40
+ test("returns false for undefined", () => {
41
+ expect(isTaskCallableAgentMode(undefined)).toBe(false)
42
+ })
43
+ })
44
+
45
+ describe("findPrimaryAgentMatch", () => {
46
+ const agents = [
47
+ { name: "Bob", mode: "primary" as const, model: "openrouter/test/bob" },
48
+ { name: "Coder", mode: "primary" as const, model: "openrouter/test/coder" },
49
+ { name: "Researcher", mode: "primary" as const, model: "openrouter/test/researcher" },
50
+ { name: "SubAgent", mode: "subagent" as const, model: "openrouter/test/sub" },
51
+ ]
52
+
53
+ test("finds primary agent by exact name", () => {
54
+ const result = findPrimaryAgentMatch(agents, "Bob")
55
+ expect(result?.name).toBe("Bob")
56
+ })
57
+
58
+ test("finds primary agent by canonical key", () => {
59
+ const result = findPrimaryAgentMatch(agents, "researcher")
60
+ expect(result?.name).toBe("Researcher")
61
+ })
62
+
63
+ test("returns undefined for non-existent agent", () => {
64
+ const result = findPrimaryAgentMatch(agents, "Nonexistent")
65
+ expect(result).toBeUndefined()
66
+ })
67
+
68
+ test("returns undefined when searching for subagent mode", () => {
69
+ const result = findPrimaryAgentMatch(agents, "SubAgent")
70
+ expect(result).toBeUndefined()
71
+ })
72
+ })
73
+
74
+ describe("findCallableAgentMatch", () => {
75
+ const agents = [
76
+ { name: "Bob", mode: "primary" as const, model: "openrouter/test/bob" },
77
+ { name: "Coder", mode: "primary" as const, model: "openrouter/test/coder" },
78
+ { name: "Researcher", mode: "subagent" as const, model: "openrouter/test/researcher" },
79
+ { name: "SubAgent", mode: "subagent" as const, model: "openrouter/test/sub" },
80
+ { name: "Vision", mode: "all" as const, model: "openrouter/test/vision" },
81
+ ]
82
+
83
+ test("finds callable agent by subagent mode", () => {
84
+ const result = findCallableAgentMatch(agents, "SubAgent")
85
+ expect(result?.name).toBe("SubAgent")
86
+ })
87
+
88
+ test("finds callable agent by all mode", () => {
89
+ const result = findCallableAgentMatch(agents, "Vision")
90
+ expect(result?.name).toBe("Vision")
91
+ })
92
+
93
+ test("finds callable agent by canonical researcher (subagent mode)", () => {
94
+ const result = findCallableAgentMatch(agents, "researcher")
95
+ expect(result?.name).toBe("Researcher")
96
+ })
97
+
98
+ test("returns undefined for primary-only agent", () => {
99
+ const result = findCallableAgentMatch(agents, "Bob")
100
+ expect(result).toBeUndefined()
101
+ })
102
+ })
103
+
104
+ describe("listCallableAgentNames", () => {
105
+ test("lists callable agents sorted alphabetically (subagent and all modes)", () => {
106
+ const agents = [
107
+ { name: "Vision", mode: "all" as const },
108
+ { name: "Researcher", mode: "subagent" as const },
109
+ { name: "Bob", mode: "primary" as const },
110
+ ]
111
+ const result = listCallableAgentNames(agents)
112
+ expect(result).toBe("Researcher, Vision")
113
+ })
114
+
115
+ test("returns empty string for no callable agents", () => {
116
+ const agents = [
117
+ { name: "Bob", mode: "primary" as const },
118
+ { name: "Coder", mode: "primary" as const },
119
+ ]
120
+ const result = listCallableAgentNames(agents)
121
+ expect(result).toBe("")
122
+ })
123
+ })
@@ -73,6 +73,23 @@ Create the work plan directly - that's your job as the planning agent.`,
73
73
  }
74
74
  }
75
75
 
76
+ const ORCHESTRATOR_AGENTS: Set<string> = new Set(["bob"])
77
+ if (ORCHESTRATOR_AGENTS.has(requestedCanonicalAgentKey)) {
78
+ return {
79
+ agentToUse: "",
80
+ categoryModel: undefined,
81
+ error: `Cannot delegate to orchestrator agent "${requestedCanonicalAgentKey}". Bob is the entry-point orchestrator and is not callable as a subagent. Use a specific subagent (researcher/strategist/coder/...) or a mode (quick/deep/...).`,
82
+ }
83
+ }
84
+
85
+ if (canonicalParentAgent && canonicalParentAgent === requestedCanonicalAgentKey) {
86
+ return {
87
+ agentToUse: "",
88
+ categoryModel: undefined,
89
+ error: `Self-delegation not allowed: agent "${requestedCanonicalAgentKey}" cannot delegate to itself via task. Continue the work directly or use session_id=... to continue an existing subagent session.`,
90
+ }
91
+ }
92
+
76
93
  let agentToUse = requestedAgentName
77
94
  let categoryModel: DelegatedModelConfig | undefined
78
95
  let fallbackChain: FallbackEntry[] | undefined = undefined
@@ -84,8 +101,8 @@ Create the work plan directly - that's your job as the planning agent.`,
84
101
  })
85
102
 
86
103
  const mergedAgents = mergeWithClaudeCodeAgents(agents, executorCtx.directory)
87
- const matchedPrimaryAgent = findPrimaryAgentMatch(mergedAgents, agentToUse)
88
104
 
105
+ const matchedPrimaryAgent = findPrimaryAgentMatch(mergedAgents, agentToUse)
89
106
  if (matchedPrimaryAgent) {
90
107
  return {
91
108
  agentToUse: "",
@@ -1,6 +1,6 @@
1
1
  export const SKILL_MCP_TOOL_NAME = "skill_mcp"
2
2
 
3
- export const SKILL_MCP_DESCRIPTION = `Invoke MCP server operations from skill-embedded MCPs. Requires mcp_name plus exactly one of: tool_name, resource_name, or prompt_name.`
3
+ export const SKILL_MCP_DESCRIPTION = `Invoke MCP server operations from skill-embedded MCPs or enabled hiai-opencode builtin MCPs. Requires mcp_name plus exactly one of: tool_name, resource_name, or prompt_name.`
4
4
 
5
5
  export const BUILTIN_MCP_TOOL_HINTS: Record<string, string[]> = {
6
6
  context7: ["context7_resolve-library-id", "context7_query-docs"],
@@ -0,0 +1,44 @@
1
+ import { expect, test } from "bun:test"
2
+ import type { ToolContext } from "@opencode-ai/plugin/tool"
3
+
4
+ import { createSkillMcpTool } from "./tools"
5
+
6
+ test("skill_mcp falls back to enabled builtin MCP when skill MCP is absent", async () => {
7
+ let callCaptured: { serverName: string; toolName: string } | null = null
8
+
9
+ const toolDef = createSkillMcpTool({
10
+ manager: {
11
+ async callTool(info, _context, toolName) {
12
+ callCaptured = { serverName: info.serverName, toolName }
13
+ return { ok: true }
14
+ },
15
+ async readResource() {
16
+ return { ok: true }
17
+ },
18
+ async getPrompt() {
19
+ return { ok: true }
20
+ },
21
+ } as any,
22
+ getLoadedSkills: () => [],
23
+ getSessionID: () => "session-test",
24
+ builtinMcp: {
25
+ firecrawl: {
26
+ enabled: true,
27
+ type: "local",
28
+ command: ["node", "firecrawl.mjs"],
29
+ },
30
+ },
31
+ })
32
+
33
+ const result = await toolDef.execute(
34
+ {
35
+ mcp_name: "firecrawl",
36
+ tool_name: "firecrawl_search",
37
+ arguments: { query: "test" },
38
+ } as any,
39
+ { sessionID: "session-test" } as ToolContext,
40
+ )
41
+
42
+ expect(callCaptured).toEqual({ serverName: "firecrawl", toolName: "firecrawl_search" })
43
+ expect(result).toContain("\"ok\": true")
44
+ })
@@ -1 +0,0 @@
1
- export {};
@@ -1,13 +0,0 @@
1
- export declare const MODEL_ROLE_GUIDE: readonly ["fast: cheap/default for bounded helpers, researcher-style scans, platform chores", "mid: balanced default for steady execution/review work", "high: stronger general-purpose model for primary implementation and planning", "ultrahigh: highest-cost/high-accuracy slot for hard architecture or critical decisions", "vision: preferred for UI/media/multimodal interpretation and visual work", "reasoning: preferred for deeper multi-step reasoning when latency/cost are acceptable"];
2
- export declare const PROVIDER_MODEL_RULES: readonly ["openai: use `openai/<model>` for direct OpenAI calls, e.g. `openai/gpt-5` or `openai/o1`", "anthropic: use `anthropic/<model>`, e.g. `anthropic/claude-3.5-sonnet`", "deepseek: use `deepseek/<model>` when connected directly", "glm: use `z-ai/<model>` or the provider id exposed by your gateway/client", "minimax: use `minimax/<model>`", "qwen: use `qwen/<model>`", "ollama: use the native local model id in Ollama config, e.g. `qwen3.5:4b`", "openrouter: use `openrouter/<vendor>/<model>`, e.g. `openrouter/anthropic/claude-3.5-sonnet`", "rule: store fully qualified model ids in config; avoid local aliases like `fast`, `sonnet`, or provider-less ids"];
3
- export declare const MODEL_PRESETS: {
4
- readonly fast: "openrouter/google/gemini-2.0-flash";
5
- readonly mid: "openrouter/anthropic/claude-3.5-sonnet";
6
- readonly high: "openrouter/anthropic/claude-3.5-opus";
7
- readonly ultrahigh: "openrouter/openai/gpt-4o";
8
- readonly vision: "openrouter/google/gemini-2.0-pro-exp-02-05";
9
- readonly reasoning: "openrouter/openai/o1";
10
- readonly strategist: "openrouter/z-ai/glm-5.1";
11
- readonly critic: "openrouter/qwen/qwen2.5-72b-instruct";
12
- readonly writing: "openrouter/kimi/kimi-latest";
13
- };
@@ -1,13 +0,0 @@
1
- import { z } from "zod";
2
- export declare const WebsearchProviderSchema: z.ZodEnum<{
3
- exa: "exa";
4
- tavily: "tavily";
5
- }>;
6
- export declare const WebsearchConfigSchema: z.ZodObject<{
7
- provider: z.ZodOptional<z.ZodEnum<{
8
- exa: "exa";
9
- tavily: "tavily";
10
- }>>;
11
- }, z.core.$strip>;
12
- export type WebsearchProvider = z.infer<typeof WebsearchProviderSchema>;
13
- export type WebsearchConfig = z.infer<typeof WebsearchConfigSchema>;
@@ -1,16 +0,0 @@
1
- import type { PluginInput } from "@opencode-ai/plugin";
2
- export declare function createNoBobGptHook(ctx: PluginInput): {
3
- "chat.message": (input: {
4
- sessionID: string;
5
- agent?: string;
6
- model?: {
7
- providerID: string;
8
- modelID: string;
9
- };
10
- }, output?: {
11
- message?: {
12
- agent?: string;
13
- [key: string]: unknown;
14
- };
15
- }) => Promise<void>;
16
- };
@@ -1 +0,0 @@
1
- export { createNoBobGptHook } from "./hook";
@@ -1,20 +0,0 @@
1
- import type { PluginInput } from "@opencode-ai/plugin";
2
- type NoCoderNonGptHookOptions = {
3
- allowNonGptModel?: boolean;
4
- };
5
- export declare function createNoCoderNonGptHook(ctx: PluginInput, options?: NoCoderNonGptHookOptions): {
6
- "chat.message": (input: {
7
- sessionID: string;
8
- agent?: string;
9
- model?: {
10
- providerID: string;
11
- modelID: string;
12
- };
13
- }, output?: {
14
- message?: {
15
- agent?: string;
16
- [key: string]: unknown;
17
- };
18
- }) => Promise<void>;
19
- };
20
- export {};
@@ -1 +0,0 @@
1
- export { createNoCoderNonGptHook } from "./hook";
@@ -1,38 +0,0 @@
1
- import type { WebsearchClient } from "./types.ts";
2
- type GeminiChunkWeb = {
3
- title?: string;
4
- uri?: string;
5
- };
6
- type GeminiChunk = {
7
- web?: GeminiChunkWeb;
8
- };
9
- type GeminiSupportSegment = {
10
- startIndex?: number;
11
- endIndex?: number;
12
- };
13
- type GeminiSupport = {
14
- segment?: GeminiSupportSegment;
15
- groundingChunkIndices?: number[];
16
- };
17
- type GeminiMetadata = {
18
- groundingChunks?: GeminiChunk[];
19
- groundingSupports?: GeminiSupport[];
20
- };
21
- type GeminiTextPart = {
22
- text?: string;
23
- thought?: unknown;
24
- };
25
- type GeminiContent = {
26
- role?: string;
27
- parts?: GeminiTextPart[];
28
- };
29
- type GeminiCandidate = {
30
- content?: GeminiContent;
31
- groundingMetadata?: GeminiMetadata;
32
- };
33
- type GeminiGenerateContentResponse = {
34
- candidates?: GeminiCandidate[];
35
- };
36
- export declare function formatWebSearchResponse(response: GeminiGenerateContentResponse, query: string): string;
37
- export declare function createGoogleWebsearchClient(model: string): WebsearchClient;
38
- export {};
@@ -1,17 +0,0 @@
1
- import { type Plugin } from "@opencode-ai/plugin";
2
- import type { GetAuth } from "./types";
3
- export declare const GOOGLE_PROVIDER_ID = "google";
4
- export declare const OPENAI_PROVIDER_ID = "openai";
5
- export declare const OPENROUTER_PROVIDER_ID = "openrouter";
6
- type SelectedProviderID = typeof GOOGLE_PROVIDER_ID | typeof OPENAI_PROVIDER_ID | typeof OPENROUTER_PROVIDER_ID;
7
- export declare function registerGetAuth(providerID: string, getAuth: GetAuth): void;
8
- export declare function resolveGetAuth(providerID: string): GetAuth | undefined;
9
- type SelectedWebsearchConfig = {
10
- providerID: SelectedProviderID;
11
- model: string;
12
- };
13
- export type WebsearchCitedFallback = SelectedWebsearchConfig;
14
- declare const WebsearchCitedPlugin: (_ctx?: unknown, fallback?: WebsearchCitedFallback) => Promise<any>;
15
- export declare const WebsearchCitedGooglePlugin: Plugin;
16
- export declare const WebsearchCitedOpenAIPlugin: Plugin;
17
- export default WebsearchCitedPlugin;
@@ -1,9 +0,0 @@
1
- import type { WebsearchClient } from "./types.ts";
2
- export type OpenAIWebsearchConfig = {
3
- reasoningEffort?: string;
4
- reasoningSummary?: string;
5
- textVerbosity?: string;
6
- store?: boolean;
7
- include?: string[];
8
- };
9
- export declare function createOpenAIWebsearchClient(model: string, config: OpenAIWebsearchConfig): WebsearchClient;
@@ -1,2 +0,0 @@
1
- import type { WebsearchClient } from "./types.ts";
2
- export declare function createOpenRouterWebsearchClient(model: string): WebsearchClient;
@@ -1,5 +0,0 @@
1
- import type { Auth as ProviderAuth } from "@opencode-ai/sdk";
2
- export type GetAuth = () => Promise<ProviderAuth | undefined>;
3
- export interface WebsearchClient {
4
- search(query: string, abortSignal: AbortSignal, getAuth: GetAuth): Promise<string>;
5
- }
@@ -1,6 +0,0 @@
1
- export declare const grep_app: {
2
- type: "remote";
3
- url: string;
4
- enabled: boolean;
5
- oauth: false;
6
- };
@@ -1,10 +0,0 @@
1
- import type { HiaiOpenCodeConfig } from "../config/schema";
2
- export { McpNameSchema, type McpName } from "./types";
3
- type RemoteMcpConfig = {
4
- type: "remote";
5
- url: string;
6
- enabled: boolean;
7
- headers?: Record<string, string>;
8
- oauth?: false;
9
- };
10
- export declare function createBuiltinMcps(disabledMcps?: string[], config?: HiaiOpenCodeConfig): Record<string, RemoteMcpConfig>;
@@ -1,11 +0,0 @@
1
- import type { WebsearchConfig } from "../config/schema";
2
- type RemoteMcpConfig = {
3
- type: "remote";
4
- url: string;
5
- enabled: boolean;
6
- headers?: Record<string, string>;
7
- oauth?: false;
8
- };
9
- export declare function createWebsearchConfig(config?: WebsearchConfig): RemoteMcpConfig | undefined;
10
- export declare const websearch: RemoteMcpConfig | undefined;
11
- export {};
@@ -1,15 +0,0 @@
1
- import { z } from "zod"
2
-
3
- export const WebsearchProviderSchema = z.enum(["exa", "tavily"])
4
-
5
- export const WebsearchConfigSchema = z.object({
6
- /**
7
- * Websearch provider to use.
8
- * - "exa": Uses Exa websearch (default, works without API key)
9
- * - "tavily": Uses Tavily websearch (requires TAVILY_API_KEY)
10
- */
11
- provider: WebsearchProviderSchema.optional(),
12
- })
13
-
14
- export type WebsearchProvider = z.infer<typeof WebsearchProviderSchema>
15
- export type WebsearchConfig = z.infer<typeof WebsearchConfigSchema>