feat(tool) show tool usage via channel

This commit is contained in:
afjcjsbx 2026-02-25 19:41:07 +01:00
parent 094d65916d
commit eca4ede77e
7 changed files with 57 additions and 4 deletions

View file

@ -203,6 +203,7 @@
}
},
"tools": {
"enable_notifications": false,
"web": {
"brave": {
"enabled": false,

View file

@ -668,6 +668,28 @@ func (al *AgentLoop) runLLMIteration(
"iteration": iteration,
})
// notify tool use on channel
if al.cfg.Tools.EnableNotifications && !constants.IsInternalChannel(opts.Channel) {
var notification string
if tool, ok := agent.Tools.Get(tc.Name); ok {
// check the tool implements the interface
if notifier, isNotifier := tool.(tools.NotificationFormatter); isNotifier {
notification = notifier.FormatNotification(tc.Arguments)
} else if tc.Name != "message" {
notification = fmt.Sprintf("🛠️ Tool use: `%s`", tc.Name)
}
}
if notification != "" {
al.bus.PublishOutbound(bus.OutboundMessage{
Channel: opts.Channel,
ChatID: opts.ChatID,
Content: notification,
})
}
}
// Create async callback for tools that implement AsyncTool
// NOTE: Following openclaw's design, async tools do NOT send results directly to users.
// Instead, they notify the agent via PublishInbound, and the agent decides

View file

@ -468,10 +468,11 @@ type ExecConfig struct {
}
type ToolsConfig struct {
Web WebToolsConfig `json:"web"`
Cron CronToolsConfig `json:"cron"`
Exec ExecConfig `json:"exec"`
Skills SkillsToolsConfig `json:"skills"`
EnableNotifications bool `json:"enable_notifications" env:"PICOCLAW_TOOLS_ENABLE_NOTIFICATIONS"`
Web WebToolsConfig `json:"web"`
Cron CronToolsConfig `json:"cron"`
Exec ExecConfig `json:"exec"`
Skills SkillsToolsConfig `json:"skills"`
}
type SkillsToolsConfig struct {

View file

@ -276,6 +276,7 @@ func DefaultConfig() *Config {
Port: 18790,
},
Tools: ToolsConfig{
EnableNotifications: false,
Web: WebToolsConfig{
Proxy: "",
Brave: BraveConfig{

View file

@ -17,6 +17,12 @@ type ContextualTool interface {
SetContext(channel, chatID string)
}
// NotificationFormatter allows a tool to generate a custom
// message to be shown to the user before execution.
type NotificationFormatter interface {
FormatNotification(args map[string]any) string
}
// AsyncCallback is a function type that async tools use to notify completion.
// When an async tool finishes its work, it calls this callback with the result.
//

View file

@ -14,6 +14,7 @@ import (
"time"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/utils"
)
type ExecTool struct {
@ -134,6 +135,13 @@ func (t *ExecTool) Parameters() map[string]any {
}
}
func (t *ExecTool) FormatNotification(args map[string]any) string {
if cmd, ok := args["command"].(string); ok {
return fmt.Sprintf("🛠️ Exec: `%s`", utils.Truncate(cmd, 500))
}
return "🛠️ Shell command execution in progress"
}
func (t *ExecTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
command, ok := args["command"].(string)
if !ok {

View file

@ -471,6 +471,13 @@ func (t *WebSearchTool) Parameters() map[string]any {
}
}
func (t *WebSearchTool) FormatNotification(args map[string]any) string {
if query, ok := args["query"].(string); ok {
return fmt.Sprintf("🛠️ Web search: \"%s\"", query)
}
return "🛠️ Web search in progress"
}
func (t *WebSearchTool) Execute(ctx context.Context, args map[string]any) *ToolResult {
query, ok := args["query"].(string)
if !ok {
@ -653,6 +660,13 @@ func (t *WebFetchTool) Execute(ctx context.Context, args map[string]any) *ToolRe
}
}
func (t *WebFetchTool) FormatNotification(args map[string]any) string {
if u, ok := args["url"].(string); ok {
return fmt.Sprintf("🛠️ Fetching page: \"%s\"", u)
}
return "🛠️ Fetching web page in progress"
}
func (t *WebFetchTool) extractText(htmlContent string) string {
re := regexp.MustCompile(`<script[\s\S]*?</script>`)
result := re.ReplaceAllLiteralString(htmlContent, "")