- Updated the API documentation in TODO.md to reflect the completion of the Go API implementation and end-to-end tests for the main flow (P0 → P1 → P2 → P3 → P4). - Clarified that Process handlers and JSAPI are optional features, moving them to Phase 12, while emphasizing that the Go API is sufficient for MVP integration. - Removed obsolete API files (`api.go`, `jsapi.go`, `process.go`) as they were stubs and not implemented in Phase 10, streamlining the codebase. - Revised the structure of the TODO.md to better outline the goals and tasks for Phases 11 and 12, ensuring clear tracking of progress and dependencies.
192 lines
4.7 KiB
Go
192 lines
4.7 KiB
Go
package api
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"sync"
|
|
|
|
"github.com/yaoapp/yao/agent/robot/store"
|
|
"github.com/yaoapp/yao/agent/robot/types"
|
|
)
|
|
|
|
// executionStore singleton
|
|
var (
|
|
execStore *store.ExecutionStore
|
|
execStoreOnce sync.Once
|
|
)
|
|
|
|
// getExecutionStore returns the singleton execution store
|
|
func getExecutionStore() *store.ExecutionStore {
|
|
execStoreOnce.Do(func() {
|
|
execStore = store.NewExecutionStore()
|
|
})
|
|
return execStore
|
|
}
|
|
|
|
// ResetExecutionStore resets the singleton for testing purposes
|
|
// This should only be called in tests
|
|
func ResetExecutionStore() {
|
|
execStoreOnce = sync.Once{}
|
|
execStore = nil
|
|
}
|
|
|
|
// ==================== Execution Query API ====================
|
|
// These functions query and manage execution history
|
|
|
|
// GetExecution returns a specific execution by ID
|
|
func GetExecution(ctx *types.Context, execID string) (*types.Execution, error) {
|
|
if execID == "" {
|
|
return nil, fmt.Errorf("execution_id is required")
|
|
}
|
|
|
|
// Try to get from execution store
|
|
record, err := getExecutionStore().Get(context.Background(), execID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to get execution: %w", err)
|
|
}
|
|
if record == nil {
|
|
return nil, fmt.Errorf("execution not found: %s", execID)
|
|
}
|
|
|
|
return record.ToExecution(), nil
|
|
}
|
|
|
|
// ListExecutions returns execution history for a robot
|
|
func ListExecutions(ctx *types.Context, memberID string, query *ExecutionQuery) (*ExecutionResult, error) {
|
|
if memberID == "" {
|
|
return nil, fmt.Errorf("member_id is required")
|
|
}
|
|
|
|
if query == nil {
|
|
query = &ExecutionQuery{}
|
|
}
|
|
query.applyDefaults()
|
|
|
|
// Build list options
|
|
opts := &store.ListOptions{
|
|
MemberID: memberID,
|
|
Limit: query.PageSize,
|
|
Offset: (query.Page - 1) * query.PageSize,
|
|
OrderBy: "start_time desc",
|
|
}
|
|
|
|
if query.Status != "" {
|
|
opts.Status = query.Status
|
|
}
|
|
if query.Trigger != "" {
|
|
opts.TriggerType = query.Trigger
|
|
}
|
|
|
|
// Query from store
|
|
records, err := getExecutionStore().List(context.Background(), opts)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to list executions: %w", err)
|
|
}
|
|
|
|
// Convert to Execution slice
|
|
executions := make([]*types.Execution, 0, len(records))
|
|
for _, record := range records {
|
|
executions = append(executions, record.ToExecution())
|
|
}
|
|
|
|
// Get total count
|
|
// Note: For accurate total, ExecutionStore.List should return total count
|
|
// Current implementation returns estimated total based on returned records
|
|
total := len(records)
|
|
if total >= query.PageSize {
|
|
// Has more records, need to query total count
|
|
// For now, indicate there might be more by setting total to -1
|
|
// UI should handle this as "has more"
|
|
countOpts := &store.ListOptions{MemberID: memberID}
|
|
if query.Status != "" {
|
|
countOpts.Status = query.Status
|
|
}
|
|
if query.Trigger != "" {
|
|
countOpts.TriggerType = query.Trigger
|
|
}
|
|
allRecords, _ := getExecutionStore().List(context.Background(), countOpts)
|
|
total = len(allRecords)
|
|
}
|
|
|
|
return &ExecutionResult{
|
|
Data: executions,
|
|
Total: total,
|
|
Page: query.Page,
|
|
PageSize: query.PageSize,
|
|
}, nil
|
|
}
|
|
|
|
// ==================== Execution Control API ====================
|
|
// These functions control running executions
|
|
|
|
// PauseExecution pauses a running execution
|
|
func PauseExecution(ctx *types.Context, execID string) error {
|
|
if execID == "" {
|
|
return fmt.Errorf("execution_id is required")
|
|
}
|
|
|
|
mgr, err := getManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mgr.PauseExecution(ctx, execID)
|
|
}
|
|
|
|
// ResumeExecution resumes a paused execution
|
|
func ResumeExecution(ctx *types.Context, execID string) error {
|
|
if execID == "" {
|
|
return fmt.Errorf("execution_id is required")
|
|
}
|
|
|
|
mgr, err := getManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mgr.ResumeExecution(ctx, execID)
|
|
}
|
|
|
|
// StopExecution stops a running execution
|
|
func StopExecution(ctx *types.Context, execID string) error {
|
|
if execID == "" {
|
|
return fmt.Errorf("execution_id is required")
|
|
}
|
|
|
|
mgr, err := getManager()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return mgr.StopExecution(ctx, execID)
|
|
}
|
|
|
|
// ==================== Execution Status API ====================
|
|
|
|
// GetExecutionStatus returns the current status of an execution
|
|
// This combines stored data with runtime state
|
|
func GetExecutionStatus(ctx *types.Context, execID string) (*types.Execution, error) {
|
|
if execID == "" {
|
|
return nil, fmt.Errorf("execution_id is required")
|
|
}
|
|
|
|
// Get from store first
|
|
exec, err := GetExecution(ctx, execID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// If manager is running, check for runtime state
|
|
mgr, mgrErr := getManager()
|
|
if mgrErr == nil {
|
|
// Check if execution is being tracked (running)
|
|
ctrlExec, ctrlErr := mgr.GetExecutionStatus(execID)
|
|
if ctrlErr == nil && ctrlExec != nil {
|
|
// Update with runtime state
|
|
exec.Status = ctrlExec.Status
|
|
exec.Phase = ctrlExec.Phase
|
|
}
|
|
}
|
|
|
|
return exec, nil
|
|
}
|