diff --git a/agent/robot/DESIGN.md b/agent/robot/DESIGN.md
index 113e9bf1..e753dc85 100644
--- a/agent/robot/DESIGN.md
+++ b/agent/robot/DESIGN.md
@@ -351,6 +351,7 @@ P2 Agent reads Goals markdown and breaks into executable tasks:
```go
type Task struct {
ID string // unique task ID
+ Description string // human-readable task description (for UI display)
Messages []context.Message // original input (text, images, files, audio)
GoalRef string // reference to goal (e.g., "Goal 1")
Source TaskSource // auto | human | event
@@ -698,17 +699,18 @@ Save to KB:
```go
type Config struct {
- Triggers *Triggers `json:"triggers,omitempty"`
- Clock *Clock `json:"clock,omitempty"`
- Identity *Identity `json:"identity"`
- Quota *Quota `json:"quota"`
- KB *KB `json:"kb,omitempty"` // shared KB (same as assistant)
- DB *DB `json:"db,omitempty"` // shared DB (same as assistant)
- Learn *Learn `json:"learn,omitempty"` // learning for private KB
- Resources *Resources `json:"resources"`
- Delivery *DeliveryPreferences `json:"delivery,omitempty"`
- Events []Event `json:"events,omitempty"`
- Executor *Executor `json:"executor,omitempty"` // executor mode settings
+ Triggers *Triggers `json:"triggers,omitempty"`
+ Clock *Clock `json:"clock,omitempty"`
+ Identity *Identity `json:"identity"`
+ Quota *Quota `json:"quota"`
+ KB *KB `json:"kb,omitempty"` // shared KB (same as assistant)
+ DB *DB `json:"db,omitempty"` // shared DB (same as assistant)
+ Learn *Learn `json:"learn,omitempty"` // learning for private KB
+ Resources *Resources `json:"resources"`
+ Delivery *DeliveryPreferences `json:"delivery,omitempty"`
+ Events []Event `json:"events,omitempty"`
+ Executor *Executor `json:"executor,omitempty"` // executor mode settings
+ DefaultLocale string `json:"default_locale,omitempty"` // default language for clock/event triggers ("en-US", "zh-CN")
}
```
diff --git a/agent/robot/TECHNICAL.md b/agent/robot/TECHNICAL.md
index 7067e841..74683dda 100644
--- a/agent/robot/TECHNICAL.md
+++ b/agent/robot/TECHNICAL.md
@@ -938,16 +938,17 @@ import "time"
// Config - robot_config in __yao.member
type Config struct {
- Triggers *Triggers `json:"triggers,omitempty"`
- Clock *Clock `json:"clock,omitempty"`
- Identity *Identity `json:"identity"`
- Quota *Quota `json:"quota,omitempty"`
- KB *KB `json:"kb,omitempty"` // shared knowledge base (same as assistant)
- DB *DB `json:"db,omitempty"` // shared database (same as assistant)
- Learn *Learn `json:"learn,omitempty"` // learning config for private KB
- Resources *Resources `json:"resources,omitempty"`
- Delivery *DeliveryPreferences `json:"delivery,omitempty"` // see section 6.2
- Events []Event `json:"events,omitempty"`
+ Triggers *Triggers `json:"triggers,omitempty"`
+ Clock *Clock `json:"clock,omitempty"`
+ Identity *Identity `json:"identity"`
+ Quota *Quota `json:"quota,omitempty"`
+ KB *KB `json:"kb,omitempty"` // shared knowledge base (same as assistant)
+ DB *DB `json:"db,omitempty"` // shared database (same as assistant)
+ Learn *Learn `json:"learn,omitempty"` // learning config for private KB
+ Resources *Resources `json:"resources,omitempty"`
+ Delivery *DeliveryPreferences `json:"delivery,omitempty"` // see section 6.2
+ Events []Event `json:"events,omitempty"`
+ DefaultLocale string `json:"default_locale,omitempty"` // Default language for clock/event triggers (e.g., "en-US", "zh-CN")
}
// Validate validates the config
@@ -1238,6 +1239,10 @@ type Execution struct {
Phase Phase `json:"phase"`
Error string `json:"error,omitempty"`
+ // UI display fields (updated by executor at each phase)
+ // These provide human-readable status for frontend display
+ Name string `json:"name,omitempty"` // Execution title (updated when goals complete)
+ CurrentTaskName string `json:"current_task_name,omitempty"` // Current task description (updated during run phase)
// Trigger input (stored for traceability)
Input *TriggerInput `json:"input,omitempty"` // original trigger input
@@ -1263,6 +1268,7 @@ type TriggerInput struct {
Action InterventionAction `json:"action,omitempty"` // task.add, goal.adjust, etc.
Messages []context.Message `json:"messages,omitempty"` // user's input (text, images, files)
UserID string `json:"user_id,omitempty"` // who triggered
+ Locale string `json:"locale,omitempty"` // language for UI display (e.g., "en-US", "zh-CN")
// For event trigger
Source EventSource `json:"source,omitempty"` // webhook | database
@@ -1308,10 +1314,11 @@ type DeliveryTarget struct {
// Task - planned task (structured, for execution)
type Task struct {
- ID string `json:"id"`
- Messages []context.Message `json:"messages"` // original input (text, images, files)
- GoalRef string `json:"goal_ref,omitempty"` // reference to goal (e.g., "Goal 1")
- Source TaskSource `json:"source"` // auto | human | event
+ ID string `json:"id"`
+ Description string `json:"description,omitempty"` // human-readable task description (for UI display)
+ Messages []context.Message `json:"messages"` // original input (text, images, files)
+ GoalRef string `json:"goal_ref,omitempty"` // reference to goal (e.g., "Goal 1")
+ Source TaskSource `json:"source"` // auto | human | event
// Executor
ExecutorType ExecutorType `json:"executor_type"`
diff --git a/agent/robot/api/robot.go b/agent/robot/api/robot.go
index dc7b918b..2f1f1ea8 100644
--- a/agent/robot/api/robot.go
+++ b/agent/robot/api/robot.go
@@ -21,6 +21,9 @@ const memberModel = "__yao.member"
// robotStore is the shared robot store instance
var robotStore = store.NewRobotStore()
+// executionStore is the shared execution store instance
+var executionStore = store.NewExecutionStore()
+
// GetRobot returns a robot by member ID
// Returns the robot from cache if available, otherwise loads from database
func GetRobot(ctx *types.Context, memberID string) (*types.Robot, error) {
@@ -62,9 +65,11 @@ func ListRobots(ctx *types.Context, query *ListQuery) (*ListResult, error) {
return listRobotsFromDB(query)
}
- // If only teamID specified (no other filters), use cache for faster lookup
- // Note: Cache only contains autonomous_mode=true robots, so this is safe
- if query.TeamID != "" && query.Status == "" && query.Keywords == "" && query.ClockMode == "" {
+ // If only teamID specified AND explicitly filtering for autonomous_mode=true, use cache
+ // Cache only contains autonomous_mode=true robots
+ // When autonomous_mode is not specified or false, must query database to include all robots
+ if query.TeamID != "" && query.Status == "" && query.Keywords == "" && query.ClockMode == "" &&
+ query.AutonomousMode != nil && *query.AutonomousMode == true {
robots := mgr.Cache().List(query.TeamID)
return paginateRobots(robots, query), nil
}
@@ -93,7 +98,6 @@ func GetRobotStatus(ctx *types.Context, memberID string) (*RobotState, error) {
DisplayName: robot.DisplayName,
Bio: robot.Bio,
Status: robot.Status,
- Running: robot.RunningCount(),
MaxRunning: 2, // default
}
@@ -107,11 +111,33 @@ func GetRobotStatus(ctx *types.Context, memberID string) (*RobotState, error) {
state.MaxRunning = robot.Config.Quota.GetMax()
}
- // Get running execution IDs
- executions := robot.GetExecutions()
- state.RunningIDs = make([]string, 0, len(executions))
- for _, exec := range executions {
- state.RunningIDs = append(state.RunningIDs, exec.ID)
+ // Get running execution IDs from ExecutionStore (more reliable than in-memory)
+ // This ensures we get accurate status even when robot is loaded from database
+ runningExecs, err := executionStore.List(context.Background(), &store.ListOptions{
+ MemberID: memberID,
+ Status: types.ExecRunning,
+ Limit: 100,
+ })
+ if err == nil && len(runningExecs) > 0 {
+ state.Running = len(runningExecs)
+ state.RunningIDs = make([]string, 0, len(runningExecs))
+ for _, exec := range runningExecs {
+ state.RunningIDs = append(state.RunningIDs, exec.ExecutionID)
+ }
+ // Update status based on running count
+ state.Status = types.RobotWorking
+ } else {
+ // No running executions from store, check in-memory
+ executions := robot.GetExecutions()
+ state.Running = len(executions)
+ state.RunningIDs = make([]string, 0, len(executions))
+ for _, exec := range executions {
+ state.RunningIDs = append(state.RunningIDs, exec.ID)
+ }
+ // If there are running executions in memory, update status
+ if state.Running > 0 {
+ state.Status = types.RobotWorking
+ }
}
// Set last run time
diff --git a/agent/robot/api/types.go b/agent/robot/api/types.go
index 3a990bc6..0f6d55fb 100644
--- a/agent/robot/api/types.go
+++ b/agent/robot/api/types.go
@@ -64,6 +64,9 @@ type TriggerRequest struct {
// Executor mode (optional, overrides robot config)
ExecutorMode types.ExecutorMode `json:"executor_mode,omitempty"`
+
+ // i18n support
+ Locale string `json:"locale,omitempty"` // Locale for UI messages (e.g., "en", "zh")
}
// InsertPosition - where to insert task in queue
diff --git a/agent/robot/executor/standard/delivery.go b/agent/robot/executor/standard/delivery.go
index 0d050e1d..4d312c69 100644
--- a/agent/robot/executor/standard/delivery.go
+++ b/agent/robot/executor/standard/delivery.go
@@ -30,6 +30,10 @@ func (e *Executor) RunDelivery(ctx *robottypes.Context, exec *robottypes.Executi
return fmt.Errorf("robot not found in execution")
}
+ // Update UI field with i18n
+ locale := getEffectiveLocale(robot, exec.Input)
+ e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "generating_delivery"))
+
// Get agent ID for delivery phase
agentID := "__yao.delivery" // default
if robot.Config != nil && robot.Config.Resources != nil {
@@ -108,6 +112,10 @@ func (e *Executor) routeToDeliveryCenter(ctx *robottypes.Context, exec *robottyp
return nil
}
+ // Update UI field to show delivery is in progress
+ locale := getEffectiveLocale(robot, exec.Input)
+ e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "sending_delivery"))
+
// Create Delivery Center and execute
center := NewDeliveryCenter()
results, err := center.Deliver(ctx, exec.Delivery.Content, &robottypes.DeliveryContext{
diff --git a/agent/robot/executor/standard/executor.go b/agent/robot/executor/standard/executor.go
index dd2cb1d0..685d5211 100644
--- a/agent/robot/executor/standard/executor.go
+++ b/agent/robot/executor/standard/executor.go
@@ -20,6 +20,7 @@ import (
type Executor struct {
config types.Config
store *store.ExecutionStore
+ robotStore *store.RobotStore
execCount atomic.Int32
currentCount atomic.Int32
onStart func()
@@ -29,15 +30,17 @@ type Executor struct {
// New creates a new standard executor
func New() *Executor {
return &Executor{
- store: store.NewExecutionStore(),
+ store: store.NewExecutionStore(),
+ robotStore: store.NewRobotStore(),
}
}
// NewWithConfig creates a new standard executor with configuration
func NewWithConfig(config types.Config) *Executor {
return &Executor{
- config: config,
- store: store.NewExecutionStore(),
+ config: config,
+ store: store.NewExecutionStore(),
+ robotStore: store.NewRobotStore(),
}
}
@@ -54,6 +57,7 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
}
// Create execution (Job system removed, using ExecutionStore only)
+ input := types.BuildTriggerInput(trigger, data)
exec := &robottypes.Execution{
ID: utils.NewID(),
MemberID: robot.MemberID,
@@ -62,9 +66,12 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
StartTime: time.Now(),
Status: robottypes.ExecPending,
Phase: robottypes.AllPhases[startPhaseIndex],
- Input: types.BuildTriggerInput(trigger, data),
+ Input: input,
}
+ // Initialize UI display fields (with i18n support)
+ exec.Name, exec.CurrentTaskName = e.initUIFields(trigger, input, robot)
+
// Set robot reference for phase methods
exec.SetRobot(robot)
@@ -90,7 +97,19 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
}).Warn("Execution quota exceeded")
return nil, robottypes.ErrQuotaExceeded
}
- defer robot.RemoveExecution(exec.ID)
+ // Defer: remove execution from robot's tracking and update robot status if no more executions
+ defer func() {
+ robot.RemoveExecution(exec.ID)
+ // Update robot status to idle if no more running executions
+ if robot.RunningCount() == 0 && !e.config.SkipPersistence && e.robotStore != nil {
+ if err := e.robotStore.UpdateStatus(ctx.Context, robot.MemberID, robottypes.RobotIdle); err != nil {
+ log.With(log.F{
+ "member_id": robot.MemberID,
+ "error": err,
+ }).Warn("Failed to update robot status to idle: %v", err)
+ }
+ }
+ }()
// Track execution count
e.execCount.Add(1)
@@ -123,6 +142,16 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
}
}
+ // Update robot status to working (when execution starts)
+ if !e.config.SkipPersistence && e.robotStore != nil {
+ if err := e.robotStore.UpdateStatus(ctx.Context, robot.MemberID, robottypes.RobotWorking); err != nil {
+ log.With(log.F{
+ "member_id": robot.MemberID,
+ "error": err,
+ }).Warn("Failed to update robot status to working: %v", err)
+ }
+ }
+
// Check for simulated failure (for testing)
if dataStr, ok := data.(string); ok && dataStr == "simulate_failure" {
exec.Status = robottypes.ExecFailed
@@ -138,12 +167,23 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
return exec, nil
}
+ // Determine locale for UI messages
+ locale := getEffectiveLocale(robot, exec.Input)
+
// Execute phases
phases := robottypes.AllPhases[startPhaseIndex:]
for _, phase := range phases {
if err := e.runPhase(ctx, exec, phase, data); err != nil {
exec.Status = robottypes.ExecFailed
exec.Error = err.Error()
+
+ // Update UI field for failure with i18n
+ // Use concise phase name, NOT the full error message (error is in exec.Error)
+ failedPrefix := getLocalizedMessage(locale, "failed_prefix")
+ phaseName := getLocalizedMessage(locale, "phase_"+string(phase))
+ failureMsg := failedPrefix + phaseName
+ e.updateUIFields(ctx, exec, "", failureMsg)
+
log.With(log.F{
"execution_id": exec.ID,
"member_id": exec.MemberID,
@@ -163,6 +203,9 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
now := time.Now()
exec.EndTime = &now
+ // Update UI field for completion with i18n
+ e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "completed"))
+
duration := now.Sub(exec.StartTime)
log.With(log.F{
"execution_id": exec.ID,
@@ -193,6 +236,17 @@ func (e *Executor) runPhase(ctx *robottypes.Context, exec *robottypes.Execution,
"phase": string(phase),
}).Info("Phase started: %s", phase)
+ // Persist phase change immediately (so frontend sees current phase)
+ if !e.config.SkipPersistence && e.store != nil {
+ if err := e.store.UpdatePhase(ctx.Context, exec.ID, phase, nil); err != nil {
+ log.With(log.F{
+ "execution_id": exec.ID,
+ "phase": string(phase),
+ "error": err,
+ }).Warn("Failed to persist phase start: %v", err)
+ }
+ }
+
if e.config.OnPhaseStart != nil {
e.config.OnPhaseStart(phase)
}
@@ -301,5 +355,198 @@ func (e *Executor) simulateStreamDelay() {
time.Sleep(DefaultStreamDelay)
}
+// initUIFields initializes UI display fields based on trigger type with i18n support
+// Returns (name, currentTaskName)
+func (e *Executor) initUIFields(trigger robottypes.TriggerType, input *robottypes.TriggerInput, robot *robottypes.Robot) (string, string) {
+ // Determine locale for UI messages
+ locale := getEffectiveLocale(robot, input)
+
+ // Get localized default messages
+ name := getLocalizedMessage(locale, "preparing")
+ currentTaskName := getLocalizedMessage(locale, "starting")
+
+ switch trigger {
+ case robottypes.TriggerHuman:
+ // For human trigger, extract name from first message
+ if input != nil && len(input.Messages) > 0 {
+ if content, ok := input.Messages[0].GetContentAsString(); ok && content != "" {
+ // Use first 100 chars of message as name
+ name = content
+ if len(name) > 100 {
+ name = name[:100] + "..."
+ }
+ }
+ }
+ case robottypes.TriggerClock:
+ name = getLocalizedMessage(locale, "scheduled_execution")
+ case robottypes.TriggerEvent:
+ if input != nil && input.EventType != "" {
+ name = getLocalizedMessage(locale, "event_prefix") + input.EventType
+ } else {
+ name = getLocalizedMessage(locale, "event_triggered")
+ }
+ }
+
+ return name, currentTaskName
+}
+
+// getEffectiveLocale determines the locale for UI display
+// Priority: input.Locale > robot.Config.DefaultLocale > "en"
+func getEffectiveLocale(robot *robottypes.Robot, input *robottypes.TriggerInput) string {
+ // 1. Human trigger with explicit locale
+ if input != nil && input.Locale != "" {
+ return input.Locale
+ }
+ // 2. Robot configured default
+ if robot != nil && robot.Config != nil {
+ return robot.Config.GetDefaultLocale()
+ }
+ // 3. System default
+ return "en"
+}
+
+// i18n message maps for UI display fields
+// Use simple locale codes (en, zh) as keys
+var uiMessages = map[string]map[string]string{
+ "en": {
+ "preparing": "Preparing...",
+ "starting": "Starting...",
+ "scheduled_execution": "Scheduled execution",
+ "event_prefix": "Event: ",
+ "event_triggered": "Event triggered",
+ "analyzing_context": "Analyzing context...",
+ "planning_goals": "Planning goals...",
+ "breaking_down_tasks": "Breaking down tasks...",
+ "generating_delivery": "Generating delivery content...",
+ "sending_delivery": "Sending delivery...",
+ "learning_from_exec": "Learning from execution...",
+ "completed": "Completed",
+ "failed_prefix": "Failed at ",
+ "task_prefix": "Task",
+ // Phase names for failure messages
+ "phase_inspiration": "inspiration",
+ "phase_goals": "goals",
+ "phase_tasks": "tasks",
+ "phase_run": "execution",
+ "phase_delivery": "delivery",
+ "phase_learning": "learning",
+ },
+ "zh": {
+ "preparing": "准备中...",
+ "starting": "启动中...",
+ "scheduled_execution": "定时执行",
+ "event_prefix": "事件: ",
+ "event_triggered": "事件触发",
+ "analyzing_context": "分析上下文...",
+ "planning_goals": "规划目标...",
+ "breaking_down_tasks": "分解任务...",
+ "generating_delivery": "生成交付内容...",
+ "sending_delivery": "正在发送...",
+ "learning_from_exec": "学习执行经验...",
+ "completed": "已完成",
+ "failed_prefix": "失败于",
+ "task_prefix": "任务",
+ // Phase names for failure messages
+ "phase_inspiration": "灵感阶段",
+ "phase_goals": "目标阶段",
+ "phase_tasks": "任务阶段",
+ "phase_run": "执行阶段",
+ "phase_delivery": "交付阶段",
+ "phase_learning": "学习阶段",
+ },
+}
+
+// getLocalizedMessage returns a localized message for the given key
+func getLocalizedMessage(locale string, key string) string {
+ if messages, ok := uiMessages[locale]; ok {
+ if msg, ok := messages[key]; ok {
+ return msg
+ }
+ }
+ // Fallback to English
+ if messages, ok := uiMessages["en"]; ok {
+ if msg, ok := messages[key]; ok {
+ return msg
+ }
+ }
+ return key // Return key as fallback
+}
+
+// updateUIFields updates UI display fields and persists to database
+func (e *Executor) updateUIFields(ctx *robottypes.Context, exec *robottypes.Execution, name string, currentTaskName string) {
+ // Update in-memory execution
+ if name != "" {
+ exec.Name = name
+ }
+ if currentTaskName != "" {
+ exec.CurrentTaskName = currentTaskName
+ }
+
+ // Persist to database
+ if !e.config.SkipPersistence && e.store != nil {
+ if err := e.store.UpdateUIFields(ctx.Context, exec.ID, name, currentTaskName); err != nil {
+ log.With(log.F{
+ "execution_id": exec.ID,
+ "error": err,
+ }).Warn("Failed to update UI fields: %v", err)
+ }
+ }
+}
+
+// updateTasksState persists the current tasks array with status to database
+// This should be called after each task status change for real-time UI updates
+func (e *Executor) updateTasksState(ctx *robottypes.Context, exec *robottypes.Execution) {
+ if e.config.SkipPersistence || e.store == nil {
+ return
+ }
+
+ // Convert Current to store.CurrentState
+ var current *store.CurrentState
+ if exec.Current != nil {
+ current = &store.CurrentState{
+ TaskIndex: exec.Current.TaskIndex,
+ Progress: exec.Current.Progress,
+ }
+ }
+
+ if err := e.store.UpdateTasks(ctx.Context, exec.ID, exec.Tasks, current); err != nil {
+ log.With(log.F{
+ "execution_id": exec.ID,
+ "error": err,
+ }).Warn("Failed to update tasks state: %v", err)
+ }
+}
+
+// extractGoalName extracts the execution name from goals output
+func extractGoalName(goals *robottypes.Goals) string {
+ if goals == nil || goals.Content == "" {
+ return ""
+ }
+
+ // Extract first line or first sentence as the goal name
+ content := goals.Content
+ // Find first newline
+ if idx := indexAny(content, "\n\r"); idx > 0 {
+ content = content[:idx]
+ }
+ // Limit length
+ if len(content) > 150 {
+ content = content[:150] + "..."
+ }
+ return content
+}
+
+// indexAny returns the index of the first occurrence of any char in chars
+func indexAny(s string, chars string) int {
+ for i, c := range s {
+ for _, ch := range chars {
+ if c == ch {
+ return i
+ }
+ }
+ }
+ return -1
+}
+
// Verify Executor implements types.Executor
var _ types.Executor = (*Executor)(nil)
diff --git a/agent/robot/executor/standard/goals.go b/agent/robot/executor/standard/goals.go
index 5d531e0f..06b837b0 100644
--- a/agent/robot/executor/standard/goals.go
+++ b/agent/robot/executor/standard/goals.go
@@ -23,6 +23,10 @@ func (e *Executor) RunGoals(ctx *robottypes.Context, exec *robottypes.Execution,
return fmt.Errorf("robot not found in execution")
}
+ // Update UI field with i18n
+ locale := getEffectiveLocale(robot, exec.Input)
+ e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "planning_goals"))
+
// Get agent ID for goals phase
agentID := "__yao.goals" // default
if robot.Config != nil && robot.Config.Resources != nil {
@@ -113,6 +117,11 @@ func (e *Executor) RunGoals(ctx *robottypes.Context, exec *robottypes.Execution,
return fmt.Errorf("goals agent (%s) returned empty content", agentID)
}
+ // Update Name from goals content (extract first line as execution title)
+ if goalName := extractGoalName(exec.Goals); goalName != "" {
+ e.updateUIFields(ctx, exec, goalName, "")
+ }
+
return nil
}
diff --git a/agent/robot/executor/standard/inspiration.go b/agent/robot/executor/standard/inspiration.go
index 6354994f..99d24f30 100644
--- a/agent/robot/executor/standard/inspiration.go
+++ b/agent/robot/executor/standard/inspiration.go
@@ -23,6 +23,10 @@ func (e *Executor) RunInspiration(ctx *robottypes.Context, exec *robottypes.Exec
return fmt.Errorf("robot not found in execution")
}
+ // Update UI field with i18n
+ locale := getEffectiveLocale(robot, exec.Input)
+ e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "analyzing_context"))
+
// Build clock context from trigger input or current time
var clock *robottypes.ClockContext
if exec.Input != nil && exec.Input.Clock != nil {
diff --git a/agent/robot/executor/standard/learning.go b/agent/robot/executor/standard/learning.go
index 727bc6be..d3f78f4c 100644
--- a/agent/robot/executor/standard/learning.go
+++ b/agent/robot/executor/standard/learning.go
@@ -20,6 +20,13 @@ import (
//
// TODO: Implement real learning extraction
func (e *Executor) RunLearning(ctx *robottypes.Context, exec *robottypes.Execution, _ interface{}) error {
+ // Get robot for locale
+ robot := exec.GetRobot()
+
+ // Update UI field with i18n
+ locale := getEffectiveLocale(robot, exec.Input)
+ e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "learning_from_exec"))
+
e.simulateStreamDelay()
exec.Learning = []robottypes.LearningEntry{
diff --git a/agent/robot/executor/standard/run.go b/agent/robot/executor/standard/run.go
index 701f703c..f48c2644 100644
--- a/agent/robot/executor/standard/run.go
+++ b/agent/robot/executor/standard/run.go
@@ -64,6 +64,9 @@ func (e *Executor) RunExecution(ctx *robottypes.Context, exec *robottypes.Execut
config = DefaultRunConfig()
}
+ // Determine locale for UI messages
+ locale := getEffectiveLocale(robot, exec.Input)
+
// Initialize results slice
exec.Results = make([]robottypes.TaskResult, 0, len(exec.Tasks))
@@ -81,11 +84,18 @@ func (e *Executor) RunExecution(ctx *robottypes.Context, exec *robottypes.Execut
Progress: fmt.Sprintf("%d/%d tasks", i+1, len(exec.Tasks)),
}
+ // Update UI field with current task description (i18n)
+ taskName := formatTaskProgressName(task, i, len(exec.Tasks), locale)
+ e.updateUIFields(ctx, exec, "", taskName)
+
// Mark task as running
task.Status = robottypes.TaskRunning
now := time.Now()
task.StartTime = &now
+ // Persist running state to database
+ e.updateTasksState(ctx, exec)
+
// Build task context with previous results
taskCtx := runner.BuildTaskContext(exec, i)
@@ -107,12 +117,17 @@ func (e *Executor) RunExecution(ctx *robottypes.Context, exec *robottypes.Execut
// Store result
exec.Results = append(exec.Results, *result)
+ // Persist completed/failed state to database
+ e.updateTasksState(ctx, exec)
+
// Check if we should continue on failure
if !result.Success && !config.ContinueOnFailure {
// Mark remaining tasks as skipped
for j := i + 1; j < len(exec.Tasks); j++ {
exec.Tasks[j].Status = robottypes.TaskSkipped
}
+ // Persist skipped state to database
+ e.updateTasksState(ctx, exec)
return fmt.Errorf("task %s failed: %s", task.ID, result.Error)
}
}
@@ -122,3 +137,32 @@ func (e *Executor) RunExecution(ctx *robottypes.Context, exec *robottypes.Execut
return nil
}
+
+// formatTaskProgressName formats a progress name for the current task (used for UI with i18n)
+func formatTaskProgressName(task *robottypes.Task, index int, total int, locale string) string {
+ taskPrefix := getLocalizedMessage(locale, "task_prefix")
+ prefix := fmt.Sprintf("%s %d/%d: ", taskPrefix, index+1, total)
+
+ // Priority 1: Use Description field if available
+ if task.Description != "" {
+ desc := task.Description
+ if len(desc) > 80 {
+ desc = desc[:80] + "..."
+ }
+ return prefix + desc
+ }
+
+ // Priority 2: Try to get description from first message
+ if len(task.Messages) > 0 {
+ if content, ok := task.Messages[0].GetContentAsString(); ok && content != "" {
+ // Truncate if too long
+ if len(content) > 80 {
+ content = content[:80] + "..."
+ }
+ return prefix + content
+ }
+ }
+
+ // Fallback to executor info
+ return prefix + string(task.ExecutorType) + ":" + task.ExecutorID
+}
diff --git a/agent/robot/executor/standard/tasks.go b/agent/robot/executor/standard/tasks.go
index 41751da5..597667af 100644
--- a/agent/robot/executor/standard/tasks.go
+++ b/agent/robot/executor/standard/tasks.go
@@ -23,6 +23,10 @@ func (e *Executor) RunTasks(ctx *robottypes.Context, exec *robottypes.Execution,
return fmt.Errorf("robot not found in execution")
}
+ // Update UI field with i18n
+ locale := getEffectiveLocale(robot, exec.Input)
+ e.updateUIFields(ctx, exec, "", getLocalizedMessage(locale, "breaking_down_tasks"))
+
// Validate: Goals must exist (from P1)
if exec.Goals == nil || exec.Goals.Content == "" {
return fmt.Errorf("goals not available for task planning")
@@ -152,9 +156,11 @@ func ParseTask(data map[string]interface{}, index int) (*robottypes.Task, error)
task.Messages = ParseMessages(messages)
}
- // Optional: description -> convert to message if no messages
- if len(task.Messages) == 0 {
- if desc, ok := data["description"].(string); ok && desc != "" {
+ // Optional: description - save to Description field and convert to message if no messages
+ if desc, ok := data["description"].(string); ok && desc != "" {
+ task.Description = desc
+ // Also convert to Messages for execution if no explicit messages provided
+ if len(task.Messages) == 0 {
task.Messages = []agentcontext.Message{
{Role: agentcontext.RoleUser, Content: desc},
}
diff --git a/agent/robot/executor/standard/tasks_test.go b/agent/robot/executor/standard/tasks_test.go
index 223e0a7f..fcfc5c21 100644
--- a/agent/robot/executor/standard/tasks_test.go
+++ b/agent/robot/executor/standard/tasks_test.go
@@ -309,7 +309,8 @@ func TestParseTasks(t *testing.T) {
// Second task
assert.Equal(t, "task-002", tasks[1].ID)
assert.Equal(t, "experts.text-writer", tasks[1].ExecutorID)
- assert.Len(t, tasks[1].Messages, 1) // description converted to message
+ assert.Equal(t, "Generate report from analysis", tasks[1].Description) // description saved to field
+ assert.Len(t, tasks[1].Messages, 1) // description also converted to message
assert.Equal(t, 1, tasks[1].Order)
})
@@ -329,6 +330,62 @@ func TestParseTasks(t *testing.T) {
assert.Equal(t, "task-001", tasks[0].ID)
})
+ t.Run("saves description to field and preserves explicit messages", func(t *testing.T) {
+ data := []interface{}{
+ map[string]interface{}{
+ "id": "task-001",
+ "executor_type": "agent",
+ "executor_id": "experts.summarizer",
+ "description": "Task description for UI",
+ "messages": []interface{}{
+ map[string]interface{}{
+ "role": "user",
+ "content": "Explicit message content",
+ },
+ },
+ },
+ }
+
+ tasks, err := standard.ParseTasks(data)
+
+ require.NoError(t, err)
+ require.Len(t, tasks, 1)
+
+ // Description should be saved to field
+ assert.Equal(t, "Task description for UI", tasks[0].Description)
+
+ // Explicit messages should be preserved (not overwritten by description)
+ assert.Len(t, tasks[0].Messages, 1)
+ content, ok := tasks[0].Messages[0].GetContentAsString()
+ assert.True(t, ok)
+ assert.Equal(t, "Explicit message content", content)
+ })
+
+ t.Run("converts description to message when no messages provided", func(t *testing.T) {
+ data := []interface{}{
+ map[string]interface{}{
+ "id": "task-001",
+ "executor_type": "agent",
+ "executor_id": "experts.summarizer",
+ "description": "Only description, no messages",
+ },
+ }
+
+ tasks, err := standard.ParseTasks(data)
+
+ require.NoError(t, err)
+ require.Len(t, tasks, 1)
+
+ // Description should be saved to field
+ assert.Equal(t, "Only description, no messages", tasks[0].Description)
+
+ // Description should also be converted to message for execution
+ assert.Len(t, tasks[0].Messages, 1)
+ content, ok := tasks[0].Messages[0].GetContentAsString()
+ assert.True(t, ok)
+ assert.Equal(t, "Only description, no messages", content)
+ })
+
t.Run("returns error for missing executor_type", func(t *testing.T) {
data := []interface{}{
map[string]interface{}{
diff --git a/agent/robot/executor/standard/ui_fields_test.go b/agent/robot/executor/standard/ui_fields_test.go
new file mode 100644
index 00000000..0ef74ed2
--- /dev/null
+++ b/agent/robot/executor/standard/ui_fields_test.go
@@ -0,0 +1,407 @@
+package standard
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+ agentcontext "github.com/yaoapp/yao/agent/context"
+ robottypes "github.com/yaoapp/yao/agent/robot/types"
+)
+
+// ============================================================================
+// getEffectiveLocale Tests
+// ============================================================================
+
+func TestGetEffectiveLocale(t *testing.T) {
+ t.Run("returns_input_locale_when_provided", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{
+ DefaultLocale: "en",
+ },
+ }
+ input := &robottypes.TriggerInput{
+ Locale: "zh",
+ }
+
+ locale := getEffectiveLocale(robot, input)
+ assert.Equal(t, "zh", locale)
+ })
+
+ t.Run("returns_robot_default_locale_when_input_locale_empty", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{
+ DefaultLocale: "zh",
+ },
+ }
+ input := &robottypes.TriggerInput{
+ Locale: "",
+ }
+
+ locale := getEffectiveLocale(robot, input)
+ assert.Equal(t, "zh", locale)
+ })
+
+ t.Run("returns_system_default_when_no_locale_configured", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{},
+ }
+ input := &robottypes.TriggerInput{}
+
+ locale := getEffectiveLocale(robot, input)
+ assert.Equal(t, "en", locale)
+ })
+
+ t.Run("returns_system_default_when_robot_config_nil", func(t *testing.T) {
+ robot := &robottypes.Robot{}
+ input := &robottypes.TriggerInput{}
+
+ locale := getEffectiveLocale(robot, input)
+ assert.Equal(t, "en", locale)
+ })
+
+ t.Run("returns_system_default_when_robot_nil", func(t *testing.T) {
+ input := &robottypes.TriggerInput{}
+
+ locale := getEffectiveLocale(nil, input)
+ assert.Equal(t, "en", locale)
+ })
+
+ t.Run("returns_system_default_when_input_nil", func(t *testing.T) {
+ robot := &robottypes.Robot{}
+
+ locale := getEffectiveLocale(robot, nil)
+ assert.Equal(t, "en", locale)
+ })
+}
+
+// ============================================================================
+// getLocalizedMessage Tests
+// ============================================================================
+
+func TestGetLocalizedMessage(t *testing.T) {
+ t.Run("returns_english_message_for_en_locale", func(t *testing.T) {
+ msg := getLocalizedMessage("en", "preparing")
+ assert.Equal(t, "Preparing...", msg)
+ })
+
+ t.Run("returns_chinese_message_for_zh_locale", func(t *testing.T) {
+ msg := getLocalizedMessage("zh", "preparing")
+ assert.Equal(t, "准备中...", msg)
+ })
+
+ t.Run("returns_english_fallback_for_unknown_locale", func(t *testing.T) {
+ msg := getLocalizedMessage("fr", "preparing")
+ assert.Equal(t, "Preparing...", msg)
+ })
+
+ t.Run("returns_key_for_unknown_message", func(t *testing.T) {
+ msg := getLocalizedMessage("en", "unknown_key")
+ assert.Equal(t, "unknown_key", msg)
+ })
+
+ t.Run("all_english_messages_exist", func(t *testing.T) {
+ keys := []string{
+ "preparing", "starting", "scheduled_execution",
+ "event_prefix", "event_triggered", "analyzing_context",
+ "planning_goals", "breaking_down_tasks",
+ "generating_delivery", "sending_delivery", "learning_from_exec",
+ "completed", "failed_prefix", "task_prefix",
+ // Phase names for failure messages
+ "phase_inspiration", "phase_goals", "phase_tasks",
+ "phase_run", "phase_delivery", "phase_learning",
+ }
+ for _, key := range keys {
+ msg := getLocalizedMessage("en", key)
+ assert.NotEqual(t, key, msg, "English message should exist for key: %s", key)
+ }
+ })
+
+ t.Run("all_chinese_messages_exist", func(t *testing.T) {
+ keys := []string{
+ "preparing", "starting", "scheduled_execution",
+ "event_prefix", "event_triggered", "analyzing_context",
+ "planning_goals", "breaking_down_tasks",
+ "generating_delivery", "sending_delivery", "learning_from_exec",
+ "completed", "failed_prefix", "task_prefix",
+ // Phase names for failure messages
+ "phase_inspiration", "phase_goals", "phase_tasks",
+ "phase_run", "phase_delivery", "phase_learning",
+ }
+ for _, key := range keys {
+ msg := getLocalizedMessage("zh", key)
+ assert.NotEqual(t, key, msg, "Chinese message should exist for key: %s", key)
+ }
+ })
+
+ t.Run("failure_message_is_concise", func(t *testing.T) {
+ // Test that failure messages use phase names, not full error text
+ enFailure := getLocalizedMessage("en", "failed_prefix") + getLocalizedMessage("en", "phase_inspiration")
+ assert.Equal(t, "Failed at inspiration", enFailure)
+
+ zhFailure := getLocalizedMessage("zh", "failed_prefix") + getLocalizedMessage("zh", "phase_inspiration")
+ assert.Equal(t, "失败于灵感阶段", zhFailure)
+ })
+}
+
+// ============================================================================
+// initUIFields Tests
+// ============================================================================
+
+func TestInitUIFields(t *testing.T) {
+ executor := New()
+
+ t.Run("human_trigger_extracts_name_from_message", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "en"},
+ }
+ input := &robottypes.TriggerInput{
+ Messages: []agentcontext.Message{
+ {Role: agentcontext.RoleUser, Content: "Please analyze the sales data"},
+ },
+ }
+
+ name, currentTaskName := executor.initUIFields(robottypes.TriggerHuman, input, robot)
+ assert.Equal(t, "Please analyze the sales data", name)
+ assert.Equal(t, "Starting...", currentTaskName)
+ })
+
+ t.Run("human_trigger_truncates_long_message", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "en"},
+ }
+ longMessage := "This is a very long message that exceeds one hundred characters and should be truncated with an ellipsis at the end"
+ input := &robottypes.TriggerInput{
+ Messages: []agentcontext.Message{
+ {Role: agentcontext.RoleUser, Content: longMessage},
+ },
+ }
+
+ name, _ := executor.initUIFields(robottypes.TriggerHuman, input, robot)
+ assert.LessOrEqual(t, len(name), 103) // 100 chars + "..."
+ assert.True(t, len(name) > 100 || name == longMessage[:100]+"...")
+ })
+
+ t.Run("clock_trigger_uses_scheduled_execution", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "en"},
+ }
+ input := &robottypes.TriggerInput{}
+
+ name, currentTaskName := executor.initUIFields(robottypes.TriggerClock, input, robot)
+ assert.Equal(t, "Scheduled execution", name)
+ assert.Equal(t, "Starting...", currentTaskName)
+ })
+
+ t.Run("clock_trigger_chinese_locale", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "zh"},
+ }
+ input := &robottypes.TriggerInput{}
+
+ name, currentTaskName := executor.initUIFields(robottypes.TriggerClock, input, robot)
+ assert.Equal(t, "定时执行", name)
+ assert.Equal(t, "启动中...", currentTaskName)
+ })
+
+ t.Run("event_trigger_with_event_type", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "en"},
+ }
+ input := &robottypes.TriggerInput{
+ EventType: "lead.created",
+ }
+
+ name, currentTaskName := executor.initUIFields(robottypes.TriggerEvent, input, robot)
+ assert.Equal(t, "Event: lead.created", name)
+ assert.Equal(t, "Starting...", currentTaskName)
+ })
+
+ t.Run("event_trigger_without_event_type", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "en"},
+ }
+ input := &robottypes.TriggerInput{}
+
+ name, _ := executor.initUIFields(robottypes.TriggerEvent, input, robot)
+ assert.Equal(t, "Event triggered", name)
+ })
+
+ t.Run("event_trigger_chinese_locale", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "zh"},
+ }
+ input := &robottypes.TriggerInput{
+ EventType: "order.placed",
+ }
+
+ name, _ := executor.initUIFields(robottypes.TriggerEvent, input, robot)
+ assert.Equal(t, "事件: order.placed", name)
+ })
+
+ t.Run("input_locale_overrides_robot_default", func(t *testing.T) {
+ robot := &robottypes.Robot{
+ Config: &robottypes.Config{DefaultLocale: "en"},
+ }
+ input := &robottypes.TriggerInput{
+ Locale: "zh",
+ }
+
+ name, currentTaskName := executor.initUIFields(robottypes.TriggerClock, input, robot)
+ assert.Equal(t, "定时执行", name)
+ assert.Equal(t, "启动中...", currentTaskName)
+ })
+}
+
+// ============================================================================
+// extractGoalName Tests
+// ============================================================================
+
+func TestExtractGoalName(t *testing.T) {
+ t.Run("extracts_first_line_from_content", func(t *testing.T) {
+ goals := &robottypes.Goals{
+ Content: "Generate monthly sales report\nAnalyze trends\nSend to stakeholders",
+ }
+
+ name := extractGoalName(goals)
+ assert.Equal(t, "Generate monthly sales report", name)
+ })
+
+ t.Run("returns_empty_for_nil_goals", func(t *testing.T) {
+ name := extractGoalName(nil)
+ assert.Equal(t, "", name)
+ })
+
+ t.Run("returns_empty_for_empty_content", func(t *testing.T) {
+ goals := &robottypes.Goals{
+ Content: "",
+ }
+
+ name := extractGoalName(goals)
+ assert.Equal(t, "", name)
+ })
+
+ t.Run("truncates_long_first_line", func(t *testing.T) {
+ longLine := "This is an extremely long goal description that exceeds one hundred and fifty characters and should be truncated with an ellipsis at the end to keep the display manageable"
+ goals := &robottypes.Goals{
+ Content: longLine,
+ }
+
+ name := extractGoalName(goals)
+ assert.LessOrEqual(t, len(name), 153) // 150 chars + "..."
+ })
+
+ t.Run("handles_single_line_content", func(t *testing.T) {
+ goals := &robottypes.Goals{
+ Content: "Single line goal",
+ }
+
+ name := extractGoalName(goals)
+ assert.Equal(t, "Single line goal", name)
+ })
+
+ t.Run("handles_carriage_return", func(t *testing.T) {
+ goals := &robottypes.Goals{
+ Content: "First goal\r\nSecond goal",
+ }
+
+ name := extractGoalName(goals)
+ assert.Equal(t, "First goal", name)
+ })
+}
+
+// ============================================================================
+// formatTaskProgressName Tests
+// ============================================================================
+
+func TestFormatTaskProgressName(t *testing.T) {
+ t.Run("prioritizes_description_field_over_messages", func(t *testing.T) {
+ task := &robottypes.Task{
+ ID: "task-001",
+ Description: "High-level task description for UI",
+ ExecutorType: robottypes.ExecutorAssistant,
+ ExecutorID: "analyst",
+ Messages: []agentcontext.Message{
+ {Role: agentcontext.RoleUser, Content: "Detailed message content for execution"},
+ },
+ }
+
+ name := formatTaskProgressName(task, 0, 3, "en")
+ // Should use Description field, NOT the message content
+ assert.Equal(t, "Task 1/3: High-level task description for UI", name)
+ })
+
+ t.Run("falls_back_to_message_when_no_description", func(t *testing.T) {
+ task := &robottypes.Task{
+ ID: "task-001",
+ Description: "", // Empty description
+ ExecutorType: robottypes.ExecutorAssistant,
+ ExecutorID: "analyst",
+ Messages: []agentcontext.Message{
+ {Role: agentcontext.RoleUser, Content: "Analyze sales data"},
+ },
+ }
+
+ name := formatTaskProgressName(task, 0, 3, "en")
+ assert.Equal(t, "Task 1/3: Analyze sales data", name)
+ })
+
+ t.Run("formats_with_chinese_locale", func(t *testing.T) {
+ task := &robottypes.Task{
+ ID: "task-001",
+ ExecutorType: robottypes.ExecutorAssistant,
+ ExecutorID: "analyst",
+ Messages: []agentcontext.Message{
+ {Role: agentcontext.RoleUser, Content: "分析销售数据"},
+ },
+ }
+
+ name := formatTaskProgressName(task, 1, 5, "zh")
+ assert.Equal(t, "任务 2/5: 分析销售数据", name)
+ })
+
+ t.Run("truncates_long_description_field", func(t *testing.T) {
+ longDesc := "This is a very long task description that should be truncated because it exceeds 80 characters which is the maximum length allowed"
+ task := &robottypes.Task{
+ ID: "task-001",
+ Description: longDesc,
+ ExecutorType: robottypes.ExecutorAssistant,
+ ExecutorID: "analyst",
+ Messages: []agentcontext.Message{},
+ }
+
+ name := formatTaskProgressName(task, 0, 1, "en")
+ // Should be "Task 1/1: " (11 chars) + truncated content (83 chars max with "...")
+ assert.Contains(t, name, "...")
+ assert.LessOrEqual(t, len(name), 100)
+ })
+
+ t.Run("truncates_long_message_content", func(t *testing.T) {
+ longContent := "This is a very long message content that should be truncated because it exceeds 80 characters which is the maximum length allowed"
+ task := &robottypes.Task{
+ ID: "task-001",
+ Description: "", // No description, will use message
+ ExecutorType: robottypes.ExecutorAssistant,
+ ExecutorID: "analyst",
+ Messages: []agentcontext.Message{
+ {Role: agentcontext.RoleUser, Content: longContent},
+ },
+ }
+
+ name := formatTaskProgressName(task, 0, 1, "en")
+ // Should be "Task 1/1: " (11 chars) + truncated content (83 chars max with "...")
+ assert.Contains(t, name, "...")
+ assert.LessOrEqual(t, len(name), 100)
+ })
+
+ t.Run("fallback_to_executor_info_when_no_messages", func(t *testing.T) {
+ task := &robottypes.Task{
+ ID: "task-001",
+ ExecutorType: robottypes.ExecutorMCP,
+ ExecutorID: "calculator",
+ Messages: []agentcontext.Message{},
+ }
+
+ name := formatTaskProgressName(task, 2, 4, "en")
+ assert.Equal(t, "Task 3/4: mcp:calculator", name)
+ })
+}
diff --git a/agent/robot/store/execution.go b/agent/robot/store/execution.go
index adfa16d8..82a42f90 100644
--- a/agent/robot/store/execution.go
+++ b/agent/robot/store/execution.go
@@ -25,6 +25,10 @@ type ExecutionRecord struct {
Current *CurrentState `json:"current,omitempty"`
Error string `json:"error,omitempty"`
+ // UI display fields (updated by executor at each phase)
+ Name string `json:"name,omitempty"` // Execution title
+ CurrentTaskName string `json:"current_task_name,omitempty"` // Current task description
+
// Trigger input
Input *types.TriggerInput `json:"input,omitempty"`
@@ -313,6 +317,69 @@ func (s *ExecutionStore) UpdateCurrent(ctx context.Context, executionID string,
return nil
}
+// UpdateTasks updates the tasks array with current status
+// This should be called after each task completes to persist status changes
+func (s *ExecutionStore) UpdateTasks(ctx context.Context, executionID string, tasks []types.Task, current *CurrentState) error {
+ mod := model.Select(s.modelID)
+ if mod == nil {
+ return fmt.Errorf("model %s not found", s.modelID)
+ }
+
+ updateData := map[string]interface{}{
+ "tasks": tasks,
+ "current": current,
+ }
+
+ _, err := mod.UpdateWhere(
+ model.QueryParam{
+ Wheres: []model.QueryWhere{
+ {Column: "execution_id", Value: executionID},
+ },
+ },
+ updateData,
+ )
+ if err != nil {
+ return fmt.Errorf("failed to update tasks: %w", err)
+ }
+
+ return nil
+}
+
+// UpdateUIFields updates the UI display fields (name and current_task_name)
+// These fields are updated by executor at each phase for frontend display
+func (s *ExecutionStore) UpdateUIFields(ctx context.Context, executionID string, name string, currentTaskName string) error {
+ mod := model.Select(s.modelID)
+ if mod == nil {
+ return fmt.Errorf("model %s not found", s.modelID)
+ }
+
+ updateData := map[string]interface{}{}
+ if name != "" {
+ updateData["name"] = name
+ }
+ if currentTaskName != "" {
+ updateData["current_task_name"] = currentTaskName
+ }
+
+ if len(updateData) == 0 {
+ return nil // Nothing to update
+ }
+
+ _, err := mod.UpdateWhere(
+ model.QueryParam{
+ Wheres: []model.QueryWhere{
+ {Column: "execution_id", Value: executionID},
+ },
+ },
+ updateData,
+ )
+ if err != nil {
+ return fmt.Errorf("failed to update UI fields: %w", err)
+ }
+
+ return nil
+}
+
// Delete removes an execution record by execution_id
func (s *ExecutionStore) Delete(ctx context.Context, executionID string) error {
mod := model.Select(s.modelID)
@@ -346,6 +413,12 @@ func (s *ExecutionStore) recordToMap(record *ExecutionRecord) map[string]interfa
if record.Error != "" {
data["error"] = record.Error
}
+ if record.Name != "" {
+ data["name"] = record.Name
+ }
+ if record.CurrentTaskName != "" {
+ data["current_task_name"] = record.CurrentTaskName
+ }
if record.Current != nil {
data["current"] = record.Current
}
@@ -416,6 +489,12 @@ func (s *ExecutionStore) mapToRecord(row map[string]interface{}) (*ExecutionReco
if v, ok := row["error"].(string); ok {
record.Error = v
}
+ if v, ok := row["name"].(string); ok {
+ record.Name = v
+ }
+ if v, ok := row["current_task_name"].(string); ok {
+ record.CurrentTaskName = v
+ }
// JSON fields - need to unmarshal
if v := row["current"]; v != nil {
@@ -624,20 +703,22 @@ func (s *ExecutionStore) parseTime(v interface{}) *time.Time {
// FromExecution creates an ExecutionRecord from a runtime Execution
func FromExecution(exec *types.Execution) *ExecutionRecord {
record := &ExecutionRecord{
- ExecutionID: exec.ID,
- MemberID: exec.MemberID,
- TeamID: exec.TeamID,
- TriggerType: exec.TriggerType,
- Status: exec.Status,
- Phase: exec.Phase,
- Error: exec.Error,
- Input: exec.Input,
- Inspiration: exec.Inspiration,
- Goals: exec.Goals,
- Tasks: exec.Tasks,
- Results: exec.Results,
- Delivery: exec.Delivery,
- Learning: exec.Learning,
+ ExecutionID: exec.ID,
+ MemberID: exec.MemberID,
+ TeamID: exec.TeamID,
+ TriggerType: exec.TriggerType,
+ Status: exec.Status,
+ Phase: exec.Phase,
+ Error: exec.Error,
+ Name: exec.Name,
+ CurrentTaskName: exec.CurrentTaskName,
+ Input: exec.Input,
+ Inspiration: exec.Inspiration,
+ Goals: exec.Goals,
+ Tasks: exec.Tasks,
+ Results: exec.Results,
+ Delivery: exec.Delivery,
+ Learning: exec.Learning,
}
// Convert timestamps
@@ -662,20 +743,22 @@ func FromExecution(exec *types.Execution) *ExecutionRecord {
// ToExecution converts an ExecutionRecord to a runtime Execution
func (r *ExecutionRecord) ToExecution() *types.Execution {
exec := &types.Execution{
- ID: r.ExecutionID,
- MemberID: r.MemberID,
- TeamID: r.TeamID,
- TriggerType: r.TriggerType,
- Status: r.Status,
- Phase: r.Phase,
- Error: r.Error,
- Input: r.Input,
- Inspiration: r.Inspiration,
- Goals: r.Goals,
- Tasks: r.Tasks,
- Results: r.Results,
- Delivery: r.Delivery,
- Learning: r.Learning,
+ ID: r.ExecutionID,
+ MemberID: r.MemberID,
+ TeamID: r.TeamID,
+ TriggerType: r.TriggerType,
+ Status: r.Status,
+ Phase: r.Phase,
+ Error: r.Error,
+ Name: r.Name,
+ CurrentTaskName: r.CurrentTaskName,
+ Input: r.Input,
+ Inspiration: r.Inspiration,
+ Goals: r.Goals,
+ Tasks: r.Tasks,
+ Results: r.Results,
+ Delivery: r.Delivery,
+ Learning: r.Learning,
}
// Convert timestamps
diff --git a/agent/robot/store/execution_test.go b/agent/robot/store/execution_test.go
index 0ded595e..f6bd9835 100644
--- a/agent/robot/store/execution_test.go
+++ b/agent/robot/store/execution_test.go
@@ -505,6 +505,260 @@ func TestExecutionStoreUpdateCurrent(t *testing.T) {
})
}
+// TestExecutionStoreUpdateUIFields tests updating UI display fields
+func TestExecutionStoreUpdateUIFields(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping integration test")
+ }
+
+ testutils.Prepare(t)
+ defer testutils.Clean(t)
+
+ cleanupTestExecutions(t)
+ defer cleanupTestExecutions(t)
+
+ s := store.NewExecutionStore()
+ ctx := context.Background()
+
+ // Create a base record
+ startTime := time.Now()
+ record := &store.ExecutionRecord{
+ ExecutionID: "exec_test_uifields_001",
+ MemberID: "member_uifields_001",
+ TeamID: "team_uifields_001",
+ TriggerType: types.TriggerHuman,
+ Status: types.ExecRunning,
+ Phase: types.PhaseInspiration,
+ StartTime: &startTime,
+ }
+ err := s.Save(ctx, record)
+ require.NoError(t, err)
+
+ t.Run("updates_name_only", func(t *testing.T) {
+ err := s.UpdateUIFields(ctx, "exec_test_uifields_001", "Analyze sales data", "")
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_uifields_001")
+ require.NoError(t, err)
+ assert.Equal(t, "Analyze sales data", saved.Name)
+ assert.Equal(t, "", saved.CurrentTaskName)
+ })
+
+ t.Run("updates_current_task_name_only", func(t *testing.T) {
+ err := s.UpdateUIFields(ctx, "exec_test_uifields_001", "", "Analyzing context...")
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_uifields_001")
+ require.NoError(t, err)
+ assert.Equal(t, "Analyze sales data", saved.Name) // Previous value retained
+ assert.Equal(t, "Analyzing context...", saved.CurrentTaskName)
+ })
+
+ t.Run("updates_both_fields", func(t *testing.T) {
+ err := s.UpdateUIFields(ctx, "exec_test_uifields_001", "Generate monthly report", "Task 1/3: Collect data")
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_uifields_001")
+ require.NoError(t, err)
+ assert.Equal(t, "Generate monthly report", saved.Name)
+ assert.Equal(t, "Task 1/3: Collect data", saved.CurrentTaskName)
+ })
+
+ t.Run("does_nothing_when_both_empty", func(t *testing.T) {
+ // Get current values
+ before, err := s.Get(ctx, "exec_test_uifields_001")
+ require.NoError(t, err)
+
+ // Update with empty strings
+ err = s.UpdateUIFields(ctx, "exec_test_uifields_001", "", "")
+ require.NoError(t, err)
+
+ // Values should remain unchanged
+ after, err := s.Get(ctx, "exec_test_uifields_001")
+ require.NoError(t, err)
+ assert.Equal(t, before.Name, after.Name)
+ assert.Equal(t, before.CurrentTaskName, after.CurrentTaskName)
+ })
+
+ t.Run("handles_chinese_content", func(t *testing.T) {
+ err := s.UpdateUIFields(ctx, "exec_test_uifields_001", "生成月度报告", "任务 2/3: 分析数据")
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_uifields_001")
+ require.NoError(t, err)
+ assert.Equal(t, "生成月度报告", saved.Name)
+ assert.Equal(t, "任务 2/3: 分析数据", saved.CurrentTaskName)
+ })
+
+ t.Run("handles_long_content", func(t *testing.T) {
+ longName := "This is a very long execution name that might come from a detailed user instruction about what they want the robot to accomplish in this particular run cycle"
+ longTask := "Task 1/5: Processing a complex multi-step operation with various sub-tasks that need to be completed..."
+
+ err := s.UpdateUIFields(ctx, "exec_test_uifields_001", longName, longTask)
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_uifields_001")
+ require.NoError(t, err)
+ assert.Equal(t, longName, saved.Name)
+ assert.Equal(t, longTask, saved.CurrentTaskName)
+ })
+}
+
+// TestExecutionStoreUpdateTasks tests updating tasks array with status
+func TestExecutionStoreUpdateTasks(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping integration test")
+ }
+
+ testutils.Prepare(t)
+ defer testutils.Clean(t)
+
+ cleanupTestExecutions(t)
+ defer cleanupTestExecutions(t)
+
+ s := store.NewExecutionStore()
+ ctx := context.Background()
+
+ // Create a base record with initial tasks
+ startTime := time.Now()
+ record := &store.ExecutionRecord{
+ ExecutionID: "exec_test_tasks_001",
+ MemberID: "member_tasks_001",
+ TeamID: "team_tasks_001",
+ TriggerType: types.TriggerClock,
+ Status: types.ExecRunning,
+ Phase: types.PhaseRun,
+ StartTime: &startTime,
+ Tasks: []types.Task{
+ {ID: "task_001", ExecutorType: types.ExecutorAssistant, Status: types.TaskPending, Order: 0},
+ {ID: "task_002", ExecutorType: types.ExecutorProcess, Status: types.TaskPending, Order: 1},
+ {ID: "task_003", ExecutorType: types.ExecutorAssistant, Status: types.TaskPending, Order: 2},
+ },
+ }
+ err := s.Save(ctx, record)
+ require.NoError(t, err)
+
+ t.Run("updates_task_status_to_running", func(t *testing.T) {
+ // Update first task to running
+ tasks := []types.Task{
+ {ID: "task_001", ExecutorType: types.ExecutorAssistant, Status: types.TaskRunning, Order: 0},
+ {ID: "task_002", ExecutorType: types.ExecutorProcess, Status: types.TaskPending, Order: 1},
+ {ID: "task_003", ExecutorType: types.ExecutorAssistant, Status: types.TaskPending, Order: 2},
+ }
+ current := &store.CurrentState{TaskIndex: 0, Progress: "1/3 tasks"}
+
+ err := s.UpdateTasks(ctx, "exec_test_tasks_001", tasks, current)
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_tasks_001")
+ require.NoError(t, err)
+ require.Len(t, saved.Tasks, 3)
+
+ assert.Equal(t, types.TaskRunning, saved.Tasks[0].Status)
+ assert.Equal(t, types.TaskPending, saved.Tasks[1].Status)
+ assert.Equal(t, types.TaskPending, saved.Tasks[2].Status)
+
+ assert.NotNil(t, saved.Current)
+ assert.Equal(t, 0, saved.Current.TaskIndex)
+ })
+
+ t.Run("updates_task_status_to_completed", func(t *testing.T) {
+ // First task completed, second running
+ tasks := []types.Task{
+ {ID: "task_001", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted, Order: 0},
+ {ID: "task_002", ExecutorType: types.ExecutorProcess, Status: types.TaskRunning, Order: 1},
+ {ID: "task_003", ExecutorType: types.ExecutorAssistant, Status: types.TaskPending, Order: 2},
+ }
+ current := &store.CurrentState{TaskIndex: 1, Progress: "2/3 tasks"}
+
+ err := s.UpdateTasks(ctx, "exec_test_tasks_001", tasks, current)
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_tasks_001")
+ require.NoError(t, err)
+
+ assert.Equal(t, types.TaskCompleted, saved.Tasks[0].Status)
+ assert.Equal(t, types.TaskRunning, saved.Tasks[1].Status)
+ assert.Equal(t, types.TaskPending, saved.Tasks[2].Status)
+ assert.Equal(t, 1, saved.Current.TaskIndex)
+ })
+
+ t.Run("updates_task_status_to_failed_with_skipped", func(t *testing.T) {
+ // Second task failed, third skipped
+ tasks := []types.Task{
+ {ID: "task_001", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted, Order: 0},
+ {ID: "task_002", ExecutorType: types.ExecutorProcess, Status: types.TaskFailed, Order: 1},
+ {ID: "task_003", ExecutorType: types.ExecutorAssistant, Status: types.TaskSkipped, Order: 2},
+ }
+ current := &store.CurrentState{TaskIndex: 1, Progress: "Failed at 2/3"}
+
+ err := s.UpdateTasks(ctx, "exec_test_tasks_001", tasks, current)
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_tasks_001")
+ require.NoError(t, err)
+
+ assert.Equal(t, types.TaskCompleted, saved.Tasks[0].Status)
+ assert.Equal(t, types.TaskFailed, saved.Tasks[1].Status)
+ assert.Equal(t, types.TaskSkipped, saved.Tasks[2].Status)
+ })
+
+ t.Run("updates_with_nil_current", func(t *testing.T) {
+ // All tasks completed, no current
+ tasks := []types.Task{
+ {ID: "task_001", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted, Order: 0},
+ {ID: "task_002", ExecutorType: types.ExecutorProcess, Status: types.TaskCompleted, Order: 1},
+ {ID: "task_003", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted, Order: 2},
+ }
+
+ err := s.UpdateTasks(ctx, "exec_test_tasks_001", tasks, nil)
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_tasks_001")
+ require.NoError(t, err)
+
+ assert.Equal(t, types.TaskCompleted, saved.Tasks[0].Status)
+ assert.Equal(t, types.TaskCompleted, saved.Tasks[1].Status)
+ assert.Equal(t, types.TaskCompleted, saved.Tasks[2].Status)
+ })
+
+ t.Run("preserves_task_description", func(t *testing.T) {
+ // Create a new record with descriptions
+ record2 := &store.ExecutionRecord{
+ ExecutionID: "exec_test_tasks_002",
+ MemberID: "member_tasks_002",
+ TeamID: "team_tasks_002",
+ TriggerType: types.TriggerHuman,
+ Status: types.ExecRunning,
+ Phase: types.PhaseRun,
+ StartTime: &startTime,
+ Tasks: []types.Task{
+ {ID: "task_d01", Description: "Analyze data", ExecutorType: types.ExecutorAssistant, Status: types.TaskPending, Order: 0},
+ {ID: "task_d02", Description: "Generate report", ExecutorType: types.ExecutorAssistant, Status: types.TaskPending, Order: 1},
+ },
+ }
+ err := s.Save(ctx, record2)
+ require.NoError(t, err)
+
+ // Update status preserving description
+ tasks := []types.Task{
+ {ID: "task_d01", Description: "Analyze data", ExecutorType: types.ExecutorAssistant, Status: types.TaskCompleted, Order: 0},
+ {ID: "task_d02", Description: "Generate report", ExecutorType: types.ExecutorAssistant, Status: types.TaskRunning, Order: 1},
+ }
+
+ err = s.UpdateTasks(ctx, "exec_test_tasks_002", tasks, &store.CurrentState{TaskIndex: 1})
+ require.NoError(t, err)
+
+ saved, err := s.Get(ctx, "exec_test_tasks_002")
+ require.NoError(t, err)
+
+ assert.Equal(t, "Analyze data", saved.Tasks[0].Description)
+ assert.Equal(t, "Generate report", saved.Tasks[1].Description)
+ assert.Equal(t, types.TaskCompleted, saved.Tasks[0].Status)
+ assert.Equal(t, types.TaskRunning, saved.Tasks[1].Status)
+ })
+}
+
// TestExecutionStoreDelete tests deleting execution records
func TestExecutionStoreDelete(t *testing.T) {
if testing.Short() {
@@ -569,17 +823,19 @@ func TestExecutionRecordConversion(t *testing.T) {
now := time.Now()
endTime := now.Add(time.Hour)
exec := &types.Execution{
- ID: "exec_convert_001",
- MemberID: "member_convert_001",
- TeamID: "team_convert_001",
- TriggerType: types.TriggerHuman,
- Status: types.ExecCompleted,
- Phase: types.PhaseDelivery,
- StartTime: now,
- EndTime: &endTime,
- Error: "",
- Inspiration: &types.InspirationReport{Content: "Test inspiration"},
- Goals: &types.Goals{Content: "Test goals"},
+ ID: "exec_convert_001",
+ MemberID: "member_convert_001",
+ TeamID: "team_convert_001",
+ TriggerType: types.TriggerHuman,
+ Status: types.ExecCompleted,
+ Phase: types.PhaseDelivery,
+ StartTime: now,
+ EndTime: &endTime,
+ Error: "",
+ Name: "Analyze sales data",
+ CurrentTaskName: "Task 1/3: Processing",
+ Inspiration: &types.InspirationReport{Content: "Test inspiration"},
+ Goals: &types.Goals{Content: "Test goals"},
Tasks: []types.Task{
{ID: "task_001", ExecutorType: types.ExecutorAssistant},
},
@@ -608,22 +864,27 @@ func TestExecutionRecordConversion(t *testing.T) {
assert.Len(t, record.Results, 1)
assert.NotNil(t, record.Current)
assert.Equal(t, 1, record.Current.TaskIndex)
+ // Verify UI fields conversion
+ assert.Equal(t, "Analyze sales data", record.Name)
+ assert.Equal(t, "Task 1/3: Processing", record.CurrentTaskName)
})
t.Run("converts_to_execution", func(t *testing.T) {
now := time.Now()
endTime := now.Add(time.Hour)
record := &store.ExecutionRecord{
- ExecutionID: "exec_convert_002",
- MemberID: "member_convert_002",
- TeamID: "team_convert_002",
- TriggerType: types.TriggerClock,
- Status: types.ExecRunning,
- Phase: types.PhaseRun,
- StartTime: &now,
- EndTime: &endTime,
- Inspiration: &types.InspirationReport{Content: "Test inspiration"},
- Goals: &types.Goals{Content: "Test goals"},
+ ExecutionID: "exec_convert_002",
+ MemberID: "member_convert_002",
+ TeamID: "team_convert_002",
+ TriggerType: types.TriggerClock,
+ Status: types.ExecRunning,
+ Phase: types.PhaseRun,
+ StartTime: &now,
+ EndTime: &endTime,
+ Name: "定时执行",
+ CurrentTaskName: "任务 1/2: 数据分析",
+ Inspiration: &types.InspirationReport{Content: "Test inspiration"},
+ Goals: &types.Goals{Content: "Test goals"},
Tasks: []types.Task{
{ID: "task_002", ExecutorType: types.ExecutorProcess},
},
@@ -650,6 +911,9 @@ func TestExecutionRecordConversion(t *testing.T) {
assert.Len(t, exec.Results, 1)
assert.NotNil(t, exec.Current)
assert.Equal(t, 0, exec.Current.TaskIndex)
+ // Verify UI fields conversion
+ assert.Equal(t, "定时执行", exec.Name)
+ assert.Equal(t, "任务 1/2: 数据分析", exec.CurrentTaskName)
})
}
diff --git a/agent/robot/types/config.go b/agent/robot/types/config.go
index 78bbb656..942eb87a 100644
--- a/agent/robot/types/config.go
+++ b/agent/robot/types/config.go
@@ -7,17 +7,18 @@ import (
// Config - robot_config in __yao.member
type Config struct {
- Triggers *Triggers `json:"triggers,omitempty"`
- Clock *Clock `json:"clock,omitempty"`
- Identity *Identity `json:"identity"`
- Quota *Quota `json:"quota,omitempty"`
- KB *KB `json:"kb,omitempty"` // shared knowledge base (same as assistant)
- DB *DB `json:"db,omitempty"` // shared database (same as assistant)
- Learn *Learn `json:"learn,omitempty"` // learning config for private KB
- Resources *Resources `json:"resources,omitempty"`
- Delivery *DeliveryPreferences `json:"delivery,omitempty"` // delivery preferences (see robot.go)
- Events []Event `json:"events,omitempty"`
- Executor *ExecutorConfig `json:"executor,omitempty"` // executor mode settings
+ Triggers *Triggers `json:"triggers,omitempty"`
+ Clock *Clock `json:"clock,omitempty"`
+ Identity *Identity `json:"identity"`
+ Quota *Quota `json:"quota,omitempty"`
+ KB *KB `json:"kb,omitempty"` // shared knowledge base (same as assistant)
+ DB *DB `json:"db,omitempty"` // shared database (same as assistant)
+ Learn *Learn `json:"learn,omitempty"` // learning config for private KB
+ Resources *Resources `json:"resources,omitempty"`
+ Delivery *DeliveryPreferences `json:"delivery,omitempty"` // delivery preferences (see robot.go)
+ Events []Event `json:"events,omitempty"`
+ Executor *ExecutorConfig `json:"executor,omitempty"` // executor mode settings
+ DefaultLocale string `json:"default_locale,omitempty"` // default language for clock/event triggers ("en", "zh")
}
// ExecutorConfig - executor settings
@@ -59,6 +60,14 @@ func (c *Config) Validate() error {
return nil
}
+// GetDefaultLocale returns the default locale (default: "en")
+func (c *Config) GetDefaultLocale() string {
+ if c == nil || c.DefaultLocale == "" {
+ return "en"
+ }
+ return c.DefaultLocale
+}
+
// Triggers - trigger enable/disable
type Triggers struct {
Clock *TriggerSwitch `json:"clock,omitempty"`
diff --git a/agent/robot/types/robot.go b/agent/robot/types/robot.go
index 1dfea09a..339472f2 100644
--- a/agent/robot/types/robot.go
+++ b/agent/robot/types/robot.go
@@ -129,6 +129,11 @@ type Execution struct {
Phase Phase `json:"phase"`
Error string `json:"error,omitempty"`
+ // UI display fields (updated by executor at each phase)
+ // These provide human-readable status for frontend display
+ Name string `json:"name,omitempty"` // Execution title (updated when goals complete)
+ CurrentTaskName string `json:"current_task_name,omitempty"` // Current task description (updated during run phase)
+
// Trigger input (stored for traceability)
Input *TriggerInput `json:"input,omitempty"` // original trigger input
@@ -163,6 +168,7 @@ type TriggerInput struct {
Action InterventionAction `json:"action,omitempty"` // task.add, goal.adjust, etc.
Messages []agentcontext.Message `json:"messages,omitempty"` // user's input (text, images, files)
UserID string `json:"user_id,omitempty"` // who triggered
+ Locale string `json:"locale,omitempty"` // language for UI display (e.g., "en-US", "zh-CN")
// For event trigger
Source EventSource `json:"source,omitempty"` // webhook | database
@@ -210,10 +216,11 @@ type DeliveryTarget struct {
// Task - planned task (structured, for execution)
type Task struct {
- ID string `json:"id"`
- Messages []agentcontext.Message `json:"messages"` // original input (text, images, files)
- GoalRef string `json:"goal_ref,omitempty"` // reference to goal (e.g., "Goal 1")
- Source TaskSource `json:"source"` // auto | human | event
+ ID string `json:"id"`
+ Description string `json:"description,omitempty"` // human-readable task description (for UI display)
+ Messages []agentcontext.Message `json:"messages"` // original input (text, images, files)
+ GoalRef string `json:"goal_ref,omitempty"` // reference to goal (e.g., "Goal 1")
+ Source TaskSource `json:"source"` // auto | human | event
// Executor
ExecutorType ExecutorType `json:"executor_type"`
diff --git a/data/bindata.go b/data/bindata.go
index c5b0e30e..200d1919 100644
--- a/data/bindata.go
+++ b/data/bindata.go
@@ -346,7 +346,7 @@ func cuiSetupIndexHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "cui/setup/index.html", size: 10, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -366,7 +366,7 @@ func cuiV09IndexHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "cui/v0.9/index.html", size: 13, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -386,7 +386,7 @@ func cuiV10IndexHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "cui/v1.0/index.html", size: 49, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -406,7 +406,7 @@ func cuiV10Layouts__indexAsyncJs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "cui/v1.0/layouts__index.async.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -426,7 +426,7 @@ func cuiV10UmiJs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "cui/v1.0/umi.js", size: 71, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -446,7 +446,7 @@ func initEnv() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.env", size: 219, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -466,7 +466,7 @@ func initVscodeSettingsJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/settings.json", size: 4666, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -486,7 +486,7 @@ func initVscodeTypesRuntimeConsoleDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/console.d.ts", size: 221, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -506,7 +506,7 @@ func initVscodeTypesRuntimeExceptionDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/exception.d.ts", size: 738, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -526,7 +526,7 @@ func initVscodeTypesRuntimeFsDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/fs.d.ts", size: 8554, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -546,7 +546,7 @@ func initVscodeTypesRuntimeGlobalDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/global.d.ts", size: 1759, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -566,7 +566,7 @@ func initVscodeTypesRuntimeHttpDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/http.d.ts", size: 6179, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -586,7 +586,7 @@ func initVscodeTypesRuntimeIoDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/io.d.ts", size: 587, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -606,7 +606,7 @@ func initVscodeTypesRuntimeLogDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/log.d.ts", size: 1692, mode: os.FileMode(493), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -626,7 +626,7 @@ func initVscodeTypesRuntimeNeoDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/neo.d.ts", size: 3750, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -646,7 +646,7 @@ func initVscodeTypesRuntimeProcessFsDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/process/fs.d.ts", size: 11133, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -666,7 +666,7 @@ func initVscodeTypesRuntimeProcessHttpDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/process/http.d.ts", size: 5653, mode: os.FileMode(493), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -686,7 +686,7 @@ func initVscodeTypesRuntimeProcessModelDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/process/model.d.ts", size: 6656, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -706,7 +706,7 @@ func initVscodeTypesRuntimeProcessDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/process.d.ts", size: 23165, mode: os.FileMode(493), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -726,7 +726,7 @@ func initVscodeTypesRuntimeQueryDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/query.d.ts", size: 6124, mode: os.FileMode(493), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -746,7 +746,7 @@ func initVscodeTypesRuntimeStoreDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/store.d.ts", size: 2251, mode: os.FileMode(493), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -766,7 +766,7 @@ func initVscodeTypesRuntimeSuiDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/sui.d.ts", size: 1713, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -786,7 +786,7 @@ func initVscodeTypesRuntimeTimeDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime/time.d.ts", size: 711, mode: os.FileMode(493), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -806,7 +806,7 @@ func initVscodeTypesRuntimeDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/runtime.d.ts", size: 424, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -826,7 +826,7 @@ func initVscodeTypesSuiDTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/.vscode/types/sui.d.ts", size: 8931, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -846,7 +846,7 @@ func initAppYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/app.yao", size: 3115, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -866,7 +866,7 @@ func initDataReadmeMd() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/README.md", size: 41, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -886,7 +886,7 @@ func initDataTemplatesDefault__assetsReadmeMd() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/__assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -906,7 +906,7 @@ func initDataTemplatesDefault__assetsImagesIconsAppPng() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/__assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -926,7 +926,7 @@ func initDataTemplatesDefault__assetsImagesLogosLogo_colorSvg() (*asset, error)
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -946,7 +946,7 @@ func initDataTemplatesDefault__assetsImagesLogosWordmarkSvg() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/__assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -966,7 +966,7 @@ func initDataTemplatesDefault__dataJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/__data.json", size: 30, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -986,7 +986,7 @@ func initDataTemplatesDefault__documentHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/__document.html", size: 492, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1006,7 +1006,7 @@ func initDataTemplatesDefaultIndexIndexCss() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/index/index.css", size: 2896, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1026,7 +1026,7 @@ func initDataTemplatesDefaultIndexIndexHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/index/index.html", size: 2361, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1046,7 +1046,7 @@ func initDataTemplatesDefaultIndexIndexJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/data/templates/default/index/index.json", size: 31, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1066,7 +1066,7 @@ func initDbReadmeMd() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/db/README.md", size: 84, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1086,7 +1086,7 @@ func initFlowsMenuFlowYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/flows/menu.flow.yao", size: 813, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1106,7 +1106,7 @@ func initFormsAccountFormYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/forms/account.form.yao", size: 1194, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1126,7 +1126,7 @@ func initIconsAppIcns() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/icons/app.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1146,7 +1146,7 @@ func initIconsAppIco() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/icons/app.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1166,7 +1166,7 @@ func initIconsAppPng() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1186,7 +1186,7 @@ func initLoginsAdminLoginYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/logins/admin.login.yao", size: 302, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1206,7 +1206,7 @@ func initLogsReadmeMd() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/logs/README.md", size: 28, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1226,7 +1226,7 @@ func initModelsAdminUserModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/models/admin/user.mod.yao", size: 6416, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1246,7 +1246,7 @@ func initModelsTestsPetModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/models/tests/pet.mod.yao", size: 525, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1266,7 +1266,7 @@ func initNeoNeoYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/neo/neo.yml", size: 724, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1286,7 +1286,7 @@ func initPublicReadmeMd() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/README.md", size: 108, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1306,7 +1306,7 @@ func initPublicAssetsReadmeMd() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/assets/README.md", size: 33, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1326,7 +1326,7 @@ func initPublicAssetsImagesIconsAppPng() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/assets/images/icons/app.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1346,7 +1346,7 @@ func initPublicAssetsImagesLogosLogo_colorSvg() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/assets/images/logos/logo_color.svg", size: 2909, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1366,7 +1366,7 @@ func initPublicAssetsImagesLogosWordmarkSvg() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/assets/images/logos/wordmark.svg", size: 3615, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1386,7 +1386,7 @@ func initPublicAssetsLibsuiMinJs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/assets/libsui.min.js", size: 12569, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1406,7 +1406,7 @@ func initPublicAssetsLibsuiMinJsMap() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/assets/libsui.min.js.map", size: 38553, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1426,7 +1426,7 @@ func initPublicIndexCfg() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/index.cfg", size: 85, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1446,7 +1446,7 @@ func initPublicIndexSui() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/public/index.sui", size: 5682, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1466,7 +1466,7 @@ func initScriptsAccountTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/scripts/account.ts", size: 2521, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1486,7 +1486,7 @@ func initScriptsAiNeoTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/scripts/ai/neo.ts", size: 375, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1506,7 +1506,7 @@ func initScriptsTestsTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/scripts/tests.ts", size: 1044, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1526,7 +1526,7 @@ func initScriptsUtilsTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/scripts/utils.ts", size: 1230, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1546,7 +1546,7 @@ func initSuisWebSuiYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/suis/web.sui.yao", size: 675, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1566,7 +1566,7 @@ func initTablesAccountTabYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/tables/account.tab.yao", size: 5597, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1586,7 +1586,7 @@ func initTsconfigJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "init/tsconfig.json", size: 178, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1606,7 +1606,7 @@ func libsuiIndexTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "libsui/index.ts", size: 13047, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "libsui/index.ts", size: 13047, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1626,7 +1626,7 @@ func libsuiOpenapiTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "libsui/openapi.ts", size: 22959, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1646,7 +1646,7 @@ func libsuiUtilsTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "libsui/utils.ts", size: 5959, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1666,7 +1666,7 @@ func libsuiYaoTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "libsui/yao.ts", size: 4338, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1686,7 +1686,7 @@ func publicIndexHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "public/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1706,7 +1706,7 @@ func uiIndexHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "ui/index.html", size: 11, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1726,7 +1726,7 @@ func yaoAssistantsEntityPackageYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 212, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/entity/package.yao", size: 212, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1746,7 +1746,7 @@ func yaoAssistantsEntityPromptsYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/entity/prompts.yml", size: 930, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1766,7 +1766,7 @@ func yaoAssistantsKeywordPackageYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 200, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/keyword/package.yao", size: 200, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1786,7 +1786,7 @@ func yaoAssistantsKeywordPromptsYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/keyword/prompts.yml", size: 990, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1806,7 +1806,7 @@ func yaoAssistantsKeywordSrcIndexTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/keyword/src/index.ts", size: 4104, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1826,7 +1826,7 @@ func yaoAssistantsNeedsearchPackageYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 207, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/needsearch/package.yao", size: 207, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1846,7 +1846,7 @@ func yaoAssistantsNeedsearchPromptsYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/needsearch/prompts.yml", size: 3092, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1866,7 +1866,7 @@ func yaoAssistantsNeedsearchSrcIndexTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/needsearch/src/index.ts", size: 2767, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1886,7 +1886,7 @@ func yaoAssistantsPromptPackageYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 186, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/prompt/package.yao", size: 186, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1906,7 +1906,7 @@ func yaoAssistantsPromptPromptsYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/prompt/prompts.yml", size: 621, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1926,7 +1926,7 @@ func yaoAssistantsQuerydslPackageYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 196, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/querydsl/package.yao", size: 196, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1946,7 +1946,7 @@ func yaoAssistantsQuerydslPromptsAggregationYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/aggregation.yml", size: 6982, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1966,7 +1966,7 @@ func yaoAssistantsQuerydslPromptsComplexYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/complex.yml", size: 7352, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -1986,7 +1986,7 @@ func yaoAssistantsQuerydslPromptsFilterYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/filter.yml", size: 7087, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2006,7 +2006,7 @@ func yaoAssistantsQuerydslPromptsJoinYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/querydsl/prompts/join.yml", size: 8167, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2026,7 +2026,7 @@ func yaoAssistantsQuerydslPromptsYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/querydsl/prompts.yml", size: 5836, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2046,7 +2046,7 @@ func yaoAssistantsQuerydslSrcIndexTs() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/querydsl/src/index.ts", size: 1873, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2066,7 +2066,7 @@ func yaoAssistantsRobot_promptPackageYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 204, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/robot_prompt/package.yao", size: 204, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2086,7 +2086,7 @@ func yaoAssistantsRobot_promptPromptsYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/robot_prompt/prompts.yml", size: 2606, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2106,7 +2106,7 @@ func yaoAssistantsTitlePackageYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 178, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/title/package.yao", size: 178, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2126,7 +2126,7 @@ func yaoAssistantsTitlePromptsYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/assistants/title/prompts.yml", size: 958, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2146,7 +2146,7 @@ func yaoDataIcons404Png() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/icons/404.png", size: 9342, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2166,7 +2166,7 @@ func yaoDataIconsIconIcns() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/icons/icon.icns", size: 67465, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2186,7 +2186,7 @@ func yaoDataIconsIconIco() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/icons/icon.ico", size: 54993, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2206,7 +2206,7 @@ func yaoDataIconsIconPng() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/icons/icon.png", size: 34558, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2226,7 +2226,7 @@ func yaoDataIndexHtml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/index.html", size: 282, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2246,7 +2246,7 @@ func yaoDataKbProvidersChunkingSemanticEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/en.json", size: 5543, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2266,7 +2266,7 @@ func yaoDataKbProvidersChunkingSemanticZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/chunking/semantic/zh-cn.json", size: 5446, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2286,7 +2286,7 @@ func yaoDataKbProvidersChunkingStructuredEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/en.json", size: 2423, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2306,7 +2306,7 @@ func yaoDataKbProvidersChunkingStructuredZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/chunking/structured/zh-cn.json", size: 2321, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2326,7 +2326,7 @@ func yaoDataKbProvidersConverterMcpEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/en.json", size: 4235, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2346,7 +2346,7 @@ func yaoDataKbProvidersConverterMcpZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/mcp/zh-cn.json", size: 4060, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2366,7 +2366,7 @@ func yaoDataKbProvidersConverterOcrEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/en.json", size: 6631, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2386,7 +2386,7 @@ func yaoDataKbProvidersConverterOcrZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/ocr/zh-cn.json", size: 6501, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2406,7 +2406,7 @@ func yaoDataKbProvidersConverterOfficeEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/en.json", size: 5476, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2426,7 +2426,7 @@ func yaoDataKbProvidersConverterOfficeZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/office/zh-cn.json", size: 5356, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2446,7 +2446,7 @@ func yaoDataKbProvidersConverterUtf8EnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/en.json", size: 292, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2466,7 +2466,7 @@ func yaoDataKbProvidersConverterUtf8ZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/utf8/zh-cn.json", size: 281, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2486,7 +2486,7 @@ func yaoDataKbProvidersConverterVideoEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/en.json", size: 6411, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2506,7 +2506,7 @@ func yaoDataKbProvidersConverterVideoZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/video/zh-cn.json", size: 6297, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2526,7 +2526,7 @@ func yaoDataKbProvidersConverterVisionEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/en.json", size: 4085, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2546,7 +2546,7 @@ func yaoDataKbProvidersConverterVisionZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/vision/zh-cn.json", size: 3949, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2566,7 +2566,7 @@ func yaoDataKbProvidersConverterWhisperEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/en.json", size: 4449, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2586,7 +2586,7 @@ func yaoDataKbProvidersConverterWhisperZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/converter/whisper/zh-cn.json", size: 4312, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2606,7 +2606,7 @@ func yaoDataKbProvidersEmbeddingFastembedEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/en.json", size: 6865, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2626,7 +2626,7 @@ func yaoDataKbProvidersEmbeddingFastembedZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/embedding/fastembed/zh-cn.json", size: 6685, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2646,7 +2646,7 @@ func yaoDataKbProvidersEmbeddingOpenaiEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/en.json", size: 5636, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2666,7 +2666,7 @@ func yaoDataKbProvidersEmbeddingOpenaiZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/embedding/openai/zh-cn.json", size: 5463, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2686,7 +2686,7 @@ func yaoDataKbProvidersExtractionOpenaiEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/en.json", size: 9110, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2706,7 +2706,7 @@ func yaoDataKbProvidersExtractionOpenaiZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/extraction/openai/zh-cn.json", size: 8827, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2726,7 +2726,7 @@ func yaoDataKbProvidersFetcherHttpEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/en.json", size: 5885, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2746,7 +2746,7 @@ func yaoDataKbProvidersFetcherHttpZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/http/zh-cn.json", size: 5925, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2766,7 +2766,7 @@ func yaoDataKbProvidersFetcherMcpEnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/en.json", size: 6819, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2786,7 +2786,7 @@ func yaoDataKbProvidersFetcherMcpZhCnJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/data/kb/providers/fetcher/mcp/zh-cn.json", size: 6611, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2806,7 +2806,7 @@ func yaoFieldsModelTransJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/fields/model.trans.json", size: 14938, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2826,7 +2826,7 @@ func yaoLangsEnUsJson() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/langs/en-US.json", size: 66, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2846,7 +2846,7 @@ func yaoLangsZhCnGlobalYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/langs/zh-cn/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2866,7 +2866,7 @@ func yaoLangsZhCnLoginsAdminLoginYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/langs/zh-cn/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2886,7 +2886,7 @@ func yaoLangsZhCnLoginsUserLoginYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/langs/zh-cn/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2906,7 +2906,7 @@ func yaoLangsZhHkGlobalYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/langs/zh-hk/global.yml", size: 1762, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2926,7 +2926,7 @@ func yaoLangsZhHkLoginsAdminLoginYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/langs/zh-hk/logins/admin.login.yml", size: 94, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2946,7 +2946,7 @@ func yaoLangsZhHkLoginsUserLoginYml() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/langs/zh-hk/logins/user.login.yml", size: 90, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2966,7 +2966,7 @@ func yaoModelsAgentAssistantModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6945, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/agent/assistant.mod.yao", size: 6945, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -2986,12 +2986,12 @@ func yaoModelsAgentChatModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/agent/chat.mod.yao", size: 3093, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
-var _yaoModelsAgentExecutionModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x58\x4d\x6f\xdc\x36\x10\xbd\xfb\x57\x0c\x74\x72\x00\x27\x48\xdb\xa4\x80\x7d\x2b\x1a\xa3\x75\x91\x22\x86\xeb\x9e\x0c\x63\xc1\x95\x66\xb5\xb4\xf9\xa1\x0c\x47\xb1\x05\xc3\xff\x3d\x20\xa5\x95\x28\xad\x76\x23\x6b\x9d\x4b\xa2\x1d\x0e\x87\xef\xbd\x21\x87\x1c\x3f\x1d\x01\x24\x46\x68\x4c\xce\x20\x39\x7f\xc4\xb4\x64\x69\x4d\x72\xe2\xcd\x4a\x2c\x51\x79\xfb\x95\x5d\x5a\x86\xc1\x68\x86\x2e\x25\x59\x04\x43\xeb\x83\x1b\x1f\x58\x4b\xc7\x96\x2a\x58\x59\x02\x26\x91\xde\x4b\x93\xc3\xe5\xfb\xb7\x97\x1f\xa1\x58\x0b\x87\x60\x4b\x2e\x4a\x76\x20\x4c\x06\x8e\x05\x97\xae\x8e\xcb\x22\x77\xc9\x19\xdc\x24\x22\x47\xc3\xc9\x09\x24\xe4\x43\xfb\x0f\x57\x39\x46\x9d\xdc\x06\xbf\x65\x29\x15\x4b\xbf\x36\x53\x89\xc1\x44\x28\x32\x6b\x54\x95\x9c\xc1\x4a\x28\x57\x1b\x9d\x25\x4e\xce\xe0\xf4\xf4\xf4\xb4\x89\xbf\x54\x9e\xed\x53\xc7\x3b\xac\xb4\xc0\x8e\x1f\x24\xa9\xd5\xda\x2f\xbf\x87\x59\x1d\x08\x9e\x43\xd4\xd4\xaa\x52\x9b\x00\xfc\x08\x00\xe0\x29\xfc\x1b\x69\x2b\xb3\x40\x2f\xd8\xb8\x2a\x82\xed\xe2\x53\x67\x6b\xc5\x8e\x8d\x11\x8a\x3f\x4a\xb6\x6f\xa5\x49\x09\xbd\x05\x0a\x92\x5a\x50\x05\xf7\x58\x25\xc1\xfb\xf9\x64\x7c\xdd\x16\xf7\x62\x0c\x81\x63\x92\x26\x1f\x41\xd1\x26\x1b\x76\xe0\xf9\xdf\xc8\xaf\x25\x46\xb2\xc8\x0c\x0d\xcb\x95\x44\x8a\xc2\xa1\xc9\x79\x9d\x9c\xc1\xef\x1f\x5a\x9b\x29\x95\x6a\x52\xd0\x26\x29\x0c\x94\x21\x62\x94\xcf\x60\x95\x26\xc3\xc7\xc6\xb8\x97\xa8\x46\xbd\x44\x7a\x19\xcb\x7f\xc3\x9c\x5d\x14\xeb\xc4\xeb\x8d\x0f\x1c\x97\x0e\xa9\xe1\xc9\x15\xac\xc8\x6a\x58\x2c\x2a\x61\xdf\xd5\x3e\x6f\x66\x12\x9f\x4c\x91\x51\xe8\x97\x11\xbc\x46\xa1\x77\xd1\x6b\xc6\x80\xd7\x08\xe1\x88\xc1\x12\x95\x35\xb9\x03\xb6\x3f\x9b\xc9\x9d\x5d\xbe\x8c\xc8\x3f\x76\xb9\x8b\xc7\x67\x69\xee\x31\x83\x3b\xbb\x7c\x57\x7b\x85\x9a\xa3\xad\x91\x6c\x07\xd1\xf6\x73\x99\xb7\xef\x98\x64\x9e\x23\x2d\x02\x85\x2d\x42\x68\x4a\x3d\x96\x97\x7a\x12\x5c\xf7\x26\x45\xa4\xfe\xb6\x0f\xc0\x6b\xe9\xa2\x03\xf6\x20\x1c\x34\x8b\x61\x24\x9d\xdd\x94\xe0\x9b\x24\x55\x36\xbd\xf7\xd5\x6b\x5d\x6a\x11\xca\x18\x7e\xf3\xf1\x6e\x5f\x31\x73\x51\xa9\x9e\x42\xf4\xbf\x81\x7b\x44\xb1\x2b\x31\xc3\x98\x11\xa5\x02\x4d\x16\x72\x08\x09\x95\xc6\x34\x9f\xa9\xd5\x85\x42\xf6\x32\x40\xb2\x12\x52\xd5\x5f\xa9\x30\x29\x2a\xff\xa3\x63\x9c\xe1\x4a\x94\x2a\x2c\xd8\xc6\x7a\x3d\x35\xc2\x55\x36\x59\x8c\xcb\xbe\x77\xa4\xc5\x9f\x25\x91\x2f\xeb\x5d\xb6\x07\x91\x23\x49\xa4\x71\x85\x24\xb1\xb9\xa9\x72\x2b\x94\xf3\x1f\x2c\xdc\xbd\x6b\x84\xf2\xff\x65\xa8\xe4\x37\xa4\xca\x7f\x2b\x14\x14\xc4\x1b\x15\xa6\x17\xf1\xf5\xc4\x49\x6b\x52\xdb\xf2\xdc\xb9\x78\xa1\x56\x9e\x8d\x08\x7e\xcf\x4c\x92\xc9\xe4\x61\xeb\x20\x1c\x7b\xee\x8b\x00\xec\x04\x0a\xb2\x39\xa1\x73\x6f\x46\xb9\xfc\x10\x35\x12\x59\xda\xc6\xcc\xf8\xc8\x63\xf7\x64\xdf\x3b\xde\xde\x7e\x04\x34\x3a\x27\x72\x04\xb9\x8a\x72\xbb\xd9\xb1\x73\xe0\x49\x53\x94\xd3\x25\xbd\xe8\x7b\x47\xf0\xbe\x90\xcc\xa5\x11\x6a\x53\x52\x20\x04\x86\xe3\xa6\x32\x85\x89\x33\x15\x1c\xdd\x4e\x3f\x04\x3a\x32\x27\x82\x7b\xf9\xbe\x79\x30\xc2\x71\xe4\x7a\x85\x85\xa5\xb9\x30\x9b\x83\x33\x11\xe0\x5f\x7d\xef\x18\xda\x2f\x2d\xb4\xe0\x34\x13\x4e\x73\x7c\x27\xc2\xb9\xee\x7b\xc7\x70\x7e\x6d\xe1\xdc\xdc\x7a\xb7\x99\x78\x08\x5d\xa9\x78\x3a\xa2\xab\xa1\x7f\x8c\xe9\xb7\x01\xa6\xda\x79\x26\xb2\xae\xb0\x4d\x84\xf6\x69\x6b\x42\x8c\xed\x43\x8b\x6d\xe3\x77\x10\xba\xb6\xd4\x4e\x45\xf7\x79\x6b\x42\x8c\xee\x63\xa4\xdc\xc6\xf3\xdc\x30\x55\x33\xe1\x39\x16\xc4\x0b\x96\x7a\xe4\xda\xf2\x56\xc7\x42\x17\xe3\x17\x39\x31\x5c\xf7\xe6\xed\xba\xcc\x89\x61\x24\xd4\xc1\x2f\x2d\x34\xd9\x0c\xe0\xe7\x26\x9b\x02\x1b\x4d\x76\x18\xe8\x23\x80\xdb\xa6\x0b\x55\xa1\x38\xf9\x66\xb0\xe6\xd0\xf4\x26\xed\xef\x08\xf9\x5a\xb8\x2f\x26\xc2\xa6\x6d\x56\xc3\x8e\x1b\x8b\x6e\xd8\xb7\x7b\xe3\xbd\xce\xca\x12\xca\xdc\xf4\x47\x5b\x64\x75\x93\x1a\x40\xe3\xde\x26\xf5\x71\x31\x68\x86\x17\x4d\xb4\xe1\x1b\x2d\x6a\x78\x63\x38\xed\x03\xf1\x76\x2b\x49\xb5\x64\x63\x59\xb8\xf0\x23\xf5\xab\xbd\x6e\xb6\xba\xab\xf2\x6b\x89\x24\xd1\xc1\x83\xe4\x75\xf3\x4e\x84\x95\x54\x8c\xb4\xbf\xed\x1d\x63\x12\xda\xa7\x7d\x3c\xda\xfe\xea\x40\x16\x3e\xce\xcf\xe2\xd0\x74\x1b\xe1\x94\xed\x60\xd1\x6b\x48\x7a\x27\x7e\x26\x9d\xe6\x85\xe0\xe7\x80\x30\x42\x55\x4e\xba\x97\x43\xef\x36\xd2\x2e\xe4\xc3\x7d\x74\x18\x6c\xda\xf1\x77\x9b\x65\xd5\x6c\xb3\x64\x70\x70\xdb\x37\xf6\x53\x54\x51\xdc\xe6\xe4\x43\xe2\xec\x8a\x17\x19\xfa\xc6\xc3\xb5\xaf\x62\xdf\x56\x90\x96\xce\xd5\x53\xbd\x2b\x3c\x1f\x3d\x1f\x7d\x0f\x00\x00\xff\xff\x5b\x06\x59\x7f\x59\x13\x00\x00")
+var _yaoModelsAgentExecutionModYao = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xb4\x58\x51\x4f\xdc\x38\x10\x7e\xe7\x57\x8c\xf2\x44\x25\x5a\xb5\xbd\x72\x12\xbc\x9d\xae\xe8\x8e\x53\xef\x8a\x38\xfa\x84\xd0\xca\x49\x66\xb3\x66\x1d\x3b\xb5\xc7\x85\x08\xf1\xdf\x2b\x3b\x4e\xd6\xc9\x66\x97\x10\xe8\x0b\x64\xc7\xe3\xf1\xf7\xcd\x8c\xc7\x1e\x3f\x1c\x00\x24\x92\x95\x98\x9c\x42\x72\x76\x8f\x99\x25\xae\x64\x72\xe4\xc4\x82\xa5\x28\x9c\xfc\x52\xa5\x8a\x60\x30\x9a\xa3\xc9\x34\xaf\xbc\xa0\xd3\xc1\x56\x07\x56\xdc\x90\xd2\x35\x2c\x95\x06\xd2\x2c\x5b\x73\x59\xc0\xc5\xfb\xb7\x17\xc7\x50\xad\x98\x41\x50\x96\x2a\x4b\x06\x98\xcc\xc1\x10\x23\x6b\x1a\xbb\xc4\x0a\x93\x9c\xc2\x75\xc2\x0a\x94\x94\x1c\x41\xa2\x9d\x69\xf7\x61\x6a\x43\x58\x26\x37\x5e\x2f\xb5\x5c\x10\x77\x6b\x93\xb6\xe8\x45\x1a\x59\xae\xa4\xa8\x93\x53\x58\x32\x61\x1a\xa1\x51\x9a\x92\x53\x38\x39\x39\x39\x09\xf6\x53\xe1\xd8\x3e\x6c\x78\xfb\x95\x16\xb8\xe1\x07\x49\xa6\xca\xd2\x2d\xbf\x87\x59\x63\x08\x1e\xbd\xd5\x4c\x09\x5b\x4a\x0f\xfc\x00\x00\xe0\xc1\xff\x8d\x7c\xcb\x73\x4f\xcf\xcb\xa8\xae\xbc\xec\xfc\xf3\x46\xd6\x39\x3b\x16\x46\x28\xfe\xb0\xa4\xde\x72\x99\x69\x74\x12\xa8\x34\x2f\x99\xae\x61\x8d\x75\xe2\xb5\x1f\x8f\xc6\xd7\xed\x70\x2f\xc6\x10\x18\xd2\x5c\x16\x23\x28\xba\x60\xc3\x0e\x3c\xdf\x24\xff\x6e\x31\x72\x0b\xcf\x51\x12\x5f\x72\xd4\x91\x39\x94\x05\xad\x92\x53\xf8\xfd\x53\x27\x93\x56\x88\x10\x82\x2e\x48\x7e\xc0\x7a\x8b\x51\x3c\xbd\x94\xcb\x1c\xef\x83\x70\x2f\xd1\x12\xcb\x14\xf5\xf3\x58\xfe\xeb\xe7\xec\xa2\xd8\x04\xbe\x6c\x75\xe0\xd0\x1a\xd4\x81\x27\xd5\xb0\xd4\xaa\x84\xc5\xa2\x66\xea\x5d\xa3\xf3\x66\x26\xf1\xc9\x14\x09\x59\xf9\x3c\x82\x57\xc8\xca\x5d\xf4\xc2\x18\xd0\x0a\xc1\x6f\x31\x48\x51\x28\x59\x18\x20\xf5\xab\x99\xdc\xaa\xf4\x79\x44\xfe\x51\xe9\x2e\x1e\x5f\xb8\x5c\x63\x0e\xb7\x2a\x7d\xd7\x68\xf9\x9a\x53\x2a\xc9\x49\x0d\xac\xed\xe7\x32\x2f\xef\x48\xf3\xa2\x40\xbd\xf0\x14\xb6\x08\xa1\xb4\xe5\x58\x5c\x9a\x49\x70\xd5\x9b\x14\x91\xfa\x5b\xdd\x01\xad\xb8\x89\x36\xd8\x1d\x33\x10\x16\xc3\xc8\x75\xaa\x2d\xc1\xd7\x49\x26\x54\xb6\x76\xd5\x6b\x65\x4b\xe6\xcb\x18\xfe\x70\xf6\x6e\x5e\x31\x72\x51\xa9\x9e\x42\xf4\xff\x81\x7a\x44\x71\x53\x62\x86\x36\x23\x4a\x15\xca\xdc\xc7\x10\x12\x6d\xa5\x0c\x9f\x99\x2a\x2b\x81\xe4\xdc\x00\xc9\x92\x71\xd1\x7c\x65\x4c\x66\x28\xdc\x8f\x0d\xe3\x1c\x97\xcc\x0a\xbf\x60\x67\xeb\xf5\xbc\xe1\x8f\xb2\xc9\xce\xb8\xe8\x6b\x47\xbe\xf8\xd3\x6a\xed\xca\xfa\x26\xda\x03\xcb\x91\x4b\xb8\x34\x15\xd7\xac\x3d\xa9\x0a\xc5\x84\x71\x1f\xc4\xcc\xda\x04\x47\xb9\x7f\x39\x0a\xfe\x03\x75\xed\xbe\x05\x32\xed\x9d\x37\xea\x98\x9e\xc5\xd7\x73\x4e\xd6\x90\xda\x76\xcf\xad\x89\x17\xea\xdc\xd3\x3a\xc1\xe5\xcc\x24\x37\xc9\xc2\xa7\x0e\xc2\xa1\xe3\xbe\xf0\xc0\x8e\xa0\xd2\xaa\xd0\x68\xcc\x9b\x51\x2e\x4f\xa2\x46\xad\x95\xde\xc6\x4c\x78\x4f\x63\xe7\x64\x5f\x3b\x4e\x6f\x37\x02\x25\x1a\xc3\x0a\x04\xbe\x8c\x62\xdb\x66\xec\x1c\x78\xfe\xff\xf4\xba\xf9\x5f\x4f\x7d\x74\xf7\x11\x27\x81\xbe\x66\x7e\x3b\x87\x9c\x9b\x4a\xb0\x1a\x0e\x6d\x95\x33\xc2\x1c\xd2\x3a\x00\x57\x1a\x18\x81\xcf\xb6\x26\x39\xc7\x8e\xbb\xe3\x0f\x1f\x67\xb1\x0a\xa9\xb2\xf0\x71\x7c\x26\xc5\x36\x29\xae\x98\x59\xc3\x4e\xbe\xad\x96\x5b\x01\xa2\xbb\xeb\x54\xe2\xda\xca\xd7\xa7\xcd\x65\x65\xa7\xef\x8f\xf3\xbe\x76\x44\xee\xab\xe6\x05\x97\x4c\xb4\xe7\x03\x78\xc3\x70\x18\x8e\x19\x3f\x71\xe6\x76\x18\xad\x0d\x4f\x02\x1d\x99\x13\xc1\xbd\x78\x1f\x6e\xff\x70\x18\xa9\x5e\x62\xa5\xf4\x5c\x98\xa1\x0a\x4e\x04\xf8\x57\x5f\x3b\x86\xf6\xa1\x83\xe6\x95\x66\xc2\x09\xb5\x78\x22\x9c\xab\xbe\x76\x0c\xe7\x63\x07\xe7\xfa\xc6\xa9\xcd\xc4\xa3\xd1\x58\x41\xd3\x11\x5d\x0e\xf5\x63\x4c\xbf\x0d\x30\x35\xca\x33\x91\x6d\x4e\xa9\x89\xd0\x3e\x6f\x4d\x88\xb1\x7d\xea\xb0\xb5\x7a\x2f\x42\xd7\x9d\x9b\x53\xd1\x7d\xd9\x9a\x10\xa3\x3b\x8e\x3c\xd7\x6a\x9e\x49\xd2\xf5\x4c\x78\x86\x98\xa6\x05\xf1\xb1\x7a\xe9\xa4\x86\x58\x59\x8d\xdf\xca\x34\xc1\x15\x7f\xf2\x6c\xf0\x0b\xc0\x88\xa9\x17\x5f\x9b\x51\xe6\x33\x80\x9f\xc9\x7c\x0a\x6c\x94\xf9\xcb\x40\x1f\x00\xdc\x84\x27\x05\xe1\x8b\x93\xeb\xec\x1b\x0e\xa1\xd1\xec\x7e\x47\xc8\x57\xcc\x7c\x95\x11\xb6\x52\xe5\x0d\xec\xb8\x4b\xdc\x0c\xbb\xde\x7d\xbc\x71\x5d\x2a\x8d\xbc\x90\xfd\xd1\x0e\x59\xf3\xe2\xe0\x41\xe3\xde\x17\x87\xfb\xc5\xe0\x65\x63\x11\xac\x0d\x2f\xdc\xd1\xeb\x45\x0c\xa7\xbb\xed\xdf\x6c\x05\xa9\x71\xd9\x58\x14\xce\xdd\x48\xd3\x82\x35\x9d\xf3\xe6\xde\xf3\xdd\xa2\xe6\x68\xe0\x8e\xd3\x2a\x5c\xfa\x61\xc9\x05\xa1\xde\xff\x86\x31\xc6\xc4\xf7\xc2\xfb\x78\x74\xcd\xf2\x0b\x59\x38\x3b\xbf\x8a\x43\x68\x1d\xfd\x2e\xdb\xc1\xa2\xd7\x5d\xf6\x76\xfc\x4c\x3a\xe1\x86\xe0\xe6\x00\x93\x4c\xd4\x86\x9b\xe7\x43\xdf\x24\xd2\x2e\xe4\xc3\x3c\x7a\x19\x6c\xbd\xe3\x11\x2e\xad\x43\x9a\x25\x83\x8d\xdb\x35\x4c\x0f\x51\x45\x31\xed\xce\x87\xc4\xa8\x25\x2d\x72\x74\x5d\xa4\xe9\x5a\x1c\xd7\x23\xea\x92\x1b\xd3\x4c\x75\xaa\xf0\x78\xf0\x78\xf0\x33\x00\x00\xff\xff\x3d\xaa\x76\x5c\x26\x15\x00\x00")
func yaoModelsAgentExecutionModYaoBytes() ([]byte, error) {
return bindataRead(
@@ -3006,7 +3006,7 @@ func yaoModelsAgentExecutionModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 4953, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/agent/execution.mod.yao", size: 5414, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3026,7 +3026,7 @@ func yaoModelsAgentMessageModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/agent/message.mod.yao", size: 3712, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3046,7 +3046,7 @@ func yaoModelsAgentResumeModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/agent/resume.mod.yao", size: 3896, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3066,7 +3066,7 @@ func yaoModelsAgentSearchModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/agent/search.mod.yao", size: 3103, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3086,7 +3086,7 @@ func yaoModelsAttachmentModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/attachment.mod.yao", size: 4687, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3106,7 +3106,7 @@ func yaoModelsAuditModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/audit.mod.yao", size: 5588, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3126,7 +3126,7 @@ func yaoModelsConfigModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/config.mod.yao", size: 1649, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3146,7 +3146,7 @@ func yaoModelsDslModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/dsl.mod.yao", size: 3826, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3166,7 +3166,7 @@ func yaoModelsInvitationModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/invitation.mod.yao", size: 6693, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3186,7 +3186,7 @@ func yaoModelsJobCategoryModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/job/category.mod.yao", size: 2041, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3206,7 +3206,7 @@ func yaoModelsJobExecutionModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/job/execution.mod.yao", size: 7201, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3226,7 +3226,7 @@ func yaoModelsJobJobModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/job/job.mod.yao", size: 6429, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3246,7 +3246,7 @@ func yaoModelsJobLogModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/job/log.mod.yao", size: 4711, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3266,7 +3266,7 @@ func yaoModelsKbCollectionModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/kb/collection.mod.yao", size: 5390, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3286,7 +3286,7 @@ func yaoModelsKbDocumentModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/kb/document.mod.yao", size: 9906, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3306,7 +3306,7 @@ func yaoModelsMemberModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/member.mod.yao", size: 14798, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3326,7 +3326,7 @@ func yaoModelsRoleModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/role.mod.yao", size: 6434, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3346,7 +3346,7 @@ func yaoModelsTeamModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/team.mod.yao", size: 15823, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3366,7 +3366,7 @@ func yaoModelsUserOauth_accountModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/user/oauth_account.mod.yao", size: 6928, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3386,7 +3386,7 @@ func yaoModelsUserTypeModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/user/type.mod.yao", size: 7502, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3406,7 +3406,7 @@ func yaoModelsUserModYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/models/user.mod.yao", size: 12335, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3426,7 +3426,7 @@ func yaoReleaseAppYaz() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/release/app.yaz", size: 181682, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3446,7 +3446,7 @@ func yaoStoresAgentCacheLruYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/agent/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3466,7 +3466,7 @@ func yaoStoresAgentMemoryChatXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/agent/memory/chat.xun.yao", size: 497, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3486,7 +3486,7 @@ func yaoStoresAgentMemoryContextXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/agent/memory/context.xun.yao", size: 507, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3506,7 +3506,7 @@ func yaoStoresAgentMemoryTeamXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/agent/memory/team.xun.yao", size: 483, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3526,7 +3526,7 @@ func yaoStoresAgentMemoryUserXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/agent/memory/user.xun.yao", size: 489, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3546,7 +3546,7 @@ func yaoStoresCacheLruYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/cache.lru.yao", size: 285, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3566,7 +3566,7 @@ func yaoStoresKbCacheLruYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/kb/cache.lru.yao", size: 304, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3586,7 +3586,7 @@ func yaoStoresKbStoreXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/kb/store.xun.yao", size: 373, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3606,7 +3606,7 @@ func yaoStoresOauthCacheLruYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/oauth/cache.lru.yao", size: 301, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3626,7 +3626,7 @@ func yaoStoresOauthClientXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/oauth/client.xun.yao", size: 377, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3646,7 +3646,7 @@ func yaoStoresOauthStoreXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/oauth/store.xun.yao", size: 401, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3666,7 +3666,7 @@ func yaoStoresStoreXunYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/stores/store.xun.yao", size: 369, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
@@ -3686,7 +3686,7 @@ func yaoUploadersAttachmentLocalYao() (*asset, error) {
return nil, err
}
- info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1769080845, 0)}
+ info := bindataFileInfo{name: "yao/uploaders/attachment.local.yao", size: 1163, mode: os.FileMode(420), modTime: time.Unix(1769226778, 0)}
a := &asset{bytes: bytes, info: info}
return a, nil
}
diff --git a/openapi/agent/robot/DESIGN.md b/openapi/agent/robot/DESIGN.md
index 63caebdc..9fcb8882 100644
--- a/openapi/agent/robot/DESIGN.md
+++ b/openapi/agent/robot/DESIGN.md
@@ -253,9 +253,13 @@ type ExecutionResponse struct {
Status string `json:"status"`
Phase string `json:"phase"`
Error *string `json:"error,omitempty"`
- JobID string `json:"job_id"`
- Name string `json:"name,omitempty"` // Localized execution name
- CurrentTaskName string `json:"current_task_name,omitempty"` // Localized current task
+
+ // UI display fields (from backend Execution)
+ // These are updated by executor at each phase for frontend display
+ Name string `json:"name,omitempty"` // Execution title
+ CurrentTaskName string `json:"current_task_name,omitempty"` // Current task description
+
+ // Phase outputs (for detail view)
Goals *GoalsResponse `json:"goals,omitempty"`
Tasks []TaskResponse `json:"tasks,omitempty"`
Current *CurrentState `json:"current,omitempty"`
@@ -263,6 +267,17 @@ type ExecutionResponse struct {
}
```
+**UI Display Fields Update Timeline:**
+
+| Phase | `Name` | `CurrentTaskName` |
+|-------|--------|-------------------|
+| Created | Human: from `input.messages[0]`
Clock/Event: "Preparing..." | "Starting..." |
+| `inspiration` | - | "Analyzing context..." |
+| `goals` complete | Extracted from first goal in `goals.content` | "Planning goals..." |
+| `tasks` | - | "Breaking down tasks..." |
+| `run` (each task) | - | Current task description |
+| Completed/Failed | - | "Completed" / "Failed: {error}" |
+
### 4.4 ResultResponse
```go
@@ -491,13 +506,50 @@ data: {"error": "Something went wrong", "phase": "run"}
### 8.1 Locale Detection
-Priority order:
-1. Query parameter: `?locale=zh-CN`
-2. Request body field: `locale: "zh-CN"`
-3. Accept-Language header
-4. Default: `en-US`
+**For API requests (Human trigger):**
-### 8.2 Localized Fields
+Priority order:
+1. Request body field: `locale: "zh-CN"` (in TriggerRequest)
+2. Query parameter: `?locale=zh-CN`
+3. Accept-Language header
+4. Robot's `default_locale` config
+5. System default: `en-US`
+
+**For Clock/Event triggers (no user context):**
+
+Priority order:
+1. Robot's `default_locale` config (from `robot_config.default_locale`)
+2. System default: `en-US`
+
+### 8.2 Robot Default Locale
+
+Robots can configure a default language for clock/event triggered executions:
+
+```go
+// In RobotConfig (robot_config field in __yao.member)
+type Config struct {
+ // ... other fields ...
+ DefaultLocale string `json:"default_locale,omitempty"` // "en-US", "zh-CN"
+}
+```
+
+**Language resolution:**
+```go
+func getLocale(robot *Robot, input *TriggerInput) string {
+ // 1. Human trigger with explicit locale
+ if input != nil && input.Locale != "" {
+ return input.Locale
+ }
+ // 2. Robot configured default
+ if robot.Config != nil && robot.Config.DefaultLocale != "" {
+ return robot.Config.DefaultLocale
+ }
+ // 3. System default
+ return "en-US"
+}
+```
+
+### 8.3 Localized Fields
| Response Type | Localized Fields |
|---------------|------------------|
diff --git a/openapi/agent/robot/TODO.md b/openapi/agent/robot/TODO.md
index c685f0a5..e31b7d6b 100644
--- a/openapi/agent/robot/TODO.md
+++ b/openapi/agent/robot/TODO.md
@@ -6,6 +6,207 @@
---
+## Field Alignment Review Summary
+
+> Last reviewed: 2026-01-23
+
+### Robot Fields ✅ Fully Aligned
+
+| Backend (`types.go`) | Frontend (`types.ts`) | Status |
+|---------------------|----------------------|--------|
+| `member_id` | `member_id` | ✅ |
+| `team_id` | `team_id` | ✅ |
+| `display_name` | `display_name` | ✅ |
+| `bio` | `bio` / `description` | ✅ |
+| `name` (← member_id) | `name` | ✅ |
+| `description` (← bio) | `description` | ✅ |
+| `robot_status` | `robot_status` | ✅ |
+| `autonomous_mode` | `autonomous_mode` | ✅ |
+| `robot_config` | `robot_config` | ✅ |
+| `robot_email` | `robot_email` | ✅ |
+| All other fields | Same | ✅ |
+
+### Execution Fields ✅ Aligned
+
+| Backend (`types.go`) | Frontend (`types.ts`) | Status |
+|---------------------|----------------------|--------|
+| `id` | `id` | ✅ Aligned |
+| `member_id` | `member_id` | ✅ |
+| `team_id` | `team_id` | ✅ |
+| `trigger_type` | `trigger_type` | ✅ |
+| `status` | `status` | ✅ |
+| `phase` | `phase` | ✅ |
+| `start_time` | `start_time` | ✅ |
+| `end_time` | `end_time` | ✅ |
+| `error` | `error` | ✅ |
+| `input` | `input` | ✅ Optional |
+| Phase outputs | Same | ✅ Detail view |
+| `name` | `name` | ✅ Added |
+| `current_task_name` | `current_task_name` | ✅ Added |
+| - | `job_id` | 🗑️ **Dead field, to be removed** |
+
+### Task Fields ✅ Aligned
+
+| Backend (`types.go`) | Frontend (`types.ts`) | Status |
+|---------------------|----------------------|--------|
+| `id` | `id` | ✅ |
+| `description` | `description` | ✅ Added |
+| `goal_ref` | `goal_ref` | ✅ |
+| `source` | `source` | ✅ |
+| `executor_type` | `executor_type` | ✅ |
+| `executor_id` | `executor_id` | ✅ |
+| `status` | `status` | ✅ |
+| `order` | `order` | ✅ |
+| `start_time` | `start_time` | ✅ |
+| `end_time` | `end_time` | ✅ |
+
+**Action Items:**
+- [x] **Backend**: `Execution` struct - add `Name`, `CurrentTaskName` fields (see Improvement Plan below)
+- [x] **Backend**: `RobotConfig` struct - add `DefaultLocale` field (see Improvement Plan below)
+- [x] **Backend**: `TriggerInput` struct - add `Locale` field (see Improvement Plan below)
+- [x] **Backend**: Database model `execution.mod.yao` - add `name`, `current_task_name` columns
+- [x] **Backend**: Executor - update `Name`, `CurrentTaskName` at each phase
+- [x] **Backend**: Store layer - add `UpdateUIFields()` method
+- [x] **Backend**: Unit tests for UI fields and i18n (executor/standard/ui_fields_test.go, store/execution_test.go)
+- [x] **Backend**: `Task` struct - add `Description` field for human-readable task description
+- [x] **Backend**: `ParseTask()` - save description from LLM output to `Task.Description`
+- [x] **Frontend**: `Task` type - add `description` field
+- [x] **Frontend**: Task list display - use `description` as primary title, fallback to `executor_id`
+- [ ] **Frontend**: Remove `job_id` field from `types.ts`
+- [ ] **Frontend**: Remove `job_id` mock data from `mock/data.ts`
+- [ ] **Frontend**: Use `name` and `current_task_name` directly from API response
+
+---
+
+### Improvement Plan: Execution UI Display Fields ✅ Implemented
+
+> **Problem:** Frontend needs to display "execution title" and "current task", which must be dynamically updated at different phases
+> **Solution:** Backend manages these fields centrally; `Execution` struct gets new fields, executor updates them at each phase
+
+**1. Execution struct fields (`agent/robot/types/robot.go`):** ✅
+```go
+type Execution struct {
+ // ... existing fields ...
+
+ // UI display fields (updated by executor at each phase)
+ Name string `json:"name,omitempty"` // Execution title
+ CurrentTaskName string `json:"current_task_name,omitempty"` // Current task description
+}
+```
+
+**2. Update timeline:** ✅
+
+| Phase | `Name` | `CurrentTaskName` |
+|-------|--------|-------------------|
+| Created | Human: extract from `input.messages[0]`
Clock/Event: "Preparing..." (localized) | "Starting..." (localized) |
+| `inspiration` | - | "Analyzing context..." (localized) |
+| `goals` complete | Extract first line from `goals.content` | "Planning goals..." (localized) |
+| `tasks` | - | "Breaking down tasks..." (localized) |
+| `run` (each task) | - | Current `task` description (e.g., "Task 1/3: ...") |
+| Completed/Failed | - | "Completed" / "Failed: {error}" (localized) |
+
+**3. Implementation files:**
+- `agent/robot/types/robot.go` - Execution struct fields ✅
+- `agent/robot/store/execution.go` - UpdateUIFields() method ✅
+- `agent/robot/executor/standard/executor.go` - initUIFields(), updateUIFields(), i18n messages ✅
+- `agent/robot/executor/standard/inspiration.go` - Update CurrentTaskName ✅
+- `agent/robot/executor/standard/goals.go` - Update Name and CurrentTaskName ✅
+- `agent/robot/executor/standard/tasks.go` - Update CurrentTaskName ✅
+- `agent/robot/executor/standard/run.go` - Update CurrentTaskName for each task ✅
+- `yao/models/agent/execution.mod.yao` - Database columns ✅
+
+---
+
+### Improvement Plan: i18n Default Locale ✅ Implemented
+
+> **Problem:** Clock/Event triggers have no user context, unknown which language to use for generated content
+> **Solution:** `RobotConfig` gets a default locale configuration field
+
+**1. RobotConfig struct field (`agent/robot/types/config.go`):** ✅
+```go
+type Config struct {
+ // ... existing fields ...
+ DefaultLocale string `json:"default_locale,omitempty"` // "en" | "zh", default "en"
+}
+
+// GetDefaultLocale returns the default locale (default: "en")
+func (c *Config) GetDefaultLocale() string {
+ if c == nil || c.DefaultLocale == "" {
+ return "en"
+ }
+ return c.DefaultLocale
+}
+```
+
+**2. TriggerInput struct field (`agent/robot/types/robot.go`):** ✅
+```go
+type TriggerInput struct {
+ // ... existing fields ...
+ Locale string `json:"locale,omitempty"` // Language from human trigger
+}
+```
+
+**3. Locale determination logic (`agent/robot/executor/standard/executor.go`):** ✅
+```go
+func getEffectiveLocale(robot *Robot, input *TriggerInput) string {
+ // 1. Human trigger: use locale from request
+ if input != nil && input.Locale != "" {
+ return input.Locale
+ }
+ // 2. Clock/Event trigger: use Robot config
+ if robot != nil && robot.Config != nil {
+ return robot.Config.GetDefaultLocale()
+ }
+ // 3. System default
+ return "en"
+}
+```
+
+**4. Locale source priority:** ✅
+
+| Trigger Type | Locale Source |
+|--------------|---------------|
+| Human | Request `locale` → Robot `default_locale` → "en" |
+| Event | Robot `default_locale` → "en" |
+| Clock | Robot `default_locale` → "en" |
+
+**5. Localized messages (`executor.go`):** ✅
+```go
+var uiMessages = map[string]map[string]string{
+ "en": {
+ "preparing": "Preparing...",
+ "starting": "Starting...",
+ "scheduled_execution": "Scheduled execution",
+ "event_prefix": "Event: ",
+ "event_triggered": "Event triggered",
+ "analyzing_context": "Analyzing context...",
+ "planning_goals": "Planning goals...",
+ "breaking_down_tasks": "Breaking down tasks...",
+ "completed": "Completed",
+ "failed_prefix": "Failed: ",
+ "task_prefix": "Task",
+ },
+ "zh": {
+ "preparing": "准备中...",
+ "starting": "启动中...",
+ "scheduled_execution": "定时执行",
+ // ... more Chinese messages
+ },
+}
+```
+
+> **Note:** User preference locale fallback deferred to future version
+
+### Deferred Features (Phase 5/6)
+
+| Feature | Current Status | Future Plan |
+|---------|---------------|-------------|
+| Trigger/Intervene UI | Backend done, frontend deferred | Phase 5 (requires SSE) |
+| Real-time refresh | Polling 60s | Phase 6 (SSE streams) |
+| Multi-turn chat | Not started | Phase 5 |
+
+---
+
## Implementation Strategy
> **Integrate frontend immediately after each phase to validate deliverables.**
@@ -42,8 +243,8 @@
└─ Locale parameter support
🟡 Medium Risk (Deferred):
- Phase 5: Multi-turn Chat API
- Phase 6: Real-time SSE Streams
+ Phase 5: Multi-turn Chat API + Trigger/Intervene UI
+ Phase 6: Real-time SSE Streams (replace polling)
```
---
@@ -277,88 +478,215 @@
---
-## 🟢 Phase 2: Execution Management ⬜ [Low Risk]
+## 🟢 Phase 2: Execution Management [Backend ✅ | Frontend ⬜]
+
+> **Backend:** Steps 1-4 ✅ Complete (including UI fields and i18n)
+> **Frontend:** Step 5 ⬜ Pending
+> **Deferred:** Trigger/Intervene UI → Phase 5 (requires SSE)
**Goal:** Execution listing, details, control, and trigger/intervene (single-submit mode)
-**Risk:** 🟢 Low - Wraps existing API functions
+**Risk:** 🟢 Low - Wraps existing `robot/api` functions
+**Workflow:** 1. Implement All Endpoints → 2. Linter Check → 3. Code Review → 4. Unit Tests → 5. Frontend Integration
-### 2.1 List Executions ⬜
+---
-- [ ] `execution.go` - GET /v1/robots/:id/executions
-- [ ] Parse query params: `status`, `trigger_type`, `keyword`, `page`, `pagesize`
-- [ ] Call `robot/api.GetExecutions()`
-- [ ] Add derived fields: `name`, `current_task_name`
-- [ ] Format response
-- [ ] Test: `tests/robot/execution_list_test.go`
+### Step 1: Implement All OpenAPI Endpoints ✅
-### 2.2 Get Execution ⬜
+> Location: `yao/openapi/agent/robot/`
+> Calls: `yao/agent/robot/api/` (existing functions)
-- [ ] GET /v1/robots/:id/executions/:exec_id
-- [ ] Call `robot/api.GetExecution()`
-- [ ] Full task details with localization
-- [ ] Test: `tests/robot/execution_get_test.go`
+#### 2.1.1 Types (`types.go`) ✅
-### 2.3 Execution Control ⬜
+- [x] `ExecutionFilter` - query params for listing
+- [x] `ExecutionResponse` - single execution response
+- [x] `ExecutionListResponse` - paginated list response
+- [x] `ExecutionControlResponse` - pause/resume/cancel response
+- [x] `TriggerRequest` - trigger execution request
+- [x] `TriggerResponse` - trigger result response
+- [x] `InterveneRequest` - human intervention request
+- [x] `InterveneResponse` - intervention result response
-- [ ] POST /v1/robots/:id/executions/:exec_id/pause
- - [ ] Call `robot/api.Pause()`
-- [ ] POST /v1/robots/:id/executions/:exec_id/resume
- - [ ] Call `robot/api.Resume()`
-- [ ] POST /v1/robots/:id/executions/:exec_id/cancel
- - [ ] Call `robot/api.Stop()`
-- [ ] POST /v1/robots/:id/executions/:exec_id/retry
- - [ ] Re-trigger with same input
-- [ ] Test: `tests/robot/execution_control_test.go`
+#### 2.1.2 Execution Handlers (`execution.go`) ✅
-### 2.4 Execution Types ⬜
+> **Permission Note:** Execution permissions are inherited from the parent robot.
+> Check robot's `__yao_team_id` and `__yao_created_by` for access control.
-- [ ] Add to `types.go`:
- - [ ] `ExecutionResponse` struct
- - [ ] `TaskResponse` struct
- - [ ] `CurrentStateResponse` struct
- - [ ] `GoalsResponse` struct
- - [ ] `DeliveryResultResponse` struct
+- [x] `ListExecutions` - GET /v1/agent/robots/:id/executions
+ - Parse query: `status`, `trigger_type`, `keyword`, `page`, `pagesize`
+ - Call `robot/api.ListExecutions()`
+ - Permission: Check robot CanRead (via robot ID)
+- [x] `GetExecution` - GET /v1/agent/robots/:id/executions/:exec_id
+ - Call `robot/api.GetExecution()`
+ - Permission: Check robot CanRead (via robot ID)
+- [x] `PauseExecution` - POST /v1/agent/robots/:id/executions/:exec_id/pause
+ - Call `robot/api.PauseExecution()`
+ - Permission: Check robot CanWrite (via robot ID)
+- [x] `ResumeExecution` - POST /v1/agent/robots/:id/executions/:exec_id/resume
+ - Call `robot/api.ResumeExecution()`
+ - Permission: Check robot CanWrite (via robot ID)
+- [x] `CancelExecution` - POST /v1/agent/robots/:id/executions/:exec_id/cancel
+ - Call `robot/api.StopExecution()`
+ - Permission: Check robot CanWrite (via robot ID)
-### 2.5 Trigger & Intervene (Single-Submit Mode) ⬜
+#### 2.1.3 Trigger Handlers (`trigger.go`) ✅
-> **Note:** This is single-submit mode. Multi-turn chat is deferred to Phase 5.
+> **Permission Note:** Same as execution - check robot's permission.
-- [ ] `trigger.go` - POST /v1/robots/:id/trigger
-- [ ] Parse `TriggerRequest` (messages, attachments)
-- [ ] Call `robot/api.Trigger()`
-- [ ] Return execution ID and status
-- [ ] Optional: Return SSE stream for progress
-- [ ] Test: `tests/robot/trigger_test.go`
+- [x] `TriggerRobot` - POST /v1/agent/robots/:id/trigger
+ - Parse `TriggerRequest` (messages, trigger_type)
+ - Call `robot/api.Trigger()`
+ - Return execution ID and status
+ - Permission: Check robot CanWrite (via robot ID)
+- [x] `InterveneRobot` - POST /v1/agent/robots/:id/intervene
+ - Parse `InterveneRequest` (action, messages)
+ - Call `robot/api.Intervene()`
+ - Return result
+ - Permission: Check robot CanWrite (via robot ID)
-- [ ] POST /v1/robots/:id/intervene
-- [ ] Parse `InterveneRequest`
-- [ ] Call `robot/api.Intervene()`
-- [ ] Return result
-- [ ] Test: `tests/robot/intervene_test.go`
+#### 2.1.4 Route Registration (`robot.go`) ✅
-### 2.6 Trigger Types ⬜
+- [x] Add execution routes to `Attach()`:
+ - `GET /:id/executions`
+ - `GET /:id/executions/:exec_id`
+ - `POST /:id/executions/:exec_id/pause`
+ - `POST /:id/executions/:exec_id/resume`
+ - `POST /:id/executions/:exec_id/cancel`
+ - `POST /:id/trigger`
+ - `POST /:id/intervene`
-- [ ] Add to `types.go`:
- - [ ] `TriggerRequest` struct
- - [ ] `TriggerResponse` struct
- - [ ] `InterveneRequest` struct
- - [ ] `InterveneResponse` struct
- - [ ] `Message` struct
- - [ ] `Attachment` struct
+---
-### 2.7 Frontend Integration ⬜
+### Step 2: Linter Check ✅
-> Integrate immediately after backend completion
+- [x] Run `ReadLints` on all modified files
+- [x] Fix any linter errors
+- [x] Verify imports are correct
+- [x] Build verification passed
-- [ ] SDK: Add execution methods to `robot.ts`
- - [ ] `listExecutions(robotId, params)`
- - [ ] `getExecution(robotId, execId)`
- - [ ] `pauseExecution()`, `resumeExecution()`, `cancelExecution()`
- - [ ] `triggerRobot(robotId, data)`
- - [ ] `intervene(robotId, data)`
-- [ ] Page: Execution list/detail page integration
-- [ ] Page: Assign Task (trigger execution) integration
-- [ ] Verify: E2E testing
+---
+
+### Step 3: Code Review ✅
+
+- [x] Review type definitions (`types.go`)
+ - `ExecutionFilter`, `ExecutionResponse`, `ExecutionListResponse`, `ExecutionControlResponse`
+ - `TriggerRequest`, `TriggerResponse`, `InterveneRequest`, `InterveneResponse`
+ - Conversion functions: `NewExecutionListResponse`, `NewExecutionResponseFromExecution`, `NewExecutionResponseBrief`
+- [x] Review permission handling
+ - All execution/trigger handlers check robot permission first
+ - Read permission for listing and getting executions
+ - Write permission for control (pause/resume/cancel), trigger, and intervene
+ - Permission inherited from parent robot (check via `YaoTeamID` and `YaoCreatedBy`)
+- [x] Review error handling
+ - Fixed: Use `errors.Is()` instead of `==` for error comparison
+ - Proper HTTP status codes (400, 404, 403, 500)
+ - Consistent error response format
+- [x] Review response formats
+ - Brief format for list view (omits phase outputs)
+ - Full format for detail view (includes all fields)
+ - Consistent with existing robot responses
+
+---
+
+### Step 4: Unit Tests ✅
+
+> Location: `yao/openapi/tests/agent/`
+> Uses `testing.Short()` to skip AI/manager-dependent tests
+
+- [x] Create `robot_execution_test.go`
+ - [x] `TestListExecutions` - list executions with pagination/filters
+ - [x] `TestGetExecution` - get execution details, not found cases
+ - [x] `TestExecutionControl` - pause/resume/cancel endpoints
+ - [x] `TestExecutionPermissions` - permission inheritance from robot
+- [x] Create `robot_trigger_test.go`
+ - [x] `TestTriggerRobot` - trigger with messages, action, invalid body
+ - [x] `TestInterveneRobot` - intervene with action, missing action validation
+ - [x] `TestTriggerPermissions` - permission inheritance from robot
+- [x] All tests use `testing.Short()` to skip AI-dependent tests
+- [x] Tests compile successfully
+- [x] All tests pass (with manager not started gracefully handled)
+
+---
+
+### Step 5: Frontend Integration ⬜
+
+> Location: `cui/packages/cui/openapi/agent/robot/`
+> **Note:** Trigger/Intervene API deferred to Phase 5 (waiting for SSE support)
+> **Note:** Use 1-minute polling for execution list refresh (will switch to SSE in Phase 6)
+
+#### 5.1 Prerequisites ✅
+
+> **Dependency:** Backend improvement plans completed (see "Improvement Plan" sections above)
+
+**Backend (Completed):**
+- [x] `Execution` struct - add `Name`, `CurrentTaskName` fields
+- [x] `RobotConfig` struct - add `DefaultLocale` field
+- [x] `TriggerInput` struct - add `Locale` field
+- [x] Executor - update `Name`, `CurrentTaskName` at each phase
+- [x] Store - add `UpdateUIFields()` method
+- [x] Unit tests for UI fields and i18n
+
+**Frontend Cleanup (Completed):**
+- [x] Components already use `exec.id` (no changes needed)
+- [x] Remove `job_id` field from `types.ts`
+- [x] ~~Remove `job_id` from `mock/data.ts`~~ (mock kept for reference, not used)
+- [x] Use `name`/`current_task_name` directly from API response (string, not `{en, cn}`)
+
+#### 5.2 SDK Types (`types.ts`) ✅
+
+- [x] `ExecutionFilter` interface
+- [x] `ExecutionResponse` interface (align with backend)
+- [x] `ExecutionListResponse` interface
+- [x] `ExecutionControlResponse` interface
+- [x] `ExecStatus`, `TriggerType`, `Phase` type aliases
+
+**Deferred to Phase 5 (SSE):**
+- [ ] ~~`TriggerRequest` / `TriggerResponse` interfaces~~
+- [ ] ~~`InterveneRequest` / `InterveneResponse` interfaces~~
+
+#### 5.3 SDK Methods (`robots.ts`) ✅
+
+- [x] `ListExecutions(robotId, filter)`
+- [x] `GetExecution(robotId, execId)`
+- [x] `PauseExecution(robotId, execId)`
+- [x] `ResumeExecution(robotId, execId)`
+- [x] `CancelExecution(robotId, execId)`
+
+**Deferred to Phase 5 (SSE):**
+- [ ] ~~`Trigger(robotId, data)`~~
+- [ ] ~~`Intervene(robotId, data)`~~
+
+#### 5.4 Page Integration ✅
+
+- [x] ActiveTab: Replace mock with `ListExecutions()` API
+ - [x] Filter: `status=running|pending`
+ - [x] Polling: 1-minute interval (60000ms) - will switch to SSE in Phase 6
+- [x] HistoryTab: Replace mock with `ListExecutions()` API
+ - [x] Filter: `status` filter, `keyword` search
+ - [x] Pagination: page/pagesize
+ - [x] Polling: 1-minute interval for list refresh
+- [x] Execution Detail: Call `GetExecution()` API
+ - [x] Display execution phases and outputs
+ - [x] Display `name` and `current_task_name` from API
+ - [x] Display `error` field for failed executions
+ - [x] Execution controls: Pause/Resume/Cancel buttons (call control APIs)
+ - [x] Auto-refresh while execution is running (5s for running)
+- [x] useRobots hook extended with execution methods
+
+**Deferred to Phase 5 (SSE):**
+- [ ] ~~Assign Task Modal: Call `Trigger()` API~~
+- [ ] ~~GuideExecution: Call `Intervene()` API~~
+
+#### 5.5 Polling vs SSE Strategy
+
+**Current (Phase 2):** Polling
+- Refresh execution list every 60 seconds
+- Manual refresh button for immediate update
+- Acceptable latency for status display
+
+**Future (Phase 6):** SSE Real-time
+- `GET /robots/:id/executions/stream` - real-time execution updates
+- `GET /robots/stream` - robot status changes
+- Instant updates, no polling delay
---
@@ -455,12 +783,13 @@
---
-## 🟡 Phase 5: Multi-turn Chat API ⬜ [Medium Risk - Deferred]
+## 🟡 Phase 5: Multi-turn Chat API + Trigger/Intervene UI ⬜ [Medium Risk - Deferred]
> **Frontend Fallback:** Single-submit mode (user input → immediate execution)
> **Risk:** 🟡 Medium - New stateful component
+> **Dependency:** Requires SSE infrastructure (partially)
-**Goal:** Multi-turn conversation before execution
+**Goal:** Multi-turn conversation before execution + Human trigger/intervene UI
### 5.1 Backend Prerequisites ⬜
@@ -486,14 +815,36 @@
- [ ] Use conversation history as execution input
- [ ] Auto-cleanup conversation after execution starts
+### 5.4 Frontend Trigger/Intervene Integration (Deferred from Phase 2) ⬜
+
+> **Note:** These features require SSE for proper UX (streaming response)
+> Currently backend `/trigger` and `/intervene` endpoints exist but return immediately
+> Frontend needs streaming response to show assistant's reaction before confirming
+
+**SDK Types:**
+- [ ] `TriggerRequest` / `TriggerResponse` interfaces
+- [ ] `InterveneRequest` / `InterveneResponse` interfaces
+- [ ] `ChatMessage` interface for multi-turn
+
+**SDK Methods:**
+- [ ] `Trigger(robotId, data)` - with SSE support
+- [ ] `Intervene(robotId, data)` - with SSE support
+- [ ] `Chat(robotId, data)` - multi-turn conversation SSE
+
+**Page Integration:**
+- [ ] AssignTaskDrawer: Multi-turn chat before trigger
+- [ ] GuideExecutionDrawer: Multi-turn intervention
+- [ ] Real-time streaming response display
+
---
## 🟡 Phase 6: Real-time SSE Streams ⬜ [Medium Risk - Deferred]
-> **Frontend Fallback:** Polling (GET /executions every 3-5 seconds)
+> **Frontend Current:** Polling every 60 seconds (1 minute)
+> **Frontend Future:** SSE streams for instant updates
> **Risk:** 🟡 Medium - Requires modification of executor/manager
-**Goal:** SSE streams for real-time status updates
+**Goal:** SSE streams for real-time status updates, replacing polling
### 6.1 Backend Event System ⬜
@@ -632,9 +983,9 @@ yao/openapi/tests/robot/
| 1. Core CRUD | 🟢 | ✅ | ✅ | Robot CRUD endpoints |
| 1-FE Frontend Integration | 🟢 | - | ✅ | SDK ✅, Page Integration ✅, UI/UX ✅ |
| 1.5 Manager Lifecycle | 🟢 | ✅ | - | Auto-start, auto-reload, graceful shutdown |
-| 2. Execution | 🟢 | ⬜ | ⬜ | Execution listing, control, trigger |
+| 2. Execution | 🟢 | ✅ | ⬜ | Execution listing, control, trigger (backend complete with UI fields & i18n) |
| 3. Results/Activities | 🟢 | ⬜ | ⬜ | Deliverables and activity feed |
-| 4. i18n | 🟢 | ⬜ | ⬜ | Locale parameter support |
+| 4. i18n | 🟢 | ✅ | ⬜ | Locale parameter support (backend executor i18n complete) |
| 5. Chat API | 🟡 | ⬜ | ⬜ | Multi-turn conversation (Deferred) |
| 6. SSE Streams | 🟡 | ⬜ | ⬜ | Real-time status updates (Deferred) |
@@ -809,8 +1160,8 @@ Each phase independently deliverable:
| Phase | Backend | Frontend | Verifiable Features |
|-------|---------|----------|---------------------|
| 1 | ✅ | ✅ | Robot CRUD basic management |
-| 2 | ⬜ | ⬜ | Execution list/control/trigger |
+| 2 | ✅ | ⬜ | Execution list/control/trigger (backend with UI fields & i18n) |
| 3 | ⬜ | ⬜ | Results/Activities viewing |
-| 4 | ⬜ | ⬜ | Multi-language support |
+| 4 | ✅ | ⬜ | Multi-language support (backend executor i18n) |
| 5 | ⬜ | ⬜ | Multi-turn chat UX (optional) |
| 6 | ⬜ | ⬜ | Real-time push (optional) |
diff --git a/openapi/agent/robot/execution.go b/openapi/agent/robot/execution.go
new file mode 100644
index 00000000..4446dd77
--- /dev/null
+++ b/openapi/agent/robot/execution.go
@@ -0,0 +1,348 @@
+package robot
+
+import (
+ "errors"
+
+ "github.com/gin-gonic/gin"
+ "github.com/yaoapp/kun/log"
+ robotapi "github.com/yaoapp/yao/agent/robot/api"
+ robottypes "github.com/yaoapp/yao/agent/robot/types"
+ "github.com/yaoapp/yao/openapi/oauth/authorized"
+ "github.com/yaoapp/yao/openapi/response"
+)
+
+// ==================== Execution Handlers ====================
+// Permission Note: Execution permissions are inherited from the parent robot.
+// Check robot's __yao_team_id and __yao_created_by for access control.
+
+// ListExecutions lists executions for a robot
+// GET /v1/agent/robots/:id/executions
+func ListExecutions(c *gin.Context) {
+ // Get authorized information
+ authInfo := authorized.GetInfo(c)
+
+ // Get robot ID from URL parameter
+ robotID := c.Param("id")
+ if robotID == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "robot id is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Create robot context
+ ctx := &robottypes.Context{}
+
+ // Check robot permission first (executions inherit robot permission)
+ robotResp, err := robotapi.GetRobotResponse(ctx, robotID)
+ if err != nil {
+ if errors.Is(err, robottypes.ErrRobotNotFound) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Robot not found: " + robotID,
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to get robot: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Check read permission on robot
+ if !CanRead(c, authInfo, robotResp.YaoTeamID, robotResp.YaoCreatedBy) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrAccessDenied.Code,
+ ErrorDescription: "Forbidden: No permission to access this robot's executions",
+ }
+ response.RespondWithError(c, response.StatusForbidden, errorResp)
+ return
+ }
+
+ // Parse query parameters
+ var filter ExecutionFilter
+ if err := c.ShouldBindQuery(&filter); err != nil {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Invalid query parameters: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Apply defaults
+ if filter.Page <= 0 {
+ filter.Page = 1
+ }
+ if filter.PageSize <= 0 {
+ filter.PageSize = 20
+ }
+ if filter.PageSize > 100 {
+ filter.PageSize = 100
+ }
+
+ // Build API query
+ query := &robotapi.ExecutionQuery{
+ Page: filter.Page,
+ PageSize: filter.PageSize,
+ }
+ if filter.Status != "" {
+ query.Status = robottypes.ExecStatus(filter.Status)
+ }
+ if filter.TriggerType != "" {
+ query.Trigger = robottypes.TriggerType(filter.TriggerType)
+ }
+
+ // Call API layer
+ result, err := robotapi.ListExecutions(ctx, robotID, query)
+ if err != nil {
+ log.Error("Failed to list executions for robot %s: %v", robotID, err)
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to list executions: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Convert to response (brief format for list)
+ data := make([]*ExecutionResponse, 0, len(result.Data))
+ for _, exec := range result.Data {
+ data = append(data, NewExecutionResponseBrief(exec))
+ }
+
+ resp := &ExecutionListResponse{
+ Data: data,
+ Total: result.Total,
+ Page: result.Page,
+ PageSize: result.PageSize,
+ }
+ response.RespondWithSuccess(c, response.StatusOK, resp)
+}
+
+// GetExecution gets a single execution by ID
+// GET /v1/agent/robots/:id/executions/:exec_id
+func GetExecution(c *gin.Context) {
+ // Get authorized information
+ authInfo := authorized.GetInfo(c)
+
+ // Get robot ID and execution ID from URL parameters
+ robotID := c.Param("id")
+ execID := c.Param("exec_id")
+
+ if robotID == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "robot id is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+ if execID == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "execution id is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Create robot context
+ ctx := &robottypes.Context{}
+
+ // Check robot permission first
+ robotResp, err := robotapi.GetRobotResponse(ctx, robotID)
+ if err != nil {
+ if errors.Is(err, robottypes.ErrRobotNotFound) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Robot not found: " + robotID,
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to get robot: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Check read permission on robot
+ if !CanRead(c, authInfo, robotResp.YaoTeamID, robotResp.YaoCreatedBy) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrAccessDenied.Code,
+ ErrorDescription: "Forbidden: No permission to access this robot's executions",
+ }
+ response.RespondWithError(c, response.StatusForbidden, errorResp)
+ return
+ }
+
+ // Get execution
+ exec, err := robotapi.GetExecution(ctx, execID)
+ if err != nil {
+ log.Error("Failed to get execution %s: %v", execID, err)
+
+ if err.Error() == "execution not found: "+execID {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Execution not found: " + execID,
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to get execution: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Verify execution belongs to this robot
+ if exec.MemberID != robotID {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Execution does not belong to this robot",
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+
+ // Convert to response (full format for detail)
+ resp := NewExecutionResponseFromExecution(exec)
+ response.RespondWithSuccess(c, response.StatusOK, resp)
+}
+
+// PauseExecution pauses a running execution
+// POST /v1/agent/robots/:id/executions/:exec_id/pause
+func PauseExecution(c *gin.Context) {
+ handleExecutionControl(c, "pause")
+}
+
+// ResumeExecution resumes a paused execution
+// POST /v1/agent/robots/:id/executions/:exec_id/resume
+func ResumeExecution(c *gin.Context) {
+ handleExecutionControl(c, "resume")
+}
+
+// CancelExecution cancels/stops an execution
+// POST /v1/agent/robots/:id/executions/:exec_id/cancel
+func CancelExecution(c *gin.Context) {
+ handleExecutionControl(c, "cancel")
+}
+
+// handleExecutionControl handles pause/resume/cancel operations
+func handleExecutionControl(c *gin.Context, action string) {
+ // Get authorized information
+ authInfo := authorized.GetInfo(c)
+
+ // Get robot ID and execution ID from URL parameters
+ robotID := c.Param("id")
+ execID := c.Param("exec_id")
+
+ if robotID == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "robot id is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+ if execID == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "execution id is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Create robot context
+ ctx := &robottypes.Context{}
+
+ // Check robot permission first
+ robotResp, err := robotapi.GetRobotResponse(ctx, robotID)
+ if err != nil {
+ if errors.Is(err, robottypes.ErrRobotNotFound) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Robot not found: " + robotID,
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to get robot: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Check write permission on robot (control operations require write)
+ if !CanWrite(c, authInfo, robotResp.YaoTeamID, robotResp.YaoCreatedBy) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrAccessDenied.Code,
+ ErrorDescription: "Forbidden: No permission to control this robot's executions",
+ }
+ response.RespondWithError(c, response.StatusForbidden, errorResp)
+ return
+ }
+
+ // Execute the control action
+ var controlErr error
+ switch action {
+ case "pause":
+ controlErr = robotapi.PauseExecution(ctx, execID)
+ case "resume":
+ controlErr = robotapi.ResumeExecution(ctx, execID)
+ case "cancel":
+ controlErr = robotapi.StopExecution(ctx, execID)
+ default:
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Invalid action: " + action,
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ if controlErr != nil {
+ log.Error("Failed to %s execution %s: %v", action, execID, controlErr)
+
+ // Check for common errors
+ errMsg := controlErr.Error()
+ if errMsg == "execution_id is required" || errMsg == "execution not found" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Execution not found: " + execID,
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to " + action + " execution: " + controlErr.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Return success
+ resp := &ExecutionControlResponse{
+ ExecutionID: execID,
+ Action: action + "d", // paused, resumed, cancelled
+ Success: true,
+ Message: "Execution " + action + "d successfully",
+ }
+ response.RespondWithSuccess(c, response.StatusOK, resp)
+}
diff --git a/openapi/agent/robot/robot.go b/openapi/agent/robot/robot.go
index a4cf498e..9ec692f0 100644
--- a/openapi/agent/robot/robot.go
+++ b/openapi/agent/robot/robot.go
@@ -22,4 +22,15 @@ func Attach(group *gin.RouterGroup, oauth types.OAuth) {
// Robot Status
group.GET("/:id/status", GetRobotStatus) // GET /robots/:id/status - Get robot runtime status
+
+ // Execution Management
+ group.GET("/:id/executions", ListExecutions) // GET /robots/:id/executions - List robot executions
+ group.GET("/:id/executions/:exec_id", GetExecution) // GET /robots/:id/executions/:exec_id - Get execution details
+ group.POST("/:id/executions/:exec_id/pause", PauseExecution) // POST /robots/:id/executions/:exec_id/pause - Pause execution
+ group.POST("/:id/executions/:exec_id/resume", ResumeExecution) // POST /robots/:id/executions/:exec_id/resume - Resume execution
+ group.POST("/:id/executions/:exec_id/cancel", CancelExecution) // POST /robots/:id/executions/:exec_id/cancel - Cancel execution
+
+ // Trigger & Intervene
+ group.POST("/:id/trigger", TriggerRobot) // POST /robots/:id/trigger - Trigger robot execution
+ group.POST("/:id/intervene", InterveneRobot) // POST /robots/:id/intervene - Human intervention
}
diff --git a/openapi/agent/robot/trigger.go b/openapi/agent/robot/trigger.go
new file mode 100644
index 00000000..2abb56ac
--- /dev/null
+++ b/openapi/agent/robot/trigger.go
@@ -0,0 +1,271 @@
+package robot
+
+import (
+ "errors"
+
+ "github.com/gin-gonic/gin"
+ "github.com/yaoapp/kun/log"
+ agentcontext "github.com/yaoapp/yao/agent/context"
+ robotapi "github.com/yaoapp/yao/agent/robot/api"
+ robottypes "github.com/yaoapp/yao/agent/robot/types"
+ "github.com/yaoapp/yao/openapi/oauth/authorized"
+ "github.com/yaoapp/yao/openapi/response"
+)
+
+// ==================== Trigger Handlers ====================
+// Permission Note: Same as execution - check robot's permission.
+
+// TriggerRobot triggers a robot execution
+// POST /v1/agent/robots/:id/trigger
+func TriggerRobot(c *gin.Context) {
+ // Get authorized information
+ authInfo := authorized.GetInfo(c)
+
+ // Get robot ID from URL parameter
+ robotID := c.Param("id")
+ if robotID == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "robot id is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Parse request body
+ var req TriggerRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Invalid request body: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Create robot context
+ ctx := &robottypes.Context{}
+
+ // Check robot permission first
+ robotResp, err := robotapi.GetRobotResponse(ctx, robotID)
+ if err != nil {
+ if errors.Is(err, robottypes.ErrRobotNotFound) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Robot not found: " + robotID,
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to get robot: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Check write permission on robot (trigger requires write permission)
+ if !CanWrite(c, authInfo, robotResp.YaoTeamID, robotResp.YaoCreatedBy) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrAccessDenied.Code,
+ ErrorDescription: "Forbidden: No permission to trigger this robot",
+ }
+ response.RespondWithError(c, response.StatusForbidden, errorResp)
+ return
+ }
+
+ // Build API trigger request
+ apiReq := buildAPITriggerRequest(&req)
+
+ // Call API layer
+ result, err := robotapi.Trigger(ctx, robotID, apiReq)
+ if err != nil {
+ log.Error("Failed to trigger robot %s: %v", robotID, err)
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to trigger robot: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Convert to response
+ resp := &TriggerResponse{
+ Accepted: result.Accepted,
+ ExecutionID: result.ExecutionID,
+ Queued: result.Queued,
+ Message: result.Message,
+ }
+
+ if result.Accepted {
+ response.RespondWithSuccess(c, response.StatusOK, resp)
+ } else {
+ // Trigger was not accepted (e.g., queue full, robot paused)
+ response.RespondWithSuccess(c, response.StatusOK, resp)
+ }
+}
+
+// InterveneRobot performs human intervention on a robot
+// POST /v1/agent/robots/:id/intervene
+func InterveneRobot(c *gin.Context) {
+ // Get authorized information
+ authInfo := authorized.GetInfo(c)
+
+ // Get robot ID from URL parameter
+ robotID := c.Param("id")
+ if robotID == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "robot id is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Parse request body
+ var req InterveneRequest
+ if err := c.ShouldBindJSON(&req); err != nil {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Invalid request body: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Validate action
+ if req.Action == "" {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "action is required",
+ }
+ response.RespondWithError(c, response.StatusBadRequest, errorResp)
+ return
+ }
+
+ // Create robot context
+ ctx := &robottypes.Context{}
+
+ // Check robot permission first
+ robotResp, err := robotapi.GetRobotResponse(ctx, robotID)
+ if err != nil {
+ if errors.Is(err, robottypes.ErrRobotNotFound) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrInvalidRequest.Code,
+ ErrorDescription: "Robot not found: " + robotID,
+ }
+ response.RespondWithError(c, response.StatusNotFound, errorResp)
+ return
+ }
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to get robot: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Check write permission on robot (intervention requires write permission)
+ if !CanWrite(c, authInfo, robotResp.YaoTeamID, robotResp.YaoCreatedBy) {
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrAccessDenied.Code,
+ ErrorDescription: "Forbidden: No permission to intervene with this robot",
+ }
+ response.RespondWithError(c, response.StatusForbidden, errorResp)
+ return
+ }
+
+ // Build API trigger request for intervention
+ apiReq := &robotapi.TriggerRequest{
+ Type: robottypes.TriggerHuman,
+ Action: robottypes.InterventionAction(req.Action),
+ PlanAt: req.PlanAt,
+ }
+
+ // Convert messages
+ if len(req.Messages) > 0 {
+ apiReq.Messages = convertMessagesToContext(req.Messages)
+ }
+
+ // Call API layer (Intervene uses TriggerHuman internally)
+ result, err := robotapi.Intervene(ctx, robotID, apiReq)
+ if err != nil {
+ log.Error("Failed to intervene with robot %s: %v", robotID, err)
+ errorResp := &response.ErrorResponse{
+ Code: response.ErrServerError.Code,
+ ErrorDescription: "Failed to intervene: " + err.Error(),
+ }
+ response.RespondWithError(c, response.StatusInternalServerError, errorResp)
+ return
+ }
+
+ // Convert to response
+ resp := &InterveneResponse{
+ Accepted: result.Accepted,
+ ExecutionID: result.ExecutionID,
+ Message: result.Message,
+ }
+
+ response.RespondWithSuccess(c, response.StatusOK, resp)
+}
+
+// ==================== Helper Functions ====================
+
+// buildAPITriggerRequest builds robotapi.TriggerRequest from HTTP request
+func buildAPITriggerRequest(req *TriggerRequest) *robotapi.TriggerRequest {
+ apiReq := &robotapi.TriggerRequest{}
+
+ // Set trigger type (default to human)
+ switch req.TriggerType {
+ case "event":
+ apiReq.Type = robottypes.TriggerEvent
+ case "clock":
+ apiReq.Type = robottypes.TriggerClock
+ default:
+ apiReq.Type = robottypes.TriggerHuman
+ }
+
+ // Human intervention fields
+ if req.Action != "" {
+ apiReq.Action = robottypes.InterventionAction(req.Action)
+ }
+ if len(req.Messages) > 0 {
+ apiReq.Messages = convertMessagesToContext(req.Messages)
+ }
+
+ // Event fields
+ if req.Source != "" {
+ apiReq.Source = robottypes.EventSource(req.Source)
+ }
+ if req.EventType != "" {
+ apiReq.EventType = req.EventType
+ }
+ if req.Data != nil {
+ apiReq.Data = req.Data
+ }
+
+ // Executor mode
+ if req.ExecutorMode != "" {
+ apiReq.ExecutorMode = robottypes.ExecutorMode(req.ExecutorMode)
+ }
+
+ // i18n locale
+ if req.Locale != "" {
+ apiReq.Locale = req.Locale
+ }
+
+ return apiReq
+}
+
+// convertMessagesToContext converts MessageItem slice to agent context messages
+func convertMessagesToContext(msgs []MessageItem) []agentcontext.Message {
+ result := make([]agentcontext.Message, 0, len(msgs))
+ for _, m := range msgs {
+ result = append(result, agentcontext.Message{
+ Role: agentcontext.MessageRole(m.Role),
+ Content: m.Content,
+ })
+ }
+ return result
+}
diff --git a/openapi/agent/robot/types.go b/openapi/agent/robot/types.go
index 7dd328de..34ca9b75 100644
--- a/openapi/agent/robot/types.go
+++ b/openapi/agent/robot/types.go
@@ -1,9 +1,11 @@
package robot
import (
+ "fmt"
"time"
robotapi "github.com/yaoapp/yao/agent/robot/api"
+ robottypes "github.com/yaoapp/yao/agent/robot/types"
)
// ==================== Request Types ====================
@@ -253,3 +255,209 @@ func NewStatusResponse(s *robotapi.RobotState) *StatusResponse {
RunningIDs: s.RunningIDs,
}
}
+
+// ==================== Execution Types ====================
+
+// ExecutionFilter - query params for listing executions
+type ExecutionFilter struct {
+ Status string `form:"status"` // pending | running | paused | completed | failed | cancelled
+ TriggerType string `form:"trigger_type"` // clock | human | event
+ Keyword string `form:"keyword"` // search in execution details
+ Page int `form:"page"`
+ PageSize int `form:"pagesize"`
+}
+
+// ExecutionResponse - single execution response
+type ExecutionResponse struct {
+ ID string `json:"id"`
+ MemberID string `json:"member_id"`
+ TeamID string `json:"team_id"`
+ TriggerType string `json:"trigger_type"`
+ Status string `json:"status"`
+ Phase string `json:"phase"`
+ StartTime time.Time `json:"start_time"`
+ EndTime *time.Time `json:"end_time,omitempty"`
+ Error string `json:"error,omitempty"`
+
+ // UI display fields (updated by executor at each phase)
+ Name string `json:"name,omitempty"` // Execution title
+ CurrentTaskName string `json:"current_task_name,omitempty"` // Current task description
+
+ // Phase outputs (optional, included in detail view)
+ Inspiration interface{} `json:"inspiration,omitempty"`
+ Goals interface{} `json:"goals,omitempty"`
+ Tasks interface{} `json:"tasks,omitempty"`
+ Current interface{} `json:"current,omitempty"`
+ Results interface{} `json:"results,omitempty"`
+ Delivery interface{} `json:"delivery,omitempty"`
+
+ // Input (optional, included in detail view)
+ Input interface{} `json:"input,omitempty"`
+}
+
+// ExecutionListResponse - paginated list response
+type ExecutionListResponse struct {
+ Data []*ExecutionResponse `json:"data"`
+ Total int `json:"total"`
+ Page int `json:"page"`
+ PageSize int `json:"pagesize"`
+}
+
+// ExecutionControlResponse - response for pause/resume/cancel
+type ExecutionControlResponse struct {
+ ExecutionID string `json:"execution_id"`
+ Action string `json:"action"` // paused | resumed | cancelled
+ Success bool `json:"success"`
+ Message string `json:"message,omitempty"`
+}
+
+// ==================== Trigger Types ====================
+
+// TriggerRequest - HTTP request to trigger robot execution
+type TriggerRequest struct {
+ // Trigger type: human | event | clock (defaults to human)
+ TriggerType string `json:"trigger_type,omitempty"`
+
+ // Human intervention fields
+ Action string `json:"action,omitempty"` // task.add, goal.adjust, etc.
+ Messages []MessageItem `json:"messages,omitempty"` // user's input
+
+ // Event fields
+ Source string `json:"source,omitempty"` // webhook | database
+ EventType string `json:"event_type,omitempty"` // lead.created, etc.
+ Data map[string]interface{} `json:"data,omitempty"` // event payload
+
+ // Executor mode (optional)
+ ExecutorMode string `json:"executor_mode,omitempty"` // standard | fast | careful
+
+ // i18n support
+ Locale string `json:"locale,omitempty"` // Locale for UI messages (e.g., "en", "zh")
+}
+
+// MessageItem - a single message in trigger request
+type MessageItem struct {
+ Role string `json:"role"` // user | assistant | system
+ Content string `json:"content"` // message text
+ Name string `json:"name,omitempty"` // optional name
+ FileID string `json:"file_id,omitempty"` // optional attachment
+}
+
+// TriggerResponse - response after triggering
+type TriggerResponse struct {
+ Accepted bool `json:"accepted"`
+ ExecutionID string `json:"execution_id,omitempty"`
+ Queued bool `json:"queued,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+// InterveneRequest - HTTP request for human intervention
+type InterveneRequest struct {
+ Action string `json:"action"` // task.add, goal.adjust, etc.
+ Messages []MessageItem `json:"messages,omitempty"` // user's input
+ PlanAt *time.Time `json:"plan_at,omitempty"` // schedule for later
+}
+
+// InterveneResponse - response after intervention
+type InterveneResponse struct {
+ Accepted bool `json:"accepted"`
+ ExecutionID string `json:"execution_id,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+// ==================== Execution Conversion Functions ====================
+
+// NewExecutionListResponse creates an ExecutionListResponse from api.ExecutionResult
+func NewExecutionListResponse(e *robotapi.ExecutionResult) *ExecutionListResponse {
+ if e == nil {
+ return nil
+ }
+
+ data := make([]*ExecutionResponse, 0, len(e.Data))
+ for _, exec := range e.Data {
+ data = append(data, NewExecutionResponseFromExecution(exec))
+ }
+
+ return &ExecutionListResponse{
+ Data: data,
+ Total: e.Total,
+ Page: e.Page,
+ PageSize: e.PageSize,
+ }
+}
+
+// NewExecutionResponseFromExecution converts types.Execution to ExecutionResponse
+func NewExecutionResponseFromExecution(exec *robottypes.Execution) *ExecutionResponse {
+ if exec == nil {
+ return nil
+ }
+
+ return &ExecutionResponse{
+ ID: exec.ID,
+ MemberID: exec.MemberID,
+ TeamID: exec.TeamID,
+ TriggerType: string(exec.TriggerType),
+ Status: string(exec.Status),
+ Phase: string(exec.Phase),
+ StartTime: exec.StartTime,
+ EndTime: exec.EndTime,
+ Error: exec.Error,
+ // UI display fields
+ Name: exec.Name,
+ CurrentTaskName: exec.CurrentTaskName,
+ // Phase outputs - include in detail view
+ Inspiration: exec.Inspiration,
+ Goals: exec.Goals,
+ Tasks: exec.Tasks,
+ Current: exec.Current,
+ Results: exec.Results,
+ Delivery: exec.Delivery,
+ Input: exec.Input,
+ }
+}
+
+// NewExecutionResponseBrief creates a brief ExecutionResponse (for list view)
+func NewExecutionResponseBrief(exec *robottypes.Execution) *ExecutionResponse {
+ if exec == nil {
+ return nil
+ }
+
+ // Calculate current state for progress bar display
+ // If exec.Current is nil but we have tasks, calculate progress from tasks
+ var current interface{}
+ if exec.Current != nil {
+ current = exec.Current
+ } else if len(exec.Tasks) > 0 {
+ // Calculate completed count from tasks
+ completedCount := 0
+ for _, task := range exec.Tasks {
+ if task.Status == robottypes.TaskCompleted ||
+ task.Status == robottypes.TaskFailed ||
+ task.Status == robottypes.TaskSkipped {
+ completedCount++
+ }
+ }
+ // Create a synthetic current state for progress display
+ current = map[string]interface{}{
+ "task_index": len(exec.Tasks),
+ "progress": fmt.Sprintf("%d/%d", completedCount, len(exec.Tasks)),
+ }
+ }
+
+ return &ExecutionResponse{
+ ID: exec.ID,
+ MemberID: exec.MemberID,
+ TeamID: exec.TeamID,
+ TriggerType: string(exec.TriggerType),
+ Status: string(exec.Status),
+ Phase: string(exec.Phase),
+ StartTime: exec.StartTime,
+ EndTime: exec.EndTime,
+ Error: exec.Error,
+ // UI display fields - include in list view for display
+ Name: exec.Name,
+ CurrentTaskName: exec.CurrentTaskName,
+ // Include Current for progress bar display in list view
+ Current: current,
+ // Omit other phase outputs for list view (inspiration, goals, tasks, results, delivery, input)
+ }
+}
diff --git a/openapi/tests/agent/robot_execution_test.go b/openapi/tests/agent/robot_execution_test.go
new file mode 100644
index 00000000..cb304c86
--- /dev/null
+++ b/openapi/tests/agent/robot_execution_test.go
@@ -0,0 +1,416 @@
+package openapi_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/yaoapp/yao/openapi"
+ "github.com/yaoapp/yao/openapi/tests/testutils"
+)
+
+// TestListExecutions tests the execution listing endpoint
+// GET /v1/agent/robots/:id/executions
+func TestListExecutions(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping execution tests in short mode")
+ }
+
+ serverURL := testutils.Prepare(t)
+ defer testutils.Clean()
+
+ baseURL := ""
+ if openapi.Server != nil && openapi.Server.Config != nil {
+ baseURL = openapi.Server.Config.BaseURL
+ }
+
+ // Register test client and get token
+ client := testutils.RegisterTestClient(t, "Execution List Test Client", []string{"https://localhost/callback"})
+ defer testutils.CleanupTestClient(t, client.ClientID)
+ tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+
+ // Create a test robot
+ robotID := fmt.Sprintf("test_exec_list_%d", time.Now().UnixNano())
+ createRobot(t, serverURL, baseURL, tokenInfo.AccessToken, robotID, "Execution List Robot")
+ defer deleteRobot(t, serverURL, baseURL, tokenInfo.AccessToken, robotID)
+
+ t.Run("ListExecutionsSuccess", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var response map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ require.NoError(t, err)
+
+ // Verify pagination fields exist
+ assert.Contains(t, response, "data")
+ assert.Contains(t, response, "page")
+ assert.Contains(t, response, "pagesize")
+ assert.Contains(t, response, "total")
+
+ // Verify execution items structure (if any executions exist)
+ data, ok := response["data"].([]interface{})
+ if ok && len(data) > 0 {
+ exec := data[0].(map[string]interface{})
+ // Basic fields should exist
+ assert.Contains(t, exec, "id")
+ assert.Contains(t, exec, "status")
+ assert.Contains(t, exec, "phase")
+ // UI display fields (may be empty string, but field should be present in response)
+ // These are new fields added for frontend display
+ t.Logf("Execution response fields: id=%v, name=%v, current_task_name=%v",
+ exec["id"], exec["name"], exec["current_task_name"])
+ }
+ })
+
+ t.Run("ListExecutionsWithPagination", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions?page=1&pagesize=5", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var response map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ require.NoError(t, err)
+
+ assert.Equal(t, float64(1), response["page"])
+ assert.Equal(t, float64(5), response["pagesize"])
+ })
+
+ t.Run("ListExecutionsWithStatusFilter", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions?status=completed", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var response map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ require.NoError(t, err)
+
+ assert.Contains(t, response, "data")
+ })
+
+ t.Run("ListExecutionsWithTriggerTypeFilter", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions?trigger_type=human", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+
+ var response map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ require.NoError(t, err)
+
+ assert.Contains(t, response, "data")
+ })
+
+ t.Run("ListExecutionsRobotNotFound", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/non_existent_robot/executions", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+
+ t.Run("ListExecutionsUnauthorized", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions", nil)
+ require.NoError(t, err)
+ // No Authorization header
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+// TestGetExecution tests the execution detail endpoint
+// GET /v1/agent/robots/:id/executions/:exec_id
+func TestGetExecution(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping execution tests in short mode")
+ }
+
+ serverURL := testutils.Prepare(t)
+ defer testutils.Clean()
+
+ baseURL := ""
+ if openapi.Server != nil && openapi.Server.Config != nil {
+ baseURL = openapi.Server.Config.BaseURL
+ }
+
+ // Register test client and get token
+ client := testutils.RegisterTestClient(t, "Execution Get Test Client", []string{"https://localhost/callback"})
+ defer testutils.CleanupTestClient(t, client.ClientID)
+ tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+
+ // Create a test robot
+ robotID := fmt.Sprintf("test_exec_get_%d", time.Now().UnixNano())
+ createRobot(t, serverURL, baseURL, tokenInfo.AccessToken, robotID, "Execution Get Robot")
+ defer deleteRobot(t, serverURL, baseURL, tokenInfo.AccessToken, robotID)
+
+ t.Run("GetExecutionNotFound", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions/non_existent_exec", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+
+ t.Run("GetExecutionRobotNotFound", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/non_existent_robot/executions/some_exec", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+}
+
+// TestExecutionControl tests the execution control endpoints
+// POST /v1/agent/robots/:id/executions/:exec_id/pause
+// POST /v1/agent/robots/:id/executions/:exec_id/resume
+// POST /v1/agent/robots/:id/executions/:exec_id/cancel
+func TestExecutionControl(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping execution control tests in short mode")
+ }
+
+ serverURL := testutils.Prepare(t)
+ defer testutils.Clean()
+
+ baseURL := ""
+ if openapi.Server != nil && openapi.Server.Config != nil {
+ baseURL = openapi.Server.Config.BaseURL
+ }
+
+ // Register test client and get token
+ client := testutils.RegisterTestClient(t, "Execution Control Test Client", []string{"https://localhost/callback"})
+ defer testutils.CleanupTestClient(t, client.ClientID)
+ tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+
+ // Create a test robot
+ robotID := fmt.Sprintf("test_exec_ctrl_%d", time.Now().UnixNano())
+ createRobot(t, serverURL, baseURL, tokenInfo.AccessToken, robotID, "Execution Control Robot")
+ defer deleteRobot(t, serverURL, baseURL, tokenInfo.AccessToken, robotID)
+
+ t.Run("PauseExecutionNotFound", func(t *testing.T) {
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/executions/non_existent_exec/pause", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ // Should return error (404 or 500 depending on implementation)
+ assert.True(t, resp.StatusCode >= 400, "Expected error status code")
+ })
+
+ t.Run("ResumeExecutionNotFound", func(t *testing.T) {
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/executions/non_existent_exec/resume", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.True(t, resp.StatusCode >= 400, "Expected error status code")
+ })
+
+ t.Run("CancelExecutionNotFound", func(t *testing.T) {
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/executions/non_existent_exec/cancel", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.True(t, resp.StatusCode >= 400, "Expected error status code")
+ })
+
+ t.Run("PauseExecutionRobotNotFound", func(t *testing.T) {
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/non_existent_robot/executions/some_exec/pause", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+
+ t.Run("ControlExecutionUnauthorized", func(t *testing.T) {
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/executions/some_exec/pause", nil)
+ require.NoError(t, err)
+ // No Authorization header
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+}
+
+// TestExecutionPermissions tests execution permission inheritance from robot
+func TestExecutionPermissions(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping execution permission tests in short mode")
+ }
+
+ serverURL := testutils.Prepare(t)
+ defer testutils.Clean()
+
+ baseURL := ""
+ if openapi.Server != nil && openapi.Server.Config != nil {
+ baseURL = openapi.Server.Config.BaseURL
+ }
+
+ // Register test client
+ client := testutils.RegisterTestClient(t, "Execution Permission Test Client", []string{"https://localhost/callback"})
+ defer testutils.CleanupTestClient(t, client.ClientID)
+
+ // Create User 1
+ token1 := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+ user1ID := token1.UserID
+
+ // Create User 2
+ token2 := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+
+ // User 1 creates a robot
+ robotID := fmt.Sprintf("test_exec_perm_%d", time.Now().UnixNano())
+ createRobotWithTeam(t, serverURL, baseURL, token1.AccessToken, robotID, "Permission Test Robot", user1ID)
+ defer deleteRobot(t, serverURL, baseURL, token1.AccessToken, robotID)
+
+ t.Run("OwnerCanListExecutions", func(t *testing.T) {
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+token1.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusOK, resp.StatusCode)
+ t.Logf("Owner (User 1) successfully listed executions for their robot")
+ })
+
+ t.Run("OtherUserExecutionAccess", func(t *testing.T) {
+ // User 2 attempts to list executions for User 1's robot
+ // With system:root scope this might succeed
+ req, err := http.NewRequest("GET", serverURL+baseURL+"/agent/robots/"+robotID+"/executions", nil)
+ require.NoError(t, err)
+
+ req.Header.Set("Authorization", "Bearer "+token2.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ t.Logf("User 2 execution list attempt status: %d (with system:root scope)", resp.StatusCode)
+ })
+}
+
+// ==================== Helper Functions ====================
+
+func createRobot(t *testing.T, serverURL, baseURL, token, robotID, displayName string) {
+ createData := map[string]interface{}{
+ "member_id": robotID,
+ "team_id": "test_team_001",
+ "display_name": displayName,
+ }
+
+ body, _ := json.Marshal(createData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ resp.Body.Close()
+}
+
+func createRobotWithTeam(t *testing.T, serverURL, baseURL, token, robotID, displayName, teamID string) {
+ createData := map[string]interface{}{
+ "member_id": robotID,
+ "team_id": teamID,
+ "display_name": displayName,
+ }
+
+ body, _ := json.Marshal(createData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ resp.Body.Close()
+}
+
+func deleteRobot(t *testing.T, serverURL, baseURL, token, robotID string) {
+ req, _ := http.NewRequest("DELETE", serverURL+baseURL+"/agent/robots/"+robotID, nil)
+ req.Header.Set("Authorization", "Bearer "+token)
+ resp, _ := http.DefaultClient.Do(req)
+ if resp != nil {
+ resp.Body.Close()
+ }
+}
diff --git a/openapi/tests/agent/robot_test.go b/openapi/tests/agent/robot_test.go
index 559c7252..92ad12aa 100644
--- a/openapi/tests/agent/robot_test.go
+++ b/openapi/tests/agent/robot_test.go
@@ -214,11 +214,11 @@ func TestCreateRobot(t *testing.T) {
createdRobotIDs = append(createdRobotIDs, robotID)
})
- t.Run("CreateRobotMissingRequiredFields", func(t *testing.T) {
- // Missing member_id
+ t.Run("CreateRobotMissingDisplayName", func(t *testing.T) {
+ // Missing display_name (the only required field)
createData := map[string]interface{}{
- "team_id": "test_team_001",
- "display_name": "Test Robot",
+ "team_id": "test_team_001",
+ // display_name is missing
}
body, _ := json.Marshal(createData)
@@ -235,6 +235,41 @@ func TestCreateRobot(t *testing.T) {
assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
})
+ t.Run("CreateRobotAutoGenerateMemberID", func(t *testing.T) {
+ // member_id is optional - should be auto-generated
+ createData := map[string]interface{}{
+ "team_id": "test_team_001",
+ "display_name": "Auto ID Robot",
+ }
+
+ body, _ := json.Marshal(createData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusCreated, resp.StatusCode)
+
+ var response map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ require.NoError(t, err)
+
+ // Verify member_id was auto-generated
+ memberID, ok := response["member_id"].(string)
+ assert.True(t, ok, "member_id should be a string")
+ assert.NotEmpty(t, memberID, "member_id should be auto-generated")
+ assert.Len(t, memberID, 12, "auto-generated member_id should be 12 digits")
+ t.Logf("Auto-generated member_id: %s", memberID)
+
+ // Cleanup
+ createdRobotIDs = append(createdRobotIDs, memberID)
+ })
+
t.Run("CreateRobotDuplicate", func(t *testing.T) {
robotID := fmt.Sprintf("test_robot_dup_%d", time.Now().UnixNano())
diff --git a/openapi/tests/agent/robot_trigger_test.go b/openapi/tests/agent/robot_trigger_test.go
new file mode 100644
index 00000000..910ae833
--- /dev/null
+++ b/openapi/tests/agent/robot_trigger_test.go
@@ -0,0 +1,502 @@
+package openapi_test
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "net/http"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+ "github.com/yaoapp/yao/openapi"
+ "github.com/yaoapp/yao/openapi/tests/testutils"
+)
+
+// TestTriggerRobot tests the robot trigger endpoint
+// POST /v1/agent/robots/:id/trigger
+func TestTriggerRobot(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping trigger tests in short mode (requires AI/manager)")
+ }
+
+ serverURL := testutils.Prepare(t)
+ defer testutils.Clean()
+
+ baseURL := ""
+ if openapi.Server != nil && openapi.Server.Config != nil {
+ baseURL = openapi.Server.Config.BaseURL
+ }
+
+ // Register test client and get token
+ client := testutils.RegisterTestClient(t, "Trigger Test Client", []string{"https://localhost/callback"})
+ defer testutils.CleanupTestClient(t, client.ClientID)
+ tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+
+ // Create a test robot
+ robotID := fmt.Sprintf("test_trigger_%d", time.Now().UnixNano())
+ createRobotForTrigger(t, serverURL, baseURL, tokenInfo.AccessToken, robotID, "Trigger Test Robot")
+ defer deleteRobotForTrigger(t, serverURL, baseURL, tokenInfo.AccessToken, robotID)
+
+ t.Run("TriggerRobotBasic", func(t *testing.T) {
+ triggerData := map[string]interface{}{
+ "trigger_type": "human",
+ "messages": []map[string]interface{}{
+ {
+ "role": "user",
+ "content": "Hello, please help me with a task",
+ },
+ },
+ }
+
+ body, _ := json.Marshal(triggerData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/trigger", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+ defer resp.Body.Close()
+
+ var response map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ require.NoError(t, err)
+
+ // If manager is not started, we get a 500 error (expected in test environment)
+ // In production with manager running, response should contain "accepted" field
+ if resp.StatusCode == http.StatusInternalServerError {
+ // Expected when robot manager is not started
+ assert.Contains(t, response, "error_description")
+ t.Logf("Trigger response (manager not started): status=%d, error=%v", resp.StatusCode, response["error_description"])
+ } else {
+ // Manager is running - verify accepted field
+ assert.Contains(t, response, "accepted")
+ t.Logf("Trigger response: status=%d, accepted=%v", resp.StatusCode, response["accepted"])
+ }
+ })
+
+ t.Run("TriggerRobotWithAction", func(t *testing.T) {
+ triggerData := map[string]interface{}{
+ "trigger_type": "human",
+ "action": "task.add",
+ "messages": []map[string]interface{}{
+ {
+ "role": "user",
+ "content": "Add a new task: Review quarterly report",
+ },
+ },
+ }
+
+ body, _ := json.Marshal(triggerData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/trigger", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ var response map[string]interface{}
+ json.NewDecoder(resp.Body).Decode(&response)
+
+ // If manager is not started, we get a 500 error (expected in test environment)
+ if resp.StatusCode == http.StatusInternalServerError {
+ assert.Contains(t, response, "error_description")
+ t.Logf("Trigger with action response (manager not started): status=%d", resp.StatusCode)
+ } else {
+ assert.Contains(t, response, "accepted")
+ t.Logf("Trigger with action response: status=%d", resp.StatusCode)
+ }
+ })
+
+ t.Run("TriggerRobotWithLocale", func(t *testing.T) {
+ // Test the new locale parameter for i18n support
+ triggerData := map[string]interface{}{
+ "trigger_type": "human",
+ "locale": "zh", // Chinese locale
+ "messages": []map[string]interface{}{
+ {
+ "role": "user",
+ "content": "请帮我分析销售数据",
+ },
+ },
+ }
+
+ body, _ := json.Marshal(triggerData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/trigger", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ var response map[string]interface{}
+ json.NewDecoder(resp.Body).Decode(&response)
+
+ // If manager is not started, we get a 500 error (expected in test environment)
+ if resp.StatusCode == http.StatusInternalServerError {
+ assert.Contains(t, response, "error_description")
+ t.Logf("Trigger with locale response (manager not started): status=%d", resp.StatusCode)
+ } else {
+ assert.Contains(t, response, "accepted")
+ t.Logf("Trigger with locale response: status=%d, accepted=%v", resp.StatusCode, response["accepted"])
+ }
+ })
+
+ t.Run("TriggerRobotNotFound", func(t *testing.T) {
+ triggerData := map[string]interface{}{
+ "trigger_type": "human",
+ }
+
+ body, _ := json.Marshal(triggerData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/non_existent_robot/trigger", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+
+ t.Run("TriggerRobotUnauthorized", func(t *testing.T) {
+ triggerData := map[string]interface{}{
+ "trigger_type": "human",
+ }
+
+ body, _ := json.Marshal(triggerData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/trigger", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ // No Authorization header
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("TriggerRobotInvalidBody", func(t *testing.T) {
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/trigger", bytes.NewBuffer([]byte("invalid json")))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+// TestInterveneRobot tests the robot intervention endpoint
+// POST /v1/agent/robots/:id/intervene
+func TestInterveneRobot(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping intervene tests in short mode (requires AI/manager)")
+ }
+
+ serverURL := testutils.Prepare(t)
+ defer testutils.Clean()
+
+ baseURL := ""
+ if openapi.Server != nil && openapi.Server.Config != nil {
+ baseURL = openapi.Server.Config.BaseURL
+ }
+
+ // Register test client and get token
+ client := testutils.RegisterTestClient(t, "Intervene Test Client", []string{"https://localhost/callback"})
+ defer testutils.CleanupTestClient(t, client.ClientID)
+ tokenInfo := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+
+ // Create a test robot
+ robotID := fmt.Sprintf("test_intervene_%d", time.Now().UnixNano())
+ createRobotForTrigger(t, serverURL, baseURL, tokenInfo.AccessToken, robotID, "Intervene Test Robot")
+ defer deleteRobotForTrigger(t, serverURL, baseURL, tokenInfo.AccessToken, robotID)
+
+ t.Run("InterveneRobotBasic", func(t *testing.T) {
+ interveneData := map[string]interface{}{
+ "action": "task.add",
+ "messages": []map[string]interface{}{
+ {
+ "role": "user",
+ "content": "Please add a high priority task",
+ },
+ },
+ }
+
+ body, _ := json.Marshal(interveneData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/intervene", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ require.NotNil(t, resp)
+ defer resp.Body.Close()
+
+ var response map[string]interface{}
+ err = json.NewDecoder(resp.Body).Decode(&response)
+ require.NoError(t, err)
+
+ // If manager is not started, we get a 500 error (expected in test environment)
+ if resp.StatusCode == http.StatusInternalServerError {
+ assert.Contains(t, response, "error_description")
+ t.Logf("Intervene response (manager not started): status=%d, error=%v", resp.StatusCode, response["error_description"])
+ } else {
+ assert.Contains(t, response, "accepted")
+ t.Logf("Intervene response: status=%d, accepted=%v", resp.StatusCode, response["accepted"])
+ }
+ })
+
+ t.Run("InterveneRobotMissingAction", func(t *testing.T) {
+ interveneData := map[string]interface{}{
+ "messages": []map[string]interface{}{
+ {
+ "role": "user",
+ "content": "Some message",
+ },
+ },
+ }
+
+ body, _ := json.Marshal(interveneData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/intervene", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ // Action is required
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+
+ t.Run("InterveneRobotNotFound", func(t *testing.T) {
+ interveneData := map[string]interface{}{
+ "action": "task.add",
+ }
+
+ body, _ := json.Marshal(interveneData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/non_existent_robot/intervene", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusNotFound, resp.StatusCode)
+ })
+
+ t.Run("InterveneRobotUnauthorized", func(t *testing.T) {
+ interveneData := map[string]interface{}{
+ "action": "task.add",
+ }
+
+ body, _ := json.Marshal(interveneData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/intervene", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ // No Authorization header
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusUnauthorized, resp.StatusCode)
+ })
+
+ t.Run("InterveneRobotInvalidBody", func(t *testing.T) {
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/intervene", bytes.NewBuffer([]byte("invalid json")))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ assert.Equal(t, http.StatusBadRequest, resp.StatusCode)
+ })
+}
+
+// TestTriggerPermissions tests trigger permission inheritance from robot
+func TestTriggerPermissions(t *testing.T) {
+ if testing.Short() {
+ t.Skip("Skipping trigger permission tests in short mode")
+ }
+
+ serverURL := testutils.Prepare(t)
+ defer testutils.Clean()
+
+ baseURL := ""
+ if openapi.Server != nil && openapi.Server.Config != nil {
+ baseURL = openapi.Server.Config.BaseURL
+ }
+
+ // Register test client
+ client := testutils.RegisterTestClient(t, "Trigger Permission Test Client", []string{"https://localhost/callback"})
+ defer testutils.CleanupTestClient(t, client.ClientID)
+
+ // Create User 1
+ token1 := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+ user1ID := token1.UserID
+
+ // Create User 2
+ token2 := testutils.ObtainAccessToken(t, serverURL, client.ClientID, client.ClientSecret, "https://localhost/callback", "openid profile")
+
+ // User 1 creates a robot
+ robotID := fmt.Sprintf("test_trig_perm_%d", time.Now().UnixNano())
+ createRobotWithTeamForTrigger(t, serverURL, baseURL, token1.AccessToken, robotID, "Trigger Perm Robot", user1ID)
+ defer deleteRobotForTrigger(t, serverURL, baseURL, token1.AccessToken, robotID)
+
+ t.Run("OwnerCanTrigger", func(t *testing.T) {
+ triggerData := map[string]interface{}{
+ "trigger_type": "human",
+ "messages": []map[string]interface{}{
+ {
+ "role": "user",
+ "content": "Owner triggering robot",
+ },
+ },
+ }
+
+ body, _ := json.Marshal(triggerData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/trigger", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token1.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ // Owner should be able to trigger (may fail at manager level with 500, but not 403 permission denied)
+ // 500 = manager not started (acceptable), 403 = permission denied (not acceptable)
+ assert.NotEqual(t, http.StatusForbidden, resp.StatusCode, "Owner should have permission to trigger")
+ t.Logf("Owner trigger attempt status: %d", resp.StatusCode)
+ })
+
+ t.Run("OwnerCanIntervene", func(t *testing.T) {
+ interveneData := map[string]interface{}{
+ "action": "task.add",
+ "messages": []map[string]interface{}{
+ {
+ "role": "user",
+ "content": "Owner intervention",
+ },
+ },
+ }
+
+ body, _ := json.Marshal(interveneData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/intervene", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token1.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ // 500 = manager not started (acceptable), 403 = permission denied (not acceptable)
+ assert.NotEqual(t, http.StatusForbidden, resp.StatusCode, "Owner should have permission to intervene")
+ t.Logf("Owner intervene attempt status: %d", resp.StatusCode)
+ })
+
+ t.Run("OtherUserTriggerAccess", func(t *testing.T) {
+ // User 2 attempts to trigger User 1's robot
+ // With system:root scope this might succeed
+ triggerData := map[string]interface{}{
+ "trigger_type": "human",
+ }
+
+ body, _ := json.Marshal(triggerData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots/"+robotID+"/trigger", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token2.AccessToken)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ defer resp.Body.Close()
+
+ t.Logf("User 2 trigger attempt status: %d (with system:root scope)", resp.StatusCode)
+ })
+}
+
+// ==================== Helper Functions ====================
+
+func createRobotForTrigger(t *testing.T, serverURL, baseURL, token, robotID, displayName string) {
+ createData := map[string]interface{}{
+ "member_id": robotID,
+ "team_id": "test_team_001",
+ "display_name": displayName,
+ }
+
+ body, _ := json.Marshal(createData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ resp.Body.Close()
+}
+
+func createRobotWithTeamForTrigger(t *testing.T, serverURL, baseURL, token, robotID, displayName, teamID string) {
+ createData := map[string]interface{}{
+ "member_id": robotID,
+ "team_id": teamID,
+ "display_name": displayName,
+ }
+
+ body, _ := json.Marshal(createData)
+ req, err := http.NewRequest("POST", serverURL+baseURL+"/agent/robots", bytes.NewBuffer(body))
+ require.NoError(t, err)
+
+ req.Header.Set("Content-Type", "application/json")
+ req.Header.Set("Authorization", "Bearer "+token)
+
+ resp, err := http.DefaultClient.Do(req)
+ require.NoError(t, err)
+ resp.Body.Close()
+}
+
+func deleteRobotForTrigger(t *testing.T, serverURL, baseURL, token, robotID string) {
+ req, _ := http.NewRequest("DELETE", serverURL+baseURL+"/agent/robots/"+robotID, nil)
+ req.Header.Set("Authorization", "Bearer "+token)
+ resp, _ := http.DefaultClient.Do(req)
+ if resp != nil {
+ resp.Body.Close()
+ }
+}
diff --git a/yao/models/agent/execution.mod.yao b/yao/models/agent/execution.mod.yao
index 288b345d..7aedf00f 100644
--- a/yao/models/agent/execution.mod.yao
+++ b/yao/models/agent/execution.mod.yao
@@ -94,6 +94,22 @@
"comment": "Error message if execution failed",
"nullable": true
},
+ {
+ "name": "name",
+ "type": "string",
+ "label": "Name",
+ "comment": "Execution title for UI display (updated by executor at goals phase)",
+ "length": 512,
+ "nullable": true
+ },
+ {
+ "name": "current_task_name",
+ "type": "string",
+ "label": "Current Task Name",
+ "comment": "Current task description for UI display (updated by executor at run phase)",
+ "length": 512,
+ "nullable": true
+ },
{
"name": "input",
"type": "json",