yao/agent/robot/api/interact.go
Max bc4787f857 Update executor to support V2 execution model and enhance event handling
- Implement V2 execution model in the standard executor, simplifying task execution to a single call without validation loops.
- Introduce support for resuming suspended executions, allowing for human input during task processing.
- Enhance event handling by pushing task completion and failure events to the event bus for better tracking and integration.
- Update tests to reflect changes in execution flow and ensure robust handling of task statuses and results.
2026-02-25 18:40:48 +08:00

131 lines
4.1 KiB
Go

package api
import (
"fmt"
"github.com/yaoapp/yao/agent/robot/executor/standard"
"github.com/yaoapp/yao/agent/robot/manager"
"github.com/yaoapp/yao/agent/robot/types"
)
// InteractRequest represents a unified interaction with a robot.
type InteractRequest struct {
ExecutionID string `json:"execution_id,omitempty"`
TaskID string `json:"task_id,omitempty"`
Source types.InteractSource `json:"source,omitempty"`
Message string `json:"message"`
Action string `json:"action,omitempty"`
}
// InteractResult is the response from an interaction.
type InteractResult struct {
ExecutionID string `json:"execution_id,omitempty"`
Status string `json:"status"`
Message string `json:"message,omitempty"`
ChatID string `json:"chat_id,omitempty"`
Reply string `json:"reply,omitempty"`
WaitForMore bool `json:"wait_for_more,omitempty"`
}
// Interact handles all human-robot interactions through a unified entry point.
//
// Routing logic:
// - If manager is running, delegate to Manager.HandleInteract (full V2 flow with Host Agent)
// - Otherwise, use legacy direct-executor path for backward compatibility
func Interact(ctx *types.Context, memberID string, req *InteractRequest) (*InteractResult, error) {
if memberID == "" {
return nil, fmt.Errorf("member_id is required")
}
if req == nil {
return nil, fmt.Errorf("interact request is required")
}
// Try V2 path via manager
mgr, err := getManager()
if err == nil && mgr != nil {
return managerInteract(ctx, mgr, memberID, req)
}
// V1 fallback: require execution_id for direct resume
if req.ExecutionID == "" {
return nil, fmt.Errorf("execution_id is required for current version (Host Agent deferred)")
}
return legacyResume(ctx, req)
}
// managerInteract delegates to the manager's HandleInteract.
func managerInteract(ctx *types.Context, mgr *manager.Manager, memberID string, req *InteractRequest) (*InteractResult, error) {
mgrReq := &manager.InteractRequest{
ExecutionID: req.ExecutionID,
TaskID: req.TaskID,
Source: req.Source,
Message: req.Message,
Action: req.Action,
}
resp, err := mgr.HandleInteract(ctx, memberID, mgrReq)
if err != nil {
return nil, err
}
return &InteractResult{
ExecutionID: resp.ExecutionID,
Status: resp.Status,
Message: resp.Message,
ChatID: resp.ChatID,
Reply: resp.Reply,
WaitForMore: resp.WaitForMore,
}, nil
}
// legacyResume handles the direct executor resume path (backward compatible).
func legacyResume(ctx *types.Context, req *InteractRequest) (*InteractResult, error) {
executor := standard.New()
err := executor.Resume(ctx, req.ExecutionID, req.Message)
if err != nil {
if err == types.ErrExecutionSuspended {
return &InteractResult{
ExecutionID: req.ExecutionID,
Status: "waiting",
Message: "Execution suspended again: needs more input",
}, nil
}
return nil, fmt.Errorf("failed to resume execution: %w", err)
}
return &InteractResult{
ExecutionID: req.ExecutionID,
Status: "resumed",
Message: "Execution resumed and completed successfully",
}, nil
}
// Reply is a semantic shortcut for replying to a specific waiting task.
func Reply(ctx *types.Context, memberID string, execID string, taskID string, message string) (*InteractResult, error) {
return Interact(ctx, memberID, &InteractRequest{
ExecutionID: execID,
TaskID: taskID,
Source: types.InteractSourceUI,
Message: message,
})
}
// Confirm is a semantic shortcut for confirming a pending execution.
func Confirm(ctx *types.Context, memberID string, execID string, message string) (*InteractResult, error) {
return Interact(ctx, memberID, &InteractRequest{
ExecutionID: execID,
Source: types.InteractSourceUI,
Message: message,
Action: "confirm",
})
}
// CancelExecution cancels a waiting/confirming execution via the manager.
func CancelExecution(ctx *types.Context, execID string) error {
mgr, err := getManager()
if err != nil {
return fmt.Errorf("cancel not available: %w", err)
}
return mgr.CancelExecution(ctx, execID)
}