package builtin // Agent 子进程工具 -- 生成新的 Agent 子进程来处理子任务. // // 这是 Agent 委派任务的能力:当一个任务可以分解为独立的子任务时, // Agent 可以创建子 Agent 来处理,避免主 Agent 的上下文窗口被占满. // // 支持三种运行模式: // - 同步模式(sync):等待子 Agent 完成后返回结果 // - 后台模式(background):立即返回任务 ID,子 Agent 在后台运行 // - Worktree 模式(worktree):在独立的 git worktree 中运行子 Agent // // 特性: // - 创建独立的子 Agent 来处理子任务 // - 子 Agent 使用工具子集(不包含 Agent 工具自身,防止无限递归) // - 支持指定模型和子 Agent 类型 // - 通过 AgentExecutor 接口解耦,避免 builtin 包与 engine 包的循环依赖 // - ConcurrencySafe: false import ( "context" "encoding/json" "fmt" "strings" "git.flytoex.net/yuanwei/flyto-agent/pkg/permission" "git.flytoex.net/yuanwei/flyto-agent/pkg/tools" ) // AgentRunRequest 是子 Agent 执行请求的统一入参结构体. // // 升华改进(ELEVATED): 替代早期方案散列参数(prompt, model, description, branchName...), // 统一为结构体后新增字段无需修改接口签名--符合开闭原则. // 跨行业扩展:可追加 Priority,Deadline,Labels 等字段而不破坏现有实现. // 替代方案:散列参数(每次新增字段都需要修改接口+所有实现,破坏性变更). type AgentRunRequest struct { // Prompt 发送给子 Agent 的任务提示 Prompt string // Description 任务描述(用于追踪和展示) Description string // Model 指定模型("" = 继承父代理模型) Model string // AgentType 指定 Agent 类型("" = general-purpose) // 类型决定了工具集和运行行为 AgentType string // BranchName worktree 模式的分支名(为空则自动生成) BranchName string } // AgentExecutor 是子 Agent 执行器接口. // Engine 层实现此接口,注入到 AgentTool 中,避免循环依赖. // 精妙之处(CLEVER): 通过 AgentExecutor 接口实现反向依赖注入-- // builtin 包定义接口,engine 包实现接口.避免了 builtin → engine 的循环引用. // AgentTool 创建时不需要 engine 实例,后续通过 SetExecutor 注入. // 这是 Go 中解决循环依赖的经典模式(依赖倒置原则). type AgentExecutor interface { // RunSubAgent 同步运行一个子 Agent,等待完成后返回结果文本. RunSubAgent(ctx context.Context, req AgentRunRequest, availableTools []tools.Tool) (string, error) // RunSubAgentBackground 在后台运行一个子 Agent,立即返回任务 ID. // 调用者可以通过任务 ID 查询进度和结果. RunSubAgentBackground(req AgentRunRequest, availableTools []tools.Tool) (string, error) // RunSubAgentWorktree 在独立的 git worktree 中运行子 Agent. // req.BranchName 为空时自动生成.同步等待完成. RunSubAgentWorktree(ctx context.Context, req AgentRunRequest, availableTools []tools.Tool) (string, error) } // AgentTool 是 Agent 子进程工具. type AgentTool struct { registry *tools.Registry executor AgentExecutor } // NewAgentTool 创建一个 Agent 工具实例. // registry 用于获取可用工具列表,子 Agent 将使用去除 Agent 工具后的工具子集. func NewAgentTool(registry *tools.Registry) *AgentTool { return &AgentTool{ registry: registry, } } // SetExecutor 设置子 Agent 执行器. // 由 Engine 层在初始化后调用,注入真正的执行逻辑. func (t *AgentTool) SetExecutor(executor AgentExecutor) { t.executor = executor } // agentInput 是 Agent 工具的输入参数. type agentInput struct { // Prompt 发送给子 Agent 的任务提示 Prompt string `json:"prompt"` // Description 子 Agent 任务描述(用于追踪和展示) Description string `json:"description,omitempty"` // Model 使用的模型(为空则继承父 Agent 的模型) Model string `json:"model,omitempty"` // Mode 运行模式:sync(默认),background,worktree Mode string `json:"mode,omitempty"` // BranchName worktree 模式的分支名(为空则自动生成) BranchName string `json:"branch_name,omitempty"` // AgentType 指定 Agent 类型("" = general-purpose) // 类型决定了工具集约束和默认运行行为 AgentType string `json:"agent_type,omitempty"` } // Name 返回工具名称. func (t *AgentTool) Name() string { return "Agent" } // Description 返回工具描述. func (t *AgentTool) Description(ctx context.Context) string { return "Spawns a new Agent sub-process to handle a sub-task. " + "Use this when a task can be decomposed into independent sub-tasks. " + "The sub-agent has access to the same tools (except Agent itself to prevent recursion). " + "Supports three modes: 'sync' (default, wait for completion), " + "'background' (return immediately with task ID), " + "'worktree' (run in isolated git worktree). " + "Returns the sub-agent's final response or a task ID for background mode." } // InputSchema 返回工具的 JSON Schema 输入定义. func (t *AgentTool) InputSchema() json.RawMessage { return json.RawMessage(`{ "type": "object", "properties": { "prompt": { "type": "string", "description": "The task prompt to send to the sub-agent" }, "description": { "type": "string", "description": "A brief description of what this sub-agent should accomplish" }, "model": { "type": "string", "description": "Optional model to use for the sub-agent (defaults to the same model as the parent)" }, "mode": { "type": "string", "description": "Run mode: 'sync' (default, wait for result), 'background' (return task ID immediately), 'worktree' (run in git worktree)", "enum": ["sync", "background", "worktree"] }, "branch_name": { "type": "string", "description": "Branch name for worktree mode (auto-generated if empty)" }, "agent_type": { "type": "string", "description": "Optional agent type to use (e.g. 'Explore', 'Plan', 'Verification', 'general-purpose'). Determines the tool subset and default behavior. Defaults to 'general-purpose' if not specified." } }, "required": ["prompt"] }`) } // Metadata 返回工具元数据. func (t *AgentTool) Metadata() tools.Metadata { return tools.Metadata{ ConcurrencySafe: false, ReadOnly: false, Destructive: false, SearchHint: "agent subagent subprocess delegate task background worktree", PermissionClass: permission.PermClassGeneric, AuditOperation: "invoke", } } // getSubTools 获取子 Agent 可用的工具列表(排除 Agent 工具自身和管理工具). func (t *AgentTool) getSubTools() []tools.Tool { allTools := t.registry.All() // 子 Agent 不能使用的工具 excluded := map[string]bool{ "Agent": true, // 防止递归 } subTools := make([]tools.Tool, 0, len(allTools)) for _, tool := range allTools { if !excluded[tool.Name()] { subTools = append(subTools, tool) } } return subTools } // Execute 创建并运行子 Agent. func (t *AgentTool) Execute(ctx context.Context, input json.RawMessage, progress tools.ProgressFunc) (*tools.Result, error) { var params agentInput if err := json.Unmarshal(input, ¶ms); err != nil { return nil, fmt.Errorf("agent: invalid input: %w", err) } if params.Prompt == "" { return &tools.Result{ Output: "error: prompt is required", IsError: true, }, nil } // 默认模式为 sync mode := params.Mode if mode == "" { mode = "sync" } // 验证模式 validModes := map[string]bool{"sync": true, "background": true, "worktree": true} if !validModes[mode] { return &tools.Result{ Output: fmt.Sprintf("error: invalid mode '%s'. Valid modes: sync, background, worktree", mode), IsError: true, }, nil } taskDesc := params.Description if taskDesc == "" { taskDesc = "Complete the given task" } // 获取子 Agent 可用的工具 subTools := t.getSubTools() if progress != nil { progress(0.1, fmt.Sprintf("Sub-agent starting (mode: %s)", mode)) } // 构建统一请求结构体 req := AgentRunRequest{ Prompt: params.Prompt, Description: taskDesc, Model: params.Model, AgentType: params.AgentType, BranchName: params.BranchName, } // 如果没有配置执行器,返回信息性提示 if t.executor == nil { return t.fallbackResult(req, subTools, mode) } // 根据模式执行 switch mode { case "sync": return t.executeSync(ctx, req, subTools, progress) case "background": return t.executeBackground(req, subTools, progress) case "worktree": return t.executeWorktree(ctx, req, subTools, progress) default: return t.executeSync(ctx, req, subTools, progress) } } // executeSync 同步模式:等待子 Agent 完成后返回结果. func (t *AgentTool) executeSync(ctx context.Context, req AgentRunRequest, subTools []tools.Tool, progress tools.ProgressFunc) (*tools.Result, error) { if progress != nil { progress(0.2, "Sub-agent running (sync mode)") } result, err := t.executor.RunSubAgent(ctx, req, subTools) if err != nil { return &tools.Result{ Output: fmt.Sprintf("Sub-agent execution failed: %v", err), IsError: true, }, nil } if progress != nil { progress(1.0, "Sub-agent completed") } return &tools.Result{ Output: result, IsError: false, }, nil } // executeBackground 后台模式:立即返回任务 ID. func (t *AgentTool) executeBackground(req AgentRunRequest, subTools []tools.Tool, progress tools.ProgressFunc) (*tools.Result, error) { taskID, err := t.executor.RunSubAgentBackground(req, subTools) if err != nil { return &tools.Result{ Output: fmt.Sprintf("Failed to start background sub-agent: %v", err), IsError: true, }, nil } if progress != nil { progress(1.0, "Background sub-agent started") } return &tools.Result{ Output: fmt.Sprintf("Background sub-agent started.\nTask ID: %s\nDescription: %s\n\nUse TaskList to check progress.", taskID, req.Description), IsError: false, Data: map[string]any{ "task_id": taskID, "mode": "background", "description": req.Description, }, }, nil } // executeWorktree Worktree 模式:在独立的 git worktree 中运行. func (t *AgentTool) executeWorktree(ctx context.Context, req AgentRunRequest, subTools []tools.Tool, progress tools.ProgressFunc) (*tools.Result, error) { if progress != nil { progress(0.1, "Creating git worktree for sub-agent") } result, err := t.executor.RunSubAgentWorktree(ctx, req, subTools) if err != nil { return &tools.Result{ Output: fmt.Sprintf("Sub-agent worktree execution failed: %v", err), IsError: true, }, nil } if progress != nil { progress(1.0, "Sub-agent completed (worktree mode)") } return &tools.Result{ Output: result, IsError: false, }, nil } // fallbackResult 在执行器未配置时返回信息性结果. func (t *AgentTool) fallbackResult(req AgentRunRequest, subTools []tools.Tool, mode string) (*tools.Result, error) { toolNames := make([]string, 0, len(subTools)) for _, tool := range subTools { toolNames = append(toolNames, tool.Name()) } var info strings.Builder fmt.Fprintf(&info, "Sub-agent task: %s\n", req.Description) fmt.Fprintf(&info, "Prompt: %s\n", req.Prompt) fmt.Fprintf(&info, "Mode: %s\n", mode) fmt.Fprintf(&info, "Available tools: [%s]\n", strings.Join(toolNames, ", ")) if req.Model != "" { fmt.Fprintf(&info, "Model: %s\n", req.Model) } if req.BranchName != "" { fmt.Fprintf(&info, "Branch: %s\n", req.BranchName) } if req.AgentType != "" { fmt.Fprintf(&info, "AgentType: %s\n", req.AgentType) } fmt.Fprintf(&info, "\nNote: Sub-agent execution is not available in the current configuration.") return &tools.Result{ Output: info.String(), IsError: false, Data: map[string]any{ "prompt": req.Prompt, "description": req.Description, "model": req.Model, "mode": mode, "agent_type": req.AgentType, "tool_count": len(subTools), }, }, nil }