feat(tools): add tool alias registry for Claude Code skill compatibility

Add alias support to the tool Registry so Claude Code skills can reference
Anthropic tool names (Read, Write, Edit, Bash, etc.) and have them resolve
to GoClaw canonical tools (read_file, write_file, edit, exec, etc.).

- Registry: aliases map, RegisterAlias(), resolve(), Aliases()
- Policy engine: auto-includes alias defs when canonical tool passes filter
- System prompt: alias entries in coreToolSummaries + missing use_skill
- Legacy toolAliases migrated to Registry.RegisterAlias() at startup
This commit is contained in:
viettranx committed 2026-03-11 17:33:28 +07:00
1 parent c25e770d43
commit ec34f488df
4 files changed
+120 -10

No files matched your search

+22
View File
@@ -536,6 +536,28 @@ func runGateway() {
toolsReg.Register(tools.NewMessageTool())
slog.Info("session + message tools registered")
// Register legacy tool aliases (backward-compat names from policy.go).
for alias, canonical := range tools.LegacyToolAliases() {
toolsReg.RegisterAlias(alias, canonical)
}
// Register Claude Code tool aliases so Claude Code skills work without modification.
// LLM calls alias name → registry resolves to canonical tool → executes.
for alias, canonical := range map[string]string{
"Read": "read_file",
"Write": "write_file",
"Edit": "edit",
"Bash": "exec",
"WebFetch": "web_fetch",
"WebSearch": "web_search",
"Agent": "spawn",
"Skill": "use_skill",
"ToolSearch": "mcp_tool_search",
} {
toolsReg.RegisterAlias(alias, canonical)
}
slog.Info("tool aliases registered", "count", len(toolsReg.Aliases()))
// Allow read_file to access skills directories and CLI workspaces (outside workspace).
// Skills can live in ~/.goclaw/skills/, ~/.agents/skills/, ~/.goclaw/skills-store/, etc.
// CLI workspaces live in ~/.goclaw/cli-workspaces/ (agent working files).
+12
View File
@@ -64,6 +64,7 @@ var coreToolSummaries = map[string]string{
"web_fetch": "Fetch and extract content from a URL",
"cron": "Manage scheduled jobs and reminders",
"skill_search": "Search available skills by keyword (weather, translate, github, etc.)",
"use_skill": "Invoke a skill by name and follow its instructions",
"mcp_tool_search": "Search for available MCP external integration tools by keyword",
"browser": "Browse web pages interactively",
"tts": "Convert text to speech audio",
@@ -86,6 +87,17 @@ var coreToolSummaries = map[string]string{
"delegate_search": "Search for agents by expertise to find the right delegation target",
"team_tasks": "Manage team task board (list, create, complete, cancel tasks)",
"team_message": "Send messages to teammates (progress updates, questions)",
// Claude Code tool aliases — enable Claude Code skills without modification
"Read": "Alias for read_file — Read file contents",
"Write": "Alias for write_file — Create or overwrite files",
"Edit": "Alias for edit — Edit a file by replacing exact text matches",
"Bash": "Alias for exec — Run shell commands",
"WebFetch": "Alias for web_fetch — Fetch and extract content from a URL",
"WebSearch": "Alias for web_search — Search the web",
"Agent": "Alias for spawn — Spawn a subagent or delegate to another agent",
"Skill": "Alias for use_skill — Invoke a skill by name",
"ToolSearch": "Alias for mcp_tool_search — Search for available MCP tools",
}
// BuildSystemPrompt constructs the full system prompt with all sections.
+28 -3
View File
@@ -54,14 +54,20 @@ var toolProfiles = map[string][]string{
"full": {}, // empty = no restrictions
}
// Tool aliases map alternative names to canonical names.
var toolAliases = map[string]string{
// Legacy tool aliases — migrated to Registry.RegisterAlias() at startup.
// Kept as seed data only; resolveAlias() is no longer used.
var legacyToolAliases = map[string]string{
"bash": "exec",
"apply-patch": "apply_patch",
"edit_file": "edit",
"sessions_spawn": "spawn",
}
// LegacyToolAliases returns legacy aliases for registration into the Registry.
func LegacyToolAliases() map[string]string {
return legacyToolAliases
}
// Subagent deny lists — tools subagents cannot use.
var subagentDenyList = []string{
"exec", // subagents should not shell out — main agent can still exec
@@ -107,11 +113,30 @@ func (pe *PolicyEngine) FilterTools(
}
// Resolve aliases and build definitions
allowedSet := make(map[string]bool, len(allowed))
var defs []providers.ToolDefinition
for _, name := range allowed {
canonical := resolveAlias(name)
if tool, ok := registry.Get(canonical); ok {
defs = append(defs, ToProviderDef(tool))
allowedSet[canonical] = true
}
}
// Add registry aliases for allowed canonical tools
for alias, canonical := range registry.Aliases() {
if !allowedSet[canonical] {
continue
}
if tool, ok := registry.Get(canonical); ok {
defs = append(defs, providers.ToolDefinition{
Type: "function",
Function: providers.ToolFunctionSchema{
Name: alias,
Description: tool.Description(),
Parameters: tool.Parameters(),
},
})
}
}
@@ -322,7 +347,7 @@ func unionWithSpec(current []string, allTools []string, spec []string) []string
}
func resolveAlias(name string) string {
if canonical, ok := toolAliases[name]; ok {
if canonical, ok := legacyToolAliases[name]; ok {
return canonical
}
return name
+58 -7
View File
@@ -13,6 +13,7 @@ import (
// Registry manages tool registration and execution.
type Registry struct {
tools map[string]Tool
aliases map[string]string // alias name → canonical tool name
mu sync.RWMutex
rateLimiter *ToolRateLimiter // nil = no rate limiting
scrubbing bool // scrub credentials from output (default true)
@@ -21,6 +22,7 @@ type Registry struct {
func NewRegistry() *Registry {
return &Registry{
tools: make(map[string]Tool),
aliases: make(map[string]string),
scrubbing: true, // enabled by default
}
}
@@ -42,12 +44,44 @@ func (r *Registry) Register(tool Tool) {
r.tools[tool.Name()] = tool
}
// Get returns a tool by name.
// RegisterAlias maps an alias name to a canonical tool name.
// Rejected if alias collides with an existing real tool.
func (r *Registry) RegisterAlias(alias, canonical string) {
r.mu.Lock()
defer r.mu.Unlock()
if _, exists := r.tools[alias]; exists {
slog.Warn("alias conflicts with registered tool", "alias", alias, "canonical", canonical)
return
}
r.aliases[alias] = canonical
}
// Aliases returns a copy of the alias map.
func (r *Registry) Aliases() map[string]string {
r.mu.RLock()
defer r.mu.RUnlock()
cp := make(map[string]string, len(r.aliases))
maps.Copy(cp, r.aliases)
return cp
}
// resolve looks up a tool by name, checking real tools first, then aliases.
func (r *Registry) resolve(name string) (Tool, bool) {
if t, ok := r.tools[name]; ok {
return t, true
}
if canonical, ok := r.aliases[name]; ok {
t, ok := r.tools[canonical]
return t, ok
}
return nil, false
}
// Get returns a tool by name (checks real tools first, then aliases).
func (r *Registry) Get(name string) (Tool, bool) {
r.mu.RLock()
defer r.mu.RUnlock()
t, ok := r.tools[name]
return t, ok
return r.resolve(name)
}
// Unregister removes a tool from the registry by name.
@@ -70,7 +104,7 @@ func (r *Registry) Execute(ctx context.Context, name string, args map[string]any
// making tool instances thread-safe for concurrent execution.
func (r *Registry) ExecuteWithContext(ctx context.Context, name string, args map[string]any, channel, chatID, peerKind, sessionKey string, asyncCB AsyncCallback) *Result {
r.mu.RLock()
tool, ok := r.tools[name]
tool, ok := r.resolve(name)
r.mu.RUnlock()
if !ok {
@@ -127,18 +161,33 @@ func (r *Registry) ExecuteWithContext(ctx context.Context, name string, args map
}
// ProviderDefs returns tool definitions for LLM provider APIs.
// Includes alias definitions (same params/description, alias name).
func (r *Registry) ProviderDefs() []providers.ToolDefinition {
r.mu.RLock()
defer r.mu.RUnlock()
defs := make([]providers.ToolDefinition, 0, len(r.tools))
defs := make([]providers.ToolDefinition, 0, len(r.tools)+len(r.aliases))
for _, tool := range r.tools {
defs = append(defs, ToProviderDef(tool))
}
for alias, canonical := range r.aliases {
tool, ok := r.tools[canonical]
if !ok {
continue
}
defs = append(defs, providers.ToolDefinition{
Type: "function",
Function: providers.ToolFunctionSchema{
Name: alias,
Description: tool.Description(),
Parameters: tool.Parameters(),
},
})
}
return defs
}
// List returns all registered tool names.
// List returns all registered canonical tool names (excludes aliases).
func (r *Registry) List() []string {
r.mu.RLock()
defer r.mu.RUnlock()
@@ -156,7 +205,7 @@ func (r *Registry) Count() int {
return len(r.tools)
}
// Clone creates a shallow copy of the registry with all registered tools.
// Clone creates a shallow copy of the registry with all registered tools and aliases.
// The clone shares the rate limiter (thread-safe) and scrubbing setting.
// Used by subagent toolsFactory so subagents inherit parent tools (web_fetch, web_search, etc.).
func (r *Registry) Clone() *Registry {
@@ -164,9 +213,11 @@ func (r *Registry) Clone() *Registry {
defer r.mu.RUnlock()
clone := &Registry{
tools: make(map[string]Tool, len(r.tools)),
aliases: make(map[string]string, len(r.aliases)),
rateLimiter: r.rateLimiter,
scrubbing: r.scrubbing,
}
maps.Copy(clone.tools, r.tools)
maps.Copy(clone.aliases, r.aliases)
return clone
}