Files
oh-my-opencode/src/plugin/tool-registry.ts
T
YeonGyu-Kim 76bf269b39 fix(#2754): include native PluginInput skills in skill() discovery
The skill tool previously only merged disk-discovered skills and preloaded
options.skills, so skills registered natively via ctx.skills (for example
from config.skills.paths or other plugins) were prompt-visible but not
discoverable by skill().

Fix:
- pass ctx.skills from tool-registry into createSkillTool
- extend SkillLoadOptions with nativeSkills accessor
- merge nativeSkills.all() results into getSkills()
- add test covering native PluginInput skill discovery
2026-03-27 17:42:43 +09:00

190 lines
6.4 KiB
TypeScript

import type { ToolDefinition } from "@opencode-ai/plugin"
import type { SkillLoadOptions } from "../tools/skill/types"
import type {
AvailableCategory,
} from "../agents/dynamic-agent-prompt-builder"
import type { OhMyOpenCodeConfig } from "../config"
import type { PluginContext, ToolsRecord } from "./types"
import {
builtinTools,
createBackgroundTools,
createCallOmoAgent,
createLookAt,
createSkillMcpTool,
createSkillTool,
createGrepTools,
createGlobTools,
createAstGrepTools,
createSessionManagerTools,
createDelegateTask,
discoverCommandsSync,
interactive_bash,
createTaskCreateTool,
createTaskGetTool,
createTaskList,
createTaskUpdateTool,
createHashlineEditTool,
} from "../tools"
import { getMainSessionID } from "../features/claude-code-session-state"
import { filterDisabledTools } from "../shared/disabled-tools"
import { log } from "../shared"
import type { Managers } from "../create-managers"
import type { SkillContext } from "./skill-context"
import { normalizeToolArgSchemas } from "./normalize-tool-arg-schemas"
export type ToolRegistryResult = {
filteredTools: ToolsRecord
taskSystemEnabled: boolean
}
export function createToolRegistry(args: {
ctx: PluginContext
pluginConfig: OhMyOpenCodeConfig
managers: Pick<Managers, "backgroundManager" | "tmuxSessionManager" | "skillMcpManager">
skillContext: SkillContext
availableCategories: AvailableCategory[]
}): ToolRegistryResult {
const { ctx, pluginConfig, managers, skillContext, availableCategories } = args
const backgroundTools = createBackgroundTools(managers.backgroundManager, ctx.client)
const callOmoAgent = createCallOmoAgent(
ctx,
managers.backgroundManager,
pluginConfig.disabled_agents ?? [],
pluginConfig.agents,
pluginConfig.categories,
)
const isMultimodalLookerEnabled = !(pluginConfig.disabled_agents ?? []).some(
(agent) => agent.toLowerCase() === "multimodal-looker",
)
const lookAt = isMultimodalLookerEnabled ? createLookAt(ctx) : null
const delegateTask = createDelegateTask({
manager: managers.backgroundManager,
client: ctx.client,
directory: ctx.directory,
userCategories: pluginConfig.categories,
agentOverrides: pluginConfig.agents,
gitMasterConfig: pluginConfig.git_master,
sisyphusJuniorModel: pluginConfig.agents?.["sisyphus-junior"]?.model,
browserProvider: skillContext.browserProvider,
disabledSkills: skillContext.disabledSkills,
availableCategories,
availableSkills: skillContext.availableSkills,
syncPollTimeoutMs: pluginConfig.background_task?.syncPollTimeoutMs,
onSyncSessionCreated: async (event) => {
log("[index] onSyncSessionCreated callback", {
sessionID: event.sessionID,
parentID: event.parentID,
title: event.title,
})
await managers.tmuxSessionManager.onSessionCreated({
type: "session.created",
properties: {
info: {
id: event.sessionID,
parentID: event.parentID,
title: event.title,
},
},
})
},
})
const getSessionIDForMcp = (): string => getMainSessionID() || ""
const skillMcpTool = createSkillMcpTool({
manager: managers.skillMcpManager,
getLoadedSkills: () => skillContext.mergedSkills,
getSessionID: getSessionIDForMcp,
})
const commands = discoverCommandsSync(ctx.directory, {
pluginsEnabled: pluginConfig.claude_code?.plugins ?? true,
enabledPluginsOverride: pluginConfig.claude_code?.plugins_override,
})
const skillTool = createSkillTool({
commands,
skills: skillContext.mergedSkills,
mcpManager: managers.skillMcpManager,
getSessionID: getSessionIDForMcp,
gitMasterConfig: pluginConfig.git_master,
nativeSkills: "skills" in ctx ? (ctx as { skills: SkillLoadOptions["nativeSkills"] }).skills : undefined,
})
// task_system defaults to true since v3.14 — delegation (oracle, subagents) requires it
const taskSystemEnabled = pluginConfig.experimental?.task_system ?? true
const taskToolsRecord: Record<string, ToolDefinition> = taskSystemEnabled
? {
task_create: createTaskCreateTool(pluginConfig, ctx),
task_get: createTaskGetTool(pluginConfig),
task_list: createTaskList(pluginConfig),
task_update: createTaskUpdateTool(pluginConfig, ctx),
}
: {}
const hashlineEnabled = pluginConfig.hashline_edit ?? false
const hashlineToolsRecord: Record<string, ToolDefinition> = hashlineEnabled
? { edit: createHashlineEditTool(ctx) }
: {}
const allTools: Record<string, ToolDefinition> = {
...builtinTools,
...createGrepTools(ctx),
...createGlobTools(ctx),
...createAstGrepTools(ctx),
...createSessionManagerTools(ctx),
...backgroundTools,
call_omo_agent: callOmoAgent,
...(lookAt ? { look_at: lookAt } : {}),
task: delegateTask,
skill_mcp: skillMcpTool,
skill: skillTool,
interactive_bash,
...taskToolsRecord,
...hashlineToolsRecord,
}
for (const toolDefinition of Object.values(allTools)) {
normalizeToolArgSchemas(toolDefinition)
}
const filteredTools: ToolsRecord = filterDisabledTools(allTools, pluginConfig.disabled_tools)
const maxTools = pluginConfig.experimental?.max_tools
if (maxTools) {
const estimatedBuiltinTools = 20
const pluginToolBudget = maxTools - estimatedBuiltinTools
const toolEntries = Object.entries(filteredTools)
if (pluginToolBudget > 0 && toolEntries.length > pluginToolBudget) {
const excess = toolEntries.length - pluginToolBudget
log(`[tool-registry] Tool count (${toolEntries.length} plugin + ~${estimatedBuiltinTools} builtin = ~${toolEntries.length + estimatedBuiltinTools}) exceeds max_tools=${maxTools}. Trimming ${excess} lower-priority tools.`)
const lowPriorityTools = [
"session_list", "session_read", "session_search", "session_info",
"call_omo_agent", "interactive_bash", "look_at",
"task_create", "task_get", "task_list", "task_update",
]
let removed = 0
for (const toolName of lowPriorityTools) {
if (removed >= excess) break
if (filteredTools[toolName]) {
delete filteredTools[toolName]
removed += 1
}
}
if (removed < excess) {
log(`[tool-registry] WARNING: Could not trim enough tools. ${toolEntries.length - removed} plugin tools remain.`)
}
}
}
return {
filteredTools,
taskSystemEnabled,
}
}