package builtin // monitor_tool.go 实现 monitor_progress 内置工具. // // 模块定位: // // MonitorTool 允许 Agent 主动推送进度消息到 UDS Inbox, // 也可以被工具子进程调用(通过 FLYTO_SESSION_SOCK 连接,直接发 JSON). // // 设计决策 - 为什么需要这个工具? // // 早期方案 Agent 无法在执行过程中向外部实时报告进度, // 长时间运行的任务(数据迁移,批量处理)对外是黑盒. // MonitorTool 打破这个限制:Agent 或子进程可以随时"吼"一声进度, // 观察者(CLI 进度条,监控仪表盘,告警系统)实时感知. // // 升华改进(ELEVATED): 工具子进程也可以用-- // // 子进程无需知道 Engine 内部结构,只要连接 FLYTO_SESSION_SOCK 发 JSON 即可. // 标准化的"进度报告协议"让跨语言工具(Python 脚本,Shell 工具)都能参与. // 跨行业扩展:仓储场景的盘点工具,医疗场景的成像分析--都可以实时报告进度. // // nil 安全设计: // // 如果 InboxServer 为 nil(未启用 UDS),Execute() 静默成功(返回 {"ok": true}). // 不报错--工具调用方无需关心 UDS 是否启用,代码路径统一. // 这是"降级而非中断"的设计哲学. // // ConcurrencySafe: true // // MonitorTool 只向 channel 写入(非阻塞),无共享状态,可并发调用. import ( "context" "encoding/json" "fmt" "git.flytoex.net/yuanwei/flyto-agent/pkg/inbox" "git.flytoex.net/yuanwei/flyto-agent/pkg/permission" "git.flytoex.net/yuanwei/flyto-agent/pkg/tools" ) // MonitorTool 进度报告工具. // // 精妙之处(CLEVER): 持有 *inbox.UDSServer 指针而非接口-- // // UDSServer 是具体实现,此处不需要抽象(只有一种实现). // 如果未来需要 mock(测试用),只需将 inbox.UDSServer 指针替换为接口. // 替代方案:定义 InboxSink 接口(过度抽象,当前无多态需求). type MonitorTool struct { srv *inbox.UDSServer // nil = UDS 未启用,静默成功 } // NewMonitorTool 创建 MonitorTool 实例. // srv 为 nil 时工具仍可用,但所有调用静默成功(不推送消息). func NewMonitorTool(srv *inbox.UDSServer) *MonitorTool { return &MonitorTool{srv: srv} } // monitorInput 是 monitor_progress 工具的输入参数. type monitorInput struct { ToolUseID string `json:"tool_use_id,omitempty"` // 关联的工具调用 ID(可选) Message string `json:"message"` // 进度消息(必填) Percent *float64 `json:"percent,omitempty"` // 完成百分比 0-100(可选) Meta json.RawMessage `json:"meta,omitempty"` // 额外元数据(可选) } // Name 返回工具名称. func (t *MonitorTool) Name() string { return "monitor_progress" } // Description 返回工具描述. func (t *MonitorTool) Description(ctx context.Context) string { return "向会话 Inbox 推送进度消息。适用于长时间运行的操作(数据库迁移、批量处理等)。" + "工具子进程可以直接连接 FLYTO_SESSION_SOCK 发送 JSON 消息,无需调用此工具。" } // InputSchema 返回工具的 JSON Schema. func (t *MonitorTool) InputSchema() json.RawMessage { return json.RawMessage(`{ "type": "object", "properties": { "tool_use_id": { "type": "string", "description": "关联的工具调用 ID(可选)" }, "message": { "type": "string", "description": "进度消息内容(必填)" }, "percent": { "type": "number", "minimum": 0, "maximum": 100, "description": "完成百分比 0-100(可选)" }, "meta": { "type": "object", "description": "额外元数据(任意 JSON 对象,可选)" } }, "required": ["message"] }`) } // Metadata 返回工具元数据. func (t *MonitorTool) Metadata() tools.Metadata { return tools.Metadata{ ConcurrencySafe: true, // 只写 channel,无共享可变状态 ReadOnly: true, // 不修改文件系统或外部资源 Destructive: false, SearchHint: "progress monitor report status update", PermissionClass: permission.PermClassReadOnly, AuditOperation: "read", } } // Execute 执行进度推送. // // 三层防御: // // a) 参数层:message 必填验证 // b) UDS 层:srv 为 nil 时静默成功(不中断 Agent 执行) // c) channel 层:非阻塞推送,channel 满时丢弃(不阻塞 Agent 主循环) func (t *MonitorTool) Execute(ctx context.Context, input json.RawMessage, progress tools.ProgressFunc) (*tools.Result, error) { var params monitorInput if err := json.Unmarshal(input, ¶ms); err != nil { return nil, fmt.Errorf("monitor_progress: invalid input: %w", err) } // 防御点 a: message 必填 if params.Message == "" { return &tools.Result{ Output: `error: "message" is required`, IsError: true, }, nil } // 防御点 b: srv 为 nil 时静默成功 // 精妙之处(CLEVER): nil check 在最外层,所有分支都走到 {"ok": true} 返回-- // 调用方无需感知 UDS 是否启用,代码路径统一. // 替代方案:返回错误(调用方必须检查 UDS 是否启用,增加耦合). if t.srv == nil { return &tools.Result{Output: `{"ok": true}`, IsError: false}, nil } // 构造 UDS 消息 // data 字段:如果有 percent,拼接到消息后面(格式:"{message} ({percent}%)") data := params.Message if params.Percent != nil { data = fmt.Sprintf("%s (%.1f%%)", params.Message, *params.Percent) } msg := inbox.UDSInboxMessage{ Type: "progress", ToolUseID: params.ToolUseID, Data: data, Meta: params.Meta, } // 防御点 c: 非阻塞推送--UDSServer.Send 内部做 select+default,不阻塞 Agent 主循环. // channel 满时静默丢弃(fire-and-forget 语义,进度丢几条不影响正确性). t.srv.Send(msg) return &tools.Result{Output: `{"ok": true}`, IsError: false}, nil }