Refactor E2E Test Structure and Enhance Logging
- Updated the `TestE2EControlStop` function to improve execution status verification with a retry mechanism, accommodating potential delays. - Streamlined `TestE2EEventTriggerVariousEventTypes` to focus on a single event type, reducing CI execution time while maintaining coverage. - Enhanced logging and assertions for better clarity in test outcomes, improving overall test reliability.
This commit is contained in:
parent
0a38c2b592
commit
a0fa0e9eff
3 changed files with 2023 additions and 0 deletions
748
openapi/agent/robot/DESIGN.md
Normal file
748
openapi/agent/robot/DESIGN.md
Normal file
|
|
@ -0,0 +1,748 @@
|
|||
# Robot OpenAPI - Design Document
|
||||
|
||||
> Based on: `yao/agent/robot/` (Backend), `cui/packages/cui/pages/mission-control/` (Frontend)
|
||||
> Gap Analysis: `yao/openapi/agent/robot/GAPS.md`
|
||||
|
||||
## 1. Overview
|
||||
|
||||
### 1.1 Purpose
|
||||
|
||||
Provide HTTP REST API endpoints for Robot Agent management, designed to support the Mission Control frontend UI.
|
||||
|
||||
### 1.2 Implementation Strategy
|
||||
|
||||
> **Low-risk phases first. Medium-risk features (Chat API, SSE Event Bus) can be deferred.**
|
||||
|
||||
| Phase | Risk | Features | Frontend Fallback |
|
||||
|-------|------|----------|-------------------|
|
||||
| 1. Core CRUD | 🟢 Low | List, Get, Create, Update, Delete | - |
|
||||
| 2. Execution Management | 🟢 Low | List, Get, Control executions | - |
|
||||
| 3. Results & Activities | 🟢 Low | Deliverables, Activity feed | - |
|
||||
| 4. i18n | 🟢 Low | Locale parameter support | - |
|
||||
| 5. Chat API | 🟡 Medium (Deferred) | Multi-turn conversation | Single-submit mode |
|
||||
| 6. SSE Event Bus | 🟡 Medium (Deferred) | Real-time status streams | Polling every 3-5s |
|
||||
|
||||
### 1.3 Route Decision: `/v1/agent/robots`
|
||||
|
||||
**Analysis of existing `openapi/` route structure:**
|
||||
|
||||
| Package | Route | Description |
|
||||
|---------|-------|-------------|
|
||||
| `agent/` | `/v1/agent/assistants` | Assistant CRUD, info |
|
||||
| `chat/` | `/v1/chat/completions` | Chat completions |
|
||||
| `kb/` | `/v1/kb/collections` | Knowledge base |
|
||||
| `job/` | `/v1/job/jobs` | Job management |
|
||||
| `file/` | `/v1/file/*` | File operations |
|
||||
| `user/` | `/v1/user/*` | User management |
|
||||
| `team/` | `/v1/team/*` | Team management |
|
||||
|
||||
**Decision:** Put Robot routes under `/v1/agent/robots` because:
|
||||
|
||||
1. **Semantic Alignment**: Robot is a type of Agent (Autonomous Robot Agent), just like Assistant is a type of Agent
|
||||
2. **Existing Pattern**: `openapi/agent/` already handles `/v1/agent/assistants`
|
||||
3. **Logical Grouping**: Agent-related APIs grouped together
|
||||
4. **Consistent Hierarchy**: `/v1/agent/{type}` pattern
|
||||
|
||||
**Route Comparison:**
|
||||
|
||||
| Option | Path | Verdict |
|
||||
|--------|------|---------|
|
||||
| ❌ `/v1/robots` | New top-level namespace | Inconsistent with agent grouping |
|
||||
| ✅ `/v1/agent/robots` | Under agent namespace | Follows existing pattern |
|
||||
| ❌ `/v1/members?type=robot` | Reuse members | Less intuitive for operations |
|
||||
|
||||
### 1.4 Architecture
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Frontend (Mission Control) │
|
||||
│ cui/packages/cui/pages/mission-control/ │
|
||||
└───────────────────────────────┬─────────────────────────────────────────┘
|
||||
│ HTTP REST / SSE
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ OpenAPI Layer │
|
||||
│ yao/openapi/agent/ │
|
||||
│ - Routes: /v1/agent/assistants/* (existing) │
|
||||
│ - Routes: /v1/agent/robots/* (NEW) │
|
||||
│ - Auth: OAuth2 via Guard middleware │
|
||||
│ - SSE: Real-time updates │
|
||||
└───────────────────────────────┬─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Robot API Layer │
|
||||
│ yao/agent/robot/api/ │
|
||||
│ - Go functions: Get(), List(), Trigger(), etc. │
|
||||
│ - Business logic │
|
||||
└───────────────────────────────┬─────────────────────────────────────────┘
|
||||
│
|
||||
▼
|
||||
┌─────────────────────────────────────────────────────────────────────────┐
|
||||
│ Robot Core │
|
||||
│ yao/agent/robot/ │
|
||||
│ - Manager, Executor, Cache, Pool, Store │
|
||||
└─────────────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### 1.5 Design Principles
|
||||
|
||||
1. **Layered Architecture**: OpenAPI layer only handles HTTP concerns (routing, request parsing, response formatting). Business logic stays in `robot/api/`.
|
||||
2. **Consistent with Existing Patterns**: Follow `yao/openapi/agent/` conventions, extend existing agent package
|
||||
3. **Incremental Implementation**: Start with core CRUD, then add real-time features
|
||||
4. **Frontend-Backend Balance**: API design considers both frontend needs and backend capabilities
|
||||
|
||||
---
|
||||
|
||||
## 2. Differences Analysis
|
||||
|
||||
### 2.1 Frontend Expectations vs Backend Reality
|
||||
|
||||
| Feature | Frontend (API.md) | Backend (robot/api/) | Gap | Solution |
|
||||
|---------|-------------------|----------------------|-----|----------|
|
||||
| Robot List | `GET /v1/robots` with `name`, `description` | `List()` returns `types.Robot` | Field mapping needed | Map in OpenAPI layer |
|
||||
| Robot Detail | `GET /v1/robots/:id` with full `config` | `Get()` returns Robot + Config | Need format conversion | Map to frontend format |
|
||||
| Create Robot | POST with `work_mode` | Not implemented | New feature | Add `Create()` |
|
||||
| Update Robot | PUT with partial update | Not implemented | New feature | Add `Update()` |
|
||||
| Delete Robot | DELETE | Not implemented | New feature | Add `Remove()` |
|
||||
| Trigger | Immediate execution | `Trigger()` returns sync result | Works | Wrap with SSE events |
|
||||
| Intervene | Immediate intervention | `Intervene()` returns sync result | Works | Wrap with SSE events |
|
||||
| Multi-turn Chat | Chat before execute | Not implemented | **Deferred** | Frontend uses single-submit |
|
||||
| Results List | `/results` endpoint | No separate results API | New feature | Derive from executions |
|
||||
| Activities | `/activities` endpoint | No activities tracking | New feature | Derive from executions |
|
||||
| Real-time Stream | SSE `/stream` endpoints | No SSE support | **Deferred** | Frontend uses polling |
|
||||
| i18n | `?locale=` query param | No i18n support | New feature | Add locale handling |
|
||||
|
||||
### 2.2 Field Mapping (Backend → Frontend API)
|
||||
|
||||
The `__yao.member` model already has the necessary fields, with different names:
|
||||
|
||||
| Frontend API | Backend DB (`__yao.member`) | Backend Go (`types.Robot`) | Mapping |
|
||||
|--------------|----------------------------|---------------------------|---------|
|
||||
| `member_id` | `member_id` | `MemberID` | Direct |
|
||||
| `name` | `member_id` | `MemberID` | **Reuse** (slug-like identifier) |
|
||||
| `display_name` | `display_name` | `DisplayName` | Direct |
|
||||
| `description` | `bio` | Need to add `Bio` field | Map in OpenAPI layer |
|
||||
| `email` | `robot_email` | `RobotEmail` | Direct |
|
||||
|
||||
**Required Backend Changes:**
|
||||
1. Add `Bio` field to `types.Robot` struct
|
||||
2. Add `bio` to `cache/load.go` memberFields
|
||||
|
||||
### 2.3 Type Differences
|
||||
|
||||
| Frontend Type | Backend Type | Solution |
|
||||
|---------------|--------------|----------|
|
||||
| `RobotState.name` | `Robot.MemberID` | Map `member_id` to `name` |
|
||||
| `RobotState.description` | `Robot.Bio` (new) | Add field, map to `description` |
|
||||
| `Execution.name` | Not in `types.Execution` | Derive from goals or input in OpenAPI layer |
|
||||
| `Execution.current_task_name` | Not in `types.Execution` | Derive from current task in OpenAPI layer |
|
||||
| `ResultFile` | No equivalent | New type in OpenAPI layer (derive from delivery) |
|
||||
| `Activity` | No equivalent | New type in OpenAPI layer (derive from executions) |
|
||||
|
||||
---
|
||||
|
||||
## 3. API Endpoints
|
||||
|
||||
> **Base Path:** `/v1/agent/robots`
|
||||
|
||||
### 3.1 Robot Management
|
||||
|
||||
| Method | Path | Handler | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| GET | /v1/agent/robots | `ListRobots` | List all robots |
|
||||
| GET | /v1/agent/robots/:id | `GetRobot` | Get robot details |
|
||||
| POST | /v1/agent/robots | `CreateRobot` | Create robot |
|
||||
| PUT | /v1/agent/robots/:id | `UpdateRobot` | Update robot |
|
||||
| DELETE | /v1/agent/robots/:id | `DeleteRobot` | Delete robot |
|
||||
|
||||
### 3.2 Execution Management
|
||||
|
||||
| Method | Path | Handler | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| GET | /v1/agent/robots/:id/executions | `ListExecutions` | List executions |
|
||||
| GET | /v1/agent/robots/:id/executions/:exec_id | `GetExecution` | Get execution detail |
|
||||
| POST | /v1/agent/robots/:id/trigger | `TriggerRobot` | Trigger execution (SSE) |
|
||||
| POST | /v1/agent/robots/:id/intervene | `InterveneRobot` | Intervene execution (SSE) |
|
||||
| POST | /v1/agent/robots/:id/executions/:exec_id/pause | `PauseExecution` | Pause execution |
|
||||
| POST | /v1/agent/robots/:id/executions/:exec_id/resume | `ResumeExecution` | Resume execution |
|
||||
| POST | /v1/agent/robots/:id/executions/:exec_id/cancel | `CancelExecution` | Cancel execution |
|
||||
| POST | /v1/agent/robots/:id/executions/:exec_id/retry | `RetryExecution` | Retry execution |
|
||||
|
||||
### 3.3 Results Management
|
||||
|
||||
| Method | Path | Handler | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| GET | /v1/agent/robots/:id/results | `ListResults` | List deliverables |
|
||||
| GET | /v1/agent/robots/:id/results/:result_id | `GetResult` | Get deliverable detail |
|
||||
|
||||
### 3.4 Activities & Real-time
|
||||
|
||||
| Method | Path | Handler | Description |
|
||||
|--------|------|---------|-------------|
|
||||
| GET | /v1/agent/robots/activities | `ListActivities` | List recent activities |
|
||||
| GET | /v1/agent/robots/stream | `StreamRobots` | Robot status SSE |
|
||||
| GET | /v1/agent/robots/:id/executions/:exec_id/stream | `StreamExecution` | Execution progress SSE |
|
||||
|
||||
---
|
||||
|
||||
## 4. Response Types
|
||||
|
||||
### 4.1 RobotResponse (for list and detail)
|
||||
|
||||
```go
|
||||
// RobotResponse - formatted robot for API response
|
||||
// Maps backend fields to frontend expected format
|
||||
type RobotResponse struct {
|
||||
MemberID string `json:"member_id"`
|
||||
TeamID string `json:"team_id"`
|
||||
Name string `json:"name"` // From Robot.MemberID (slug-like identifier)
|
||||
DisplayName string `json:"display_name"` // From Robot.DisplayName
|
||||
Description string `json:"description,omitempty"` // From Robot.Bio
|
||||
Status string `json:"status"` // idle | working | paused | error | maintenance
|
||||
Running int `json:"running"` // Current running count
|
||||
MaxRunning int `json:"max_running"` // From Config.Quota.Max
|
||||
LastRun *string `json:"last_run,omitempty"` // ISO timestamp
|
||||
NextRun *string `json:"next_run,omitempty"` // ISO timestamp
|
||||
RunningIDs []string `json:"running_ids,omitempty"` // Execution IDs
|
||||
Config *ConfigResponse `json:"config,omitempty"` // Full config (for detail)
|
||||
}
|
||||
|
||||
// NewRobotResponse converts backend Robot to API response
|
||||
func NewRobotResponse(robot *types.Robot) *RobotResponse {
|
||||
return &RobotResponse{
|
||||
MemberID: robot.MemberID,
|
||||
TeamID: robot.TeamID,
|
||||
Name: robot.MemberID, // Use MemberID as unique identifier
|
||||
DisplayName: robot.DisplayName,
|
||||
Description: robot.Bio, // Map Bio to Description
|
||||
Status: string(robot.Status),
|
||||
// ... other fields
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 4.2 ConfigResponse (robot config)
|
||||
|
||||
```go
|
||||
// ConfigResponse - formatted config for API response
|
||||
type ConfigResponse struct {
|
||||
Identity *IdentityConfig `json:"identity,omitempty"`
|
||||
Clock *ClockConfig `json:"clock,omitempty"`
|
||||
Events []EventConfig `json:"events,omitempty"`
|
||||
Quota *QuotaConfig `json:"quota,omitempty"`
|
||||
Resources *ResourcesConfig `json:"resources,omitempty"`
|
||||
Delivery *DeliveryConfig `json:"delivery,omitempty"`
|
||||
Triggers *TriggersConfig `json:"triggers,omitempty"`
|
||||
Learn *LearnConfig `json:"learn,omitempty"`
|
||||
Executor *ExecutorConfig `json:"executor,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 ExecutionResponse
|
||||
|
||||
```go
|
||||
// ExecutionResponse - formatted execution for API response
|
||||
type ExecutionResponse struct {
|
||||
ID string `json:"id"`
|
||||
MemberID string `json:"member_id"`
|
||||
TeamID string `json:"team_id"`
|
||||
TriggerType string `json:"trigger_type"`
|
||||
StartTime string `json:"start_time"`
|
||||
EndTime *string `json:"end_time,omitempty"`
|
||||
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
|
||||
Goals *GoalsResponse `json:"goals,omitempty"`
|
||||
Tasks []TaskResponse `json:"tasks,omitempty"`
|
||||
Current *CurrentState `json:"current,omitempty"`
|
||||
Delivery *DeliveryResult `json:"delivery,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### 4.4 ResultResponse
|
||||
|
||||
```go
|
||||
// ResultResponse - deliverable file for Results tab
|
||||
type ResultResponse struct {
|
||||
ID string `json:"id"`
|
||||
MemberID string `json:"member_id"`
|
||||
ExecutionID string `json:"execution_id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"` // pdf, xlsx, csv, json, md
|
||||
Size int64 `json:"size"` // bytes
|
||||
CreatedAt string `json:"created_at"`
|
||||
TriggerType string `json:"trigger_type,omitempty"`
|
||||
ExecutionName string `json:"execution_name,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### 4.5 ActivityResponse
|
||||
|
||||
```go
|
||||
// ActivityResponse - activity item
|
||||
type ActivityResponse struct {
|
||||
ID string `json:"id"`
|
||||
Type string `json:"type"` // completed | file | error | started | paused
|
||||
MemberID string `json:"member_id"`
|
||||
RobotName string `json:"robot_name"` // Localized
|
||||
Title string `json:"title"` // Localized
|
||||
Description string `json:"description,omitempty"` // Localized
|
||||
FileID string `json:"file_id,omitempty"`
|
||||
Timestamp string `json:"timestamp"`
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. Request Types
|
||||
|
||||
### 5.1 CreateRobotRequest
|
||||
|
||||
```go
|
||||
// CreateRobotRequest - create robot request
|
||||
type CreateRobotRequest struct {
|
||||
Locale string `json:"locale,omitempty"` // zh-CN | en-US
|
||||
Name string `json:"name"` // Unique identifier
|
||||
DisplayName string `json:"display_name"` // Display name
|
||||
Email string `json:"email,omitempty"` // Robot email
|
||||
ManagerID string `json:"manager_id,omitempty"` // Manager user ID
|
||||
WorkMode string `json:"work_mode"` // autonomous | on-demand
|
||||
Identity *IdentityConfig `json:"identity"`
|
||||
Resources *ResourcesConfig `json:"resources,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### 5.2 UpdateRobotRequest
|
||||
|
||||
```go
|
||||
// UpdateRobotRequest - update robot request
|
||||
type UpdateRobotRequest struct {
|
||||
Locale string `json:"locale,omitempty"`
|
||||
DisplayName *string `json:"display_name,omitempty"`
|
||||
Config *ConfigResponse `json:"config,omitempty"` // Partial update supported
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 TriggerRequest (SSE)
|
||||
|
||||
```go
|
||||
// TriggerRequest - trigger robot execution
|
||||
type TriggerRequest struct {
|
||||
Locale string `json:"locale,omitempty"`
|
||||
Messages []Message `json:"messages"`
|
||||
Attachments []Attachment `json:"attachments,omitempty"`
|
||||
}
|
||||
|
||||
// Message - chat message
|
||||
type Message struct {
|
||||
Role string `json:"role"` // user | assistant
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
// Attachment - file attachment
|
||||
type Attachment struct {
|
||||
File string `json:"file"` // __yao.attachment://fileID
|
||||
Name string `json:"name,omitempty"`
|
||||
}
|
||||
```
|
||||
|
||||
### 5.4 InterveneRequest (SSE)
|
||||
|
||||
```go
|
||||
// InterveneRequest - intervene during execution
|
||||
type InterveneRequest struct {
|
||||
Locale string `json:"locale,omitempty"`
|
||||
ExecutionID string `json:"execution_id"`
|
||||
Action string `json:"action"` // task.add | goal.adjust | instruct
|
||||
Messages []Message `json:"messages"`
|
||||
Priority string `json:"priority,omitempty"` // high | normal | low
|
||||
Position string `json:"position,omitempty"` // first | last | next | at
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Deferred Features
|
||||
|
||||
### 6.1 Multi-turn Chat API (Phase 5 - Deferred)
|
||||
|
||||
> **Risk Level:** 🟡 Medium - Requires new stateful component
|
||||
> **Frontend Fallback:** Single-submit mode (user input → immediate execution)
|
||||
|
||||
The frontend `ChatDrawer` component expects multi-turn conversation before execution:
|
||||
|
||||
```
|
||||
User: "Help me analyze competitor pricing"
|
||||
↓
|
||||
Robot: "Got it. Which competitors?"
|
||||
↓
|
||||
User: "Focus on Company A and B"
|
||||
↓
|
||||
Robot: "Understood. Ready to start?"
|
||||
↓
|
||||
User clicks [Confirm] → Execution starts
|
||||
```
|
||||
|
||||
**Current backend behavior:** `Trigger()` immediately submits to execution pool.
|
||||
|
||||
**Deferred implementation:**
|
||||
```
|
||||
POST /v1/agent/robots/:id/chat
|
||||
{
|
||||
"conversation_id": "conv_001", // For continuing conversation
|
||||
"messages": [{ "role": "user", "content": "..." }]
|
||||
}
|
||||
|
||||
Response (SSE):
|
||||
event: message
|
||||
data: {"role": "assistant", "content": "..."}
|
||||
|
||||
event: state
|
||||
data: {"conversation_id": "conv_001", "ready_to_execute": false}
|
||||
```
|
||||
|
||||
**For now:** Frontend can skip chat flow, directly call `/trigger` with user message.
|
||||
|
||||
### 6.2 SSE Event Bus (Phase 6 - Deferred)
|
||||
|
||||
> **Risk Level:** 🟡 Medium - Requires modification of executor/manager
|
||||
> **Frontend Fallback:** Polling (GET /executions every 3-5 seconds)
|
||||
|
||||
Real-time status updates via SSE require an event bus integrated with:
|
||||
- Manager (robot status changes)
|
||||
- Executor (execution progress)
|
||||
|
||||
**For now:** Frontend uses polling to refresh status.
|
||||
|
||||
---
|
||||
|
||||
## 7. SSE Events
|
||||
|
||||
### 7.1 Trigger/Intervene SSE Events
|
||||
|
||||
```
|
||||
event: received
|
||||
data: {"message": "Task received, creating execution..."}
|
||||
|
||||
event: execution
|
||||
data: {"execution_id": "exec_002", "status": "pending"}
|
||||
|
||||
event: message
|
||||
data: {"role": "assistant", "content": "好的,我开始处理..."}
|
||||
|
||||
event: phase
|
||||
data: {"phase": "goals", "message": "正在生成目标..."}
|
||||
|
||||
event: complete
|
||||
data: {"execution_id": "exec_002", "status": "running"}
|
||||
|
||||
event: error
|
||||
data: {"error": "Something went wrong"}
|
||||
```
|
||||
|
||||
### 7.2 Robot Stream SSE Events (Phase 6 - Deferred)
|
||||
|
||||
```
|
||||
event: robot_status
|
||||
data: {"member_id": "robot_001", "status": "working", "running": 1}
|
||||
|
||||
event: execution_start
|
||||
data: {"member_id": "robot_001", "execution_id": "exec_001", "name": "每日报表生成"}
|
||||
|
||||
event: execution_complete
|
||||
data: {"member_id": "robot_001", "execution_id": "exec_001", "status": "completed"}
|
||||
|
||||
event: activity
|
||||
data: {"id": "act_001", "type": "completed", "member_id": "robot_001", ...}
|
||||
```
|
||||
|
||||
### 7.3 Execution Stream SSE Events (Phase 6 - Deferred)
|
||||
|
||||
```
|
||||
event: phase
|
||||
data: {"phase": "tasks", "progress": "2/5 tasks"}
|
||||
|
||||
event: task_start
|
||||
data: {"task_id": "task_002", "order": 2}
|
||||
|
||||
event: task_complete
|
||||
data: {"task_id": "task_002", "status": "completed"}
|
||||
|
||||
event: message
|
||||
data: {"role": "assistant", "content": "正在分析数据..."}
|
||||
|
||||
event: delivery
|
||||
data: {"summary": "...", "attachments": [...]}
|
||||
|
||||
event: complete
|
||||
data: {"status": "completed"}
|
||||
|
||||
event: error
|
||||
data: {"error": "Something went wrong", "phase": "run"}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. i18n Support
|
||||
|
||||
### 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`
|
||||
|
||||
### 8.2 Localized Fields
|
||||
|
||||
| Response Type | Localized Fields |
|
||||
|---------------|------------------|
|
||||
| RobotResponse | display_name, description |
|
||||
| ExecutionResponse | name, current_task_name |
|
||||
| TaskResponse | (none - tasks use executor_id) |
|
||||
| ResultResponse | name, execution_name |
|
||||
| ActivityResponse | robot_name, title, description |
|
||||
|
||||
---
|
||||
|
||||
## 9. Authentication & Authorization
|
||||
|
||||
### 9.1 Guard Middleware
|
||||
|
||||
All endpoints require OAuth2 authentication via `oauth.Guard` middleware.
|
||||
|
||||
```go
|
||||
// In router registration
|
||||
router.Use(oauth.Guard())
|
||||
```
|
||||
|
||||
### 9.2 Permission Checks
|
||||
|
||||
| Endpoint | Required Scope |
|
||||
|----------|----------------|
|
||||
| GET /robots | `robots:read` |
|
||||
| POST /robots | `robots:write` |
|
||||
| PUT/DELETE /robots/:id | `robots:write` + ownership check |
|
||||
| Trigger/Intervene | `robots:execute` |
|
||||
| Stream endpoints | `robots:read` |
|
||||
|
||||
### 9.3 Team Isolation
|
||||
|
||||
Robots are team-scoped. Users can only access robots in their team.
|
||||
|
||||
```go
|
||||
func checkTeamAccess(ctx context.Context, memberID string) error {
|
||||
auth := oauth.GetAuthorized(ctx)
|
||||
robot, _ := robotapi.Get(memberID)
|
||||
if robot.TeamID != auth.TeamID {
|
||||
return errors.New("access denied")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. File Structure
|
||||
|
||||
**Decision: Sub-package under `openapi/agent/`**
|
||||
|
||||
Robot logic is complex enough to warrant its own package. This keeps code organized and follows the pattern used by other complex modules.
|
||||
|
||||
```
|
||||
yao/openapi/agent/
|
||||
├── agent.go # Main route registration (MODIFY: add robot.Attach)
|
||||
├── assistant.go # Assistant handlers (existing)
|
||||
├── filter.go # Query filtering (existing)
|
||||
├── models.go # LLM models (existing)
|
||||
├── types.go # Types (existing)
|
||||
│
|
||||
└── robot/ # Robot sub-package (NEW)
|
||||
├── DESIGN.md # This document ✅
|
||||
├── TODO.md # Implementation plan ✅
|
||||
│
|
||||
├── robot.go # Route registration (Attach function)
|
||||
├── types.go # Request/Response types
|
||||
│
|
||||
├── list.go # GET /v1/agent/robots
|
||||
├── detail.go # GET/POST/PUT/DELETE /v1/agent/robots/:id
|
||||
│
|
||||
├── execution.go # Execution list/detail/control handlers
|
||||
├── trigger.go # POST /trigger, POST /intervene (SSE)
|
||||
│
|
||||
├── results.go # GET /results, GET /results/:id
|
||||
├── activities.go # GET /activities
|
||||
│
|
||||
├── stream.go # GET /stream, GET /executions/:id/stream (SSE)
|
||||
│
|
||||
├── filter.go # Query param parsing helpers
|
||||
└── utils.go # Locale, time formatting utilities
|
||||
```
|
||||
|
||||
**Route Registration (in `openapi/agent/agent.go`):**
|
||||
|
||||
```go
|
||||
import "github.com/yaoapp/yao/openapi/agent/robot"
|
||||
|
||||
func Attach(group *gin.RouterGroup, oauth types.OAuth) {
|
||||
group.Use(oauth.Guard)
|
||||
|
||||
// Assistant routes (existing)
|
||||
group.GET("/assistants", ListAssistants)
|
||||
group.POST("/assistants", CreateAssistant)
|
||||
// ...
|
||||
|
||||
// Robot routes (NEW)
|
||||
robot.Attach(group.Group("/robots"), oauth)
|
||||
}
|
||||
```
|
||||
|
||||
**Robot Route Registration (`robot/robot.go`):**
|
||||
|
||||
```go
|
||||
package robot
|
||||
|
||||
func Attach(group *gin.RouterGroup, oauth types.OAuth) {
|
||||
// Robot CRUD
|
||||
group.GET("", ListRobots)
|
||||
group.POST("", CreateRobot)
|
||||
group.GET("/:id", GetRobot)
|
||||
group.PUT("/:id", UpdateRobot)
|
||||
group.DELETE("/:id", DeleteRobot)
|
||||
|
||||
// Activities (before :id to avoid conflict)
|
||||
group.GET("/activities", ListActivities)
|
||||
group.GET("/stream", StreamRobots)
|
||||
|
||||
// Execution management
|
||||
group.GET("/:id/executions", ListExecutions)
|
||||
group.GET("/:id/executions/:exec_id", GetExecution)
|
||||
group.GET("/:id/executions/:exec_id/stream", StreamExecution)
|
||||
group.POST("/:id/executions/:exec_id/pause", PauseExecution)
|
||||
group.POST("/:id/executions/:exec_id/resume", ResumeExecution)
|
||||
group.POST("/:id/executions/:exec_id/cancel", CancelExecution)
|
||||
group.POST("/:id/executions/:exec_id/retry", RetryExecution)
|
||||
|
||||
// Trigger & Intervene (SSE)
|
||||
group.POST("/:id/trigger", TriggerRobot)
|
||||
group.POST("/:id/intervene", InterveneRobot)
|
||||
|
||||
// Results
|
||||
group.GET("/:id/results", ListResults)
|
||||
group.GET("/:id/results/:result_id", GetResult)
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. Error Handling
|
||||
|
||||
### 11.1 Error Response Format
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "ROBOT_NOT_FOUND",
|
||||
"message": "Robot not found",
|
||||
"details": {
|
||||
"member_id": "robot_001"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 11.2 Error Codes
|
||||
|
||||
| Code | HTTP Status | Description |
|
||||
|------|-------------|-------------|
|
||||
| ROBOT_NOT_FOUND | 404 | Robot does not exist |
|
||||
| EXECUTION_NOT_FOUND | 404 | Execution does not exist |
|
||||
| ROBOT_BUSY | 409 | Robot at max capacity |
|
||||
| TRIGGER_DISABLED | 403 | Trigger type disabled |
|
||||
| EXECUTION_NOT_RUNNING | 400 | Cannot pause/resume non-running execution |
|
||||
| INVALID_REQUEST | 400 | Request validation failed |
|
||||
| UNAUTHORIZED | 401 | Not authenticated |
|
||||
| FORBIDDEN | 403 | No permission |
|
||||
|
||||
---
|
||||
|
||||
## 12. Implementation Notes
|
||||
|
||||
### 12.1 Backend API Extension
|
||||
|
||||
The existing `robot/api/` package needs these additions:
|
||||
|
||||
1. **Robot CRUD**: `Create()`, `Update()`, `Remove()` functions
|
||||
2. **Results API**: `ListResults()`, `GetResult()` functions
|
||||
3. **Activities API**: `ListActivities()` function
|
||||
4. **Localization**: Add `Locale` parameter support
|
||||
|
||||
### 12.2 Store Extension
|
||||
|
||||
The `robot/store/` package needs:
|
||||
|
||||
1. **Results Store**: Store and query deliverable files
|
||||
2. **Activities Store**: Store and query activities (or derive from job logs)
|
||||
|
||||
### 12.3 SSE Implementation
|
||||
|
||||
Use standard Go SSE pattern:
|
||||
|
||||
```go
|
||||
func streamHandler(w http.ResponseWriter, r *http.Request) {
|
||||
w.Header().Set("Content-Type", "text/event-stream")
|
||||
w.Header().Set("Cache-Control", "no-cache")
|
||||
w.Header().Set("Connection", "keep-alive")
|
||||
|
||||
flusher, _ := w.(http.Flusher)
|
||||
|
||||
for event := range events {
|
||||
fmt.Fprintf(w, "event: %s\ndata: %s\n\n", event.Type, event.Data)
|
||||
flusher.Flush()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 12.4 Localization Strategy
|
||||
|
||||
- Store display names in `__yao.member.display_name` (single language) initially
|
||||
- Future: Add `display_name_cn`, `display_name_en` or use JSON `{"en": "...", "cn": "..."}`
|
||||
- Execution names derived from goals or input message
|
||||
- Activities derive titles from execution data
|
||||
|
||||
---
|
||||
|
||||
## 13. API Base Path Decision
|
||||
|
||||
Based on analysis of existing `openapi/` structure:
|
||||
|
||||
| Option | Path | Pros | Cons |
|
||||
|--------|------|------|------|
|
||||
| ❌ A | `/v1/robots` | Shorter path | New namespace, inconsistent |
|
||||
| ✅ B | `/v1/agent/robots` | Groups with agent APIs, consistent | Longer path |
|
||||
| ❌ C | `/v1/members?type=robot` | Uses existing members | Less intuitive |
|
||||
|
||||
**Decision**: Use `/v1/agent/robots` as base path.
|
||||
|
||||
**Rationale:**
|
||||
1. `openapi/agent/` already exists with `/v1/agent/assistants`
|
||||
2. Robot is conceptually an Agent type (Autonomous Robot Agent)
|
||||
3. Follows the established pattern: `/v1/agent/{agent-type}`
|
||||
4. Keeps agent-related APIs logically grouped
|
||||
|
||||
**Frontend Impact:**
|
||||
- Update `cui/packages/cui/pages/mission-control/API.md` base path from `/v1/robots` to `/v1/agent/robots`
|
||||
- Minimal code change (just update base URL constant)
|
||||
|
||||
---
|
||||
|
||||
## 14. References
|
||||
|
||||
- Frontend API Requirements: `cui/packages/cui/pages/mission-control/API.md`
|
||||
- Backend Robot Design: `yao/agent/robot/DESIGN.md`
|
||||
- Backend Technical Spec: `yao/agent/robot/TECHNICAL.md`
|
||||
- Existing OpenAPI Patterns: `yao/openapi/kb/`, `yao/openapi/chat/`
|
||||
721
openapi/agent/robot/GAPS.md
Normal file
721
openapi/agent/robot/GAPS.md
Normal file
|
|
@ -0,0 +1,721 @@
|
|||
# Robot OpenAPI - Gap Analysis
|
||||
|
||||
> This document analyzes the gaps between existing backend implementation and frontend API requirements.
|
||||
> Generated from reviewing: `yao/agent/robot/`, `yao/openapi/agent/`, `cui/packages/cui/pages/mission-control/`
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
| Category | Risk | Status | Items to Implement |
|
||||
|----------|------|--------|-------------------|
|
||||
| Backend Types | 🟢 Low | 🟡 Partial | 1 field to add (`Bio`), 2 fields for Execution |
|
||||
| Backend Cache | 🟢 Low | 🟡 Partial | Add `bio` to memberFields in `cache/load.go` |
|
||||
| Backend API | 🟢 Low | 🟡 Partial | 7 functions missing (CRUD + Results + Activities) |
|
||||
| OpenAPI Layer | 🟢 Low | ⬜ New | 19 endpoints, response type mapping |
|
||||
| i18n | 🟢 Low | ⬜ New | Locale parameter support |
|
||||
| **Chat API** | 🟡 Medium | ⬜ Deferred | Multi-turn conversation (frontend fallback: single-submit) |
|
||||
| **SSE Infrastructure** | 🟡 Medium | ⬜ Deferred | Event bus + SSE handlers (frontend fallback: polling) |
|
||||
|
||||
### Key Field Mapping (Backend → Frontend)
|
||||
|
||||
| Frontend API | Backend DB (`__yao.member`) | Backend Go (`types.Robot`) |
|
||||
|--------------|----------------------------|---------------------------|
|
||||
| `name` | `member_id` | `MemberID` |
|
||||
| `display_name` | `display_name` | `DisplayName` |
|
||||
| `description` | `bio` | Need to add `Bio` field |
|
||||
| `email` | `robot_email` | `RobotEmail` |
|
||||
|
||||
---
|
||||
|
||||
## 1. Backend Types Gaps (`yao/agent/robot/types/`)
|
||||
|
||||
### 1.1 Field Mapping (Backend → Frontend API)
|
||||
|
||||
The `__yao.member` model already has the necessary fields, but with different names:
|
||||
|
||||
| Frontend API Field | Backend DB Field | Status | Notes |
|
||||
|-------------------|------------------|--------|-------|
|
||||
| `member_id` | `member_id` | ✅ Exists | Global unique identifier |
|
||||
| `name` | `member_id` | ✅ **Reuse** | Frontend expects a slug like `sales-analyst`, can use `member_id` |
|
||||
| `display_name` | `display_name` | ✅ Exists | Localized display name |
|
||||
| `description` | `bio` | ✅ Exists | `bio` field in `__yao.member` is the robot description |
|
||||
|
||||
**Backend Robot struct (`types/robot.go`):**
|
||||
```go
|
||||
type Robot struct {
|
||||
MemberID string `json:"member_id"` // ✅ Exists
|
||||
TeamID string `json:"team_id"` // ✅ Exists
|
||||
DisplayName string `json:"display_name"` // ✅ Exists
|
||||
SystemPrompt string `json:"system_prompt"`// ✅ Exists
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
**Missing fields to add to Robot struct:**
|
||||
```go
|
||||
type Robot struct {
|
||||
// ... existing fields ...
|
||||
Bio string `json:"bio"` // NEW: from __yao.member.bio (robot description)
|
||||
}
|
||||
```
|
||||
|
||||
**OpenAPI Response Mapping:**
|
||||
```go
|
||||
// In OpenAPI layer, map backend fields to frontend expected format
|
||||
type RobotResponse struct {
|
||||
MemberID string `json:"member_id"`
|
||||
Name string `json:"name"` // Use MemberID as unique slug
|
||||
DisplayName string `json:"display_name"`
|
||||
Description string `json:"description"` // Map from Robot.Bio
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 Cache/Load Update Needed
|
||||
|
||||
Update `cache/load.go` to fetch `bio` field:
|
||||
|
||||
```go
|
||||
var memberFields = []interface{}{
|
||||
"id",
|
||||
"member_id",
|
||||
"team_id",
|
||||
"display_name",
|
||||
"bio", // ADD THIS
|
||||
"system_prompt",
|
||||
"robot_status",
|
||||
"autonomous_mode",
|
||||
"robot_config",
|
||||
"robot_email", // Already there
|
||||
}
|
||||
```
|
||||
|
||||
### 1.3 Missing Fields in `Execution` struct
|
||||
|
||||
| Field | Type | Location | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `Name` | `string` | `types/robot.go` | Derived from goals or human input, for UI display |
|
||||
| `CurrentTaskName` | `string` | `types/robot.go` | What the agent is doing RIGHT NOW |
|
||||
|
||||
**Required (add to Execution struct):**
|
||||
```go
|
||||
type Execution struct {
|
||||
// ... existing fields ...
|
||||
Name string `json:"name,omitempty"` // NEW: execution name for UI
|
||||
CurrentTaskName string `json:"current_task_name,omitempty"` // NEW: current task description
|
||||
}
|
||||
```
|
||||
|
||||
> **Note:** These can be derived in the OpenAPI layer from existing fields:
|
||||
> - `Name`: Derive from `Goals.Content` first line or `Input.Messages[0].Content`
|
||||
> - `CurrentTaskName`: Derive from `Current.Task` executor info or progress
|
||||
|
||||
### 1.4 New Types Needed
|
||||
|
||||
#### Activity Type (for Activity API)
|
||||
|
||||
> **Note:** Activity can be derived from execution history without new storage.
|
||||
> These types go in OpenAPI response layer, not core types.
|
||||
|
||||
```go
|
||||
// openapi/agent/robot/types.go (API response types)
|
||||
|
||||
// ActivityType - activity type enum
|
||||
type ActivityType string
|
||||
|
||||
const (
|
||||
ActivityCompleted ActivityType = "completed"
|
||||
ActivityFile ActivityType = "file"
|
||||
ActivityError ActivityType = "error"
|
||||
ActivityStarted ActivityType = "started"
|
||||
ActivityPaused ActivityType = "paused"
|
||||
)
|
||||
|
||||
// ActivityResponse - activity item for UI
|
||||
type ActivityResponse struct {
|
||||
ID string `json:"id"`
|
||||
Type ActivityType `json:"type"`
|
||||
MemberID string `json:"member_id"`
|
||||
RobotName string `json:"robot_name"` // Localized
|
||||
Title string `json:"title"` // Localized
|
||||
Description string `json:"description,omitempty"` // Localized
|
||||
FileID string `json:"file_id,omitempty"`
|
||||
Timestamp string `json:"timestamp"` // ISO format
|
||||
}
|
||||
```
|
||||
|
||||
#### ResultFile Type (for Results API)
|
||||
|
||||
> **Note:** Results are derived from `execution.delivery.content.attachments`.
|
||||
> No separate storage needed.
|
||||
|
||||
```go
|
||||
// openapi/agent/robot/types.go (API response types)
|
||||
|
||||
// ResultFileResponse - deliverable file for Results Tab
|
||||
type ResultFileResponse struct {
|
||||
ID string `json:"id"` // attachment index or file ID
|
||||
MemberID string `json:"member_id"`
|
||||
ExecutionID string `json:"execution_id"`
|
||||
Name string `json:"name"` // From attachment.Title
|
||||
Type string `json:"type"` // Derived from file extension
|
||||
Size int64 `json:"size"` // From file system
|
||||
CreatedAt string `json:"created_at"` // Execution end time
|
||||
TriggerType string `json:"trigger_type,omitempty"`
|
||||
ExecutionName string `json:"execution_name,omitempty"` // Derived
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Multi-turn Conversation Gap (Critical)
|
||||
|
||||
### 2.1 Frontend Expectation
|
||||
|
||||
The frontend `ChatDrawer` component expects **multi-turn conversation** before execution starts:
|
||||
|
||||
```
|
||||
┌─────────────────────────────────────────────────────────────────┐
|
||||
│ ASSIGN TASK DRAWER (ChatDrawer) │
|
||||
├─────────────────────────────────────────────────────────────────┤
|
||||
│ │
|
||||
│ User: "Help me analyze competitor pricing" │
|
||||
│ ↓ │
|
||||
│ Robot: "Got it. Which competitors? Any specific metrics?" │
|
||||
│ ↓ │
|
||||
│ User: "Focus on Company A and B, compare pricing tiers" │
|
||||
│ ↓ │
|
||||
│ Robot: "Understood. I'll analyze A and B pricing tiers. │
|
||||
│ Ready to start?" │
|
||||
│ ↓ │
|
||||
│ User clicks [Confirm] → Execution starts │
|
||||
│ │
|
||||
└─────────────────────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
**Key Flow:**
|
||||
1. User sends message → Backend returns assistant response
|
||||
2. User can continue conversation (refine task)
|
||||
3. User confirms → Execution actually starts
|
||||
|
||||
### 2.2 Current Backend Implementation
|
||||
|
||||
```go
|
||||
// api/trigger.go - Current behavior
|
||||
func Trigger(ctx *types.Context, memberID string, req *TriggerRequest) (*TriggerResult, error) {
|
||||
// Immediately submits to execution pool
|
||||
// No conversation state, no confirmation step
|
||||
}
|
||||
```
|
||||
|
||||
**Problem:** Backend triggers execution immediately on first message. No multi-turn conversation support.
|
||||
|
||||
### 2.3 Gap Analysis
|
||||
|
||||
| Feature | Frontend Expects | Backend Has |
|
||||
|---------|------------------|-------------|
|
||||
| Multi-turn chat | ✅ Yes | ❌ No |
|
||||
| Conversation state | ✅ Yes | ❌ No |
|
||||
| Confirm before execute | ✅ Yes | ❌ No |
|
||||
| SSE for each message | ✅ Yes | ❌ No |
|
||||
|
||||
### 2.4 Required New API
|
||||
|
||||
**Option A: Chat API (Recommended)**
|
||||
|
||||
```
|
||||
POST /v1/agent/robots/:id/chat
|
||||
```
|
||||
|
||||
**Request:**
|
||||
```json
|
||||
{
|
||||
"conversation_id": "conv_001", // Optional, for continuing conversation
|
||||
"messages": [
|
||||
{ "role": "user", "content": "Help me analyze competitor pricing" }
|
||||
],
|
||||
"attachments": []
|
||||
}
|
||||
```
|
||||
|
||||
**Response (SSE):**
|
||||
```
|
||||
event: message
|
||||
data: {"role": "assistant", "content": "Got it. Which competitors?"}
|
||||
|
||||
event: state
|
||||
data: {"conversation_id": "conv_001", "ready_to_execute": false}
|
||||
```
|
||||
|
||||
**Then Trigger with conversation:**
|
||||
```
|
||||
POST /v1/agent/robots/:id/trigger
|
||||
{
|
||||
"conversation_id": "conv_001", // References chat history
|
||||
"confirm": true
|
||||
}
|
||||
```
|
||||
|
||||
**Option B: Extend Trigger API**
|
||||
|
||||
Add `confirm` parameter to trigger:
|
||||
```json
|
||||
{
|
||||
"messages": [...],
|
||||
"confirm": false // false = chat mode, true = execute
|
||||
}
|
||||
```
|
||||
|
||||
### 2.5 Backend Implementation Needed
|
||||
|
||||
1. **Conversation Store** - Store chat history temporarily
|
||||
```go
|
||||
// store/conversation.go (NEW)
|
||||
type ConversationStore interface {
|
||||
Create(memberID string, messages []Message) (conversationID string, error)
|
||||
Append(conversationID string, messages []Message) error
|
||||
Get(conversationID string) (*Conversation, error)
|
||||
Delete(conversationID string) error // Auto-cleanup after execution
|
||||
}
|
||||
```
|
||||
|
||||
2. **Chat Handler** - Process messages, return assistant response
|
||||
```go
|
||||
// api/chat.go (NEW)
|
||||
func Chat(ctx *types.Context, memberID string, req *ChatRequest) (*ChatResponse, error) {
|
||||
// 1. Get or create conversation
|
||||
// 2. Call LLM for response (using robot's system prompt)
|
||||
// 3. Store updated conversation
|
||||
// 4. Return assistant message + conversation_id
|
||||
}
|
||||
```
|
||||
|
||||
3. **Trigger Extension** - Support conversation_id
|
||||
```go
|
||||
// api/trigger.go (MODIFY)
|
||||
type TriggerRequest struct {
|
||||
// ... existing fields ...
|
||||
ConversationID string `json:"conversation_id,omitempty"` // NEW
|
||||
}
|
||||
```
|
||||
|
||||
### 2.6 Same for Intervention
|
||||
|
||||
`GuideExecutionDrawer` also uses `ChatDrawer` and expects the same multi-turn behavior for intervention.
|
||||
|
||||
---
|
||||
|
||||
## 3. Backend API Gaps (`yao/agent/robot/api/`)
|
||||
|
||||
### 3.1 Missing Functions
|
||||
|
||||
| Function | Status | Description |
|
||||
|----------|--------|-------------|
|
||||
| `Create()` | ⬜ Missing | Create new robot member |
|
||||
| `Update()` | ⬜ Missing | Update robot config |
|
||||
| `Remove()` | ⬜ Missing | Delete robot member |
|
||||
| `ListResults()` | ⬜ Missing | Query deliverable files from executions |
|
||||
| `GetResult()` | ⬜ Missing | Get single deliverable detail |
|
||||
| `ListActivities()` | ⬜ Missing | Query activity feed |
|
||||
| `RetryExecution()` | ⬜ Missing | Retry a failed execution |
|
||||
|
||||
### 3.2 Existing Functions (API layer can call these)
|
||||
|
||||
| Function | File | Status |
|
||||
|----------|------|--------|
|
||||
| `List()` | `robot.go` | ✅ Exists |
|
||||
| `Get()` | `robot.go` | ✅ Exists |
|
||||
| `GetStatus()` | `robot.go` | ✅ Exists |
|
||||
| `Trigger()` | `trigger.go` | ✅ Exists |
|
||||
| `Intervene()` | `trigger.go` | ✅ Exists |
|
||||
| `GetExecutions()` | `execution.go` | ✅ Exists |
|
||||
| `GetExecution()` | `execution.go` | ✅ Exists |
|
||||
| `PauseExecution()` | `execution.go` | ✅ Exists |
|
||||
| `ResumeExecution()` | `execution.go` | ✅ Exists |
|
||||
| `StopExecution()` | `execution.go` | ✅ Exists |
|
||||
|
||||
### 3.3 Required API Extensions
|
||||
|
||||
**File: `api/robot.go`**
|
||||
```go
|
||||
// Create creates a new robot member
|
||||
func Create(ctx *types.Context, teamID string, req *CreateRobotRequest) (*types.Robot, error)
|
||||
|
||||
// Update updates robot config
|
||||
func Update(ctx *types.Context, memberID string, req *UpdateRobotRequest) (*types.Robot, error)
|
||||
|
||||
// Remove deletes a robot member
|
||||
func Remove(ctx *types.Context, memberID string) error
|
||||
```
|
||||
|
||||
**File: `api/results.go` (NEW)**
|
||||
```go
|
||||
// ListResults returns deliverable files for a robot
|
||||
func ListResults(ctx *types.Context, memberID string, query *ResultQuery) (*ResultsResult, error)
|
||||
|
||||
// GetResult returns a single deliverable detail
|
||||
func GetResult(ctx *types.Context, resultID string) (*types.ResultFile, error)
|
||||
```
|
||||
|
||||
**File: `api/activities.go` (NEW)**
|
||||
```go
|
||||
// ListActivities returns recent activities
|
||||
func ListActivities(ctx *types.Context, query *ActivityQuery) (*ActivitiesResult, error)
|
||||
```
|
||||
|
||||
**File: `api/execution.go` (extend)**
|
||||
```go
|
||||
// RetryExecution retries a failed execution
|
||||
func RetryExecution(ctx *types.Context, execID string) (*TriggerResult, error)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. OpenAPI Layer (`yao/openapi/agent/robot/`)
|
||||
|
||||
### 4.1 Files to Create
|
||||
|
||||
```
|
||||
yao/openapi/agent/robot/
|
||||
├── DESIGN.md # ✅ Exists
|
||||
├── TODO.md # ✅ Exists
|
||||
├── GAPS.md # ✅ This file
|
||||
│
|
||||
├── robot.go # Route registration
|
||||
├── types.go # Request/Response types
|
||||
├── list.go # GET /v1/agent/robots
|
||||
├── detail.go # GET/POST/PUT/DELETE /v1/agent/robots/:id
|
||||
├── execution.go # Execution list/detail/control
|
||||
├── trigger.go # Trigger/Intervene (SSE)
|
||||
├── results.go # Results endpoints
|
||||
├── activities.go # Activities endpoint
|
||||
├── stream.go # Real-time SSE streams
|
||||
├── filter.go # Query param parsing
|
||||
└── utils.go # Locale, time formatting
|
||||
```
|
||||
|
||||
### 4.2 Endpoints to Implement
|
||||
|
||||
#### Robot CRUD (5 endpoints)
|
||||
|
||||
| Endpoint | Handler | Backend API |
|
||||
|----------|---------|-------------|
|
||||
| `GET /robots` | `ListRobots` | `api.List()` ✅ |
|
||||
| `GET /robots/:id` | `GetRobot` | `api.Get()` + `api.GetStatus()` ✅ |
|
||||
| `POST /robots` | `CreateRobot` | `api.Create()` ⬜ |
|
||||
| `PUT /robots/:id` | `UpdateRobot` | `api.Update()` ⬜ |
|
||||
| `DELETE /robots/:id` | `DeleteRobot` | `api.Remove()` ⬜ |
|
||||
|
||||
#### Chat & Execution Management (9 endpoints)
|
||||
|
||||
| Endpoint | Handler | Backend API |
|
||||
|----------|---------|-------------|
|
||||
| `POST /robots/:id/chat` | `ChatWithRobot` | `api.Chat()` ⬜ **NEW - Multi-turn conversation** |
|
||||
| `GET /robots/:id/executions` | `ListExecutions` | `api.GetExecutions()` ✅ |
|
||||
| `GET /robots/:id/executions/:exec_id` | `GetExecution` | `api.GetExecution()` ✅ |
|
||||
| `POST /robots/:id/trigger` | `TriggerRobot` | `api.Trigger()` ✅ (needs conversation_id support) |
|
||||
| `POST /robots/:id/intervene` | `InterveneRobot` | `api.Intervene()` ✅ (needs conversation_id support) |
|
||||
| `POST /robots/:id/executions/:exec_id/pause` | `PauseExecution` | `api.PauseExecution()` ✅ |
|
||||
| `POST /robots/:id/executions/:exec_id/resume` | `ResumeExecution` | `api.ResumeExecution()` ✅ |
|
||||
| `POST /robots/:id/executions/:exec_id/cancel` | `CancelExecution` | `api.StopExecution()` ✅ |
|
||||
| `POST /robots/:id/executions/:exec_id/retry` | `RetryExecution` | `api.RetryExecution()` ⬜ |
|
||||
|
||||
#### Results (2 endpoints)
|
||||
|
||||
| Endpoint | Handler | Backend API |
|
||||
|----------|---------|-------------|
|
||||
| `GET /robots/:id/results` | `ListResults` | `api.ListResults()` ⬜ |
|
||||
| `GET /robots/:id/results/:result_id` | `GetResult` | `api.GetResult()` ⬜ |
|
||||
|
||||
#### Activities (1 endpoint)
|
||||
|
||||
| Endpoint | Handler | Backend API |
|
||||
|----------|---------|-------------|
|
||||
| `GET /robots/activities` | `ListActivities` | `api.ListActivities()` ⬜ |
|
||||
|
||||
#### SSE Streams (3 endpoints)
|
||||
|
||||
| Endpoint | Handler | Backend Event Bus |
|
||||
|----------|---------|-------------------|
|
||||
| `GET /robots/stream` | `StreamRobots` | ⬜ New event bus needed |
|
||||
| `GET /robots/:id/executions/:exec_id/stream` | `StreamExecution` | ⬜ New event bus needed |
|
||||
| `POST /robots/:id/trigger` (SSE) | `TriggerRobot` | Wrap existing `api.Trigger()` |
|
||||
| `POST /robots/:id/intervene` (SSE) | `InterveneRobot` | Wrap existing `api.Intervene()` |
|
||||
|
||||
---
|
||||
|
||||
## 5. SSE Infrastructure Gaps
|
||||
|
||||
### 5.1 Event Bus Needed
|
||||
|
||||
The backend needs an event bus to publish real-time events. Currently, the robot module doesn't have one.
|
||||
|
||||
**Required Components:**
|
||||
|
||||
```go
|
||||
// robot/events/bus.go (NEW PACKAGE)
|
||||
|
||||
type EventBus struct {
|
||||
subscribers map[string][]chan Event
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
type Event struct {
|
||||
Type string `json:"type"` // robot_status, execution_start, etc.
|
||||
Payload interface{} `json:"payload"`
|
||||
}
|
||||
|
||||
func (bus *EventBus) Publish(event Event)
|
||||
func (bus *EventBus) Subscribe(topic string) <-chan Event
|
||||
func (bus *EventBus) Unsubscribe(topic string, ch <-chan Event)
|
||||
```
|
||||
|
||||
### 5.2 Event Publishers Needed
|
||||
|
||||
| Event | Source | When |
|
||||
|-------|--------|------|
|
||||
| `robot_status` | Manager | Robot status changes |
|
||||
| `execution_start` | Executor | Execution begins |
|
||||
| `execution_complete` | Executor | Execution ends |
|
||||
| `phase` | Executor | Phase changes |
|
||||
| `task_start` | Runner | Task begins |
|
||||
| `task_complete` | Runner | Task ends |
|
||||
| `activity` | Multiple | Any activity event |
|
||||
|
||||
### 5.3 Integration Points
|
||||
|
||||
**In `manager/manager.go`:**
|
||||
```go
|
||||
// Publish when robot status changes
|
||||
eventBus.Publish(Event{Type: "robot_status", Payload: ...})
|
||||
```
|
||||
|
||||
**In `executor/standard/executor.go`:**
|
||||
```go
|
||||
// Publish when execution starts/ends
|
||||
eventBus.Publish(Event{Type: "execution_start", Payload: ...})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. i18n Support Gaps
|
||||
|
||||
### 6.1 Current State
|
||||
|
||||
- No locale parameter in backend API
|
||||
- No localization infrastructure
|
||||
|
||||
### 6.2 Required Changes
|
||||
|
||||
**Add locale to context:**
|
||||
```go
|
||||
// types/context.go
|
||||
type Context struct {
|
||||
context.Context
|
||||
Auth *types.AuthorizedInfo
|
||||
MemberID string
|
||||
Locale string // NEW: "zh-CN" | "en-US"
|
||||
}
|
||||
```
|
||||
|
||||
**Add locale helper:**
|
||||
```go
|
||||
// utils/locale.go (NEW)
|
||||
func GetLocale(r *http.Request) string
|
||||
func Localize(key, locale string) string
|
||||
```
|
||||
|
||||
**Localized fields:**
|
||||
- `RobotState.display_name`
|
||||
- `RobotState.description`
|
||||
- `Execution.name`
|
||||
- `Execution.current_task_name`
|
||||
- `ResultFile.name`
|
||||
- `ResultFile.execution_name`
|
||||
- `Activity.robot_name`
|
||||
- `Activity.title`
|
||||
- `Activity.description`
|
||||
|
||||
---
|
||||
|
||||
## 7. Data Source Gaps
|
||||
|
||||
### 7.1 Results Data
|
||||
|
||||
Results are derived from execution delivery data. Need to:
|
||||
|
||||
1. **Query from `store/execution.go`** - executions with delivery attachments
|
||||
2. **Extract attachment metadata** - file ID, name, type, size
|
||||
|
||||
**Implementation:**
|
||||
```go
|
||||
// store/results.go (NEW)
|
||||
func (s *ExecutionStore) ListResults(ctx context.Context, memberID string, opts *ResultsQuery) ([]*ResultFile, int, error) {
|
||||
// Query executions with delivery.content.attachments
|
||||
// Extract and format as ResultFile
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Activities Data
|
||||
|
||||
Activities can be derived from:
|
||||
1. **Job system logs** - existing `job.ListLogs()`
|
||||
2. **Execution state changes** - from `store/execution.go`
|
||||
|
||||
**Implementation Options:**
|
||||
|
||||
**Option A: Derive from execution history**
|
||||
```go
|
||||
func ListActivities(ctx context.Context, query *ActivityQuery) ([]*Activity, error) {
|
||||
// Query recent executions
|
||||
// Map to Activity based on status changes
|
||||
}
|
||||
```
|
||||
|
||||
**Option B: Separate activity log (recommended for real-time)**
|
||||
```go
|
||||
// New table: __yao.robot_activity
|
||||
type ActivityRecord struct {
|
||||
ID int64
|
||||
Type ActivityType
|
||||
MemberID string
|
||||
ExecutionID string
|
||||
Data JSON
|
||||
Timestamp time.Time
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. Implementation Priority
|
||||
|
||||
> **Strategy:** Low-risk phases first. Medium-risk features (Chat API, SSE) can be deferred.
|
||||
> Frontend can use polling and single-submit mode as fallback.
|
||||
|
||||
---
|
||||
|
||||
### 🟢 Phase 1: Core CRUD [Low Risk]
|
||||
|
||||
1. ⬜ Add `Bio` field to `Robot` struct (`types/robot.go`)
|
||||
2. ⬜ Add `bio` to `memberFields` in `cache/load.go`
|
||||
3. ⬜ Implement `api.Create()`, `api.Update()`, `api.Remove()`
|
||||
4. ⬜ Create OpenAPI handlers: list, detail, create, update, delete
|
||||
5. ⬜ Add response type mapping (`name` ← `member_id`, `description` ← `bio`)
|
||||
|
||||
### 🟢 Phase 2: Execution Management [Low Risk]
|
||||
|
||||
1. ⬜ Add derived fields in OpenAPI layer (`name`, `current_task_name`)
|
||||
2. ⬜ Implement `api.RetryExecution()`
|
||||
3. ⬜ Create OpenAPI handlers: execution list, detail, control
|
||||
4. ⬜ Wrap trigger/intervene (single-submit mode, no chat)
|
||||
|
||||
### 🟢 Phase 3: Results & Activities [Low Risk]
|
||||
|
||||
1. ⬜ Create `ActivityResponse` and `ResultFileResponse` types in OpenAPI layer
|
||||
2. ⬜ Implement `api.ListResults()`, `api.GetResult()` (derive from executions)
|
||||
3. ⬜ Implement `api.ListActivities()` (derive from execution history)
|
||||
4. ⬜ Create OpenAPI handlers
|
||||
|
||||
### 🟢 Phase 4: i18n [Low Risk]
|
||||
|
||||
1. ⬜ Add `Locale` to context
|
||||
2. ⬜ Add locale helper functions
|
||||
3. ⬜ Implement localized response fields
|
||||
|
||||
---
|
||||
|
||||
### 🟡 Phase 5: Multi-turn Chat API [Medium Risk - Deferred]
|
||||
|
||||
> **Fallback:** Frontend uses single-submit mode (user input → immediate execution)
|
||||
|
||||
1. ⬜ Create `store/conversation.go` - temporary conversation storage
|
||||
2. ⬜ Create `api/chat.go` - chat handler with LLM call
|
||||
3. ⬜ Extend `api/trigger.go` - support `conversation_id`
|
||||
4. ⬜ Create OpenAPI endpoint: `POST /robots/:id/chat` (SSE)
|
||||
5. ⬜ Update `POST /robots/:id/trigger` to accept conversation reference
|
||||
6. ⬜ Same for `POST /robots/:id/intervene`
|
||||
|
||||
### 🟡 Phase 6: Real-time SSE [Medium Risk - Deferred]
|
||||
|
||||
> **Fallback:** Frontend uses polling (GET /executions every 3-5s)
|
||||
|
||||
1. ⬜ Create event bus package
|
||||
2. ⬜ Integrate event publishing in manager/executor
|
||||
3. ⬜ Implement SSE stream handlers
|
||||
4. ⬜ End-to-end testing
|
||||
|
||||
---
|
||||
|
||||
## 9. Testing Strategy
|
||||
|
||||
### Unit Tests
|
||||
|
||||
- `types/activity_test.go` - new types
|
||||
- `types/result_test.go` - new types
|
||||
- `api/robot_test.go` - CRUD functions
|
||||
- `api/results_test.go` - results API
|
||||
- `api/activities_test.go` - activities API
|
||||
|
||||
### Integration Tests
|
||||
|
||||
- `openapi/agent/robot/*_test.go` - HTTP endpoint tests
|
||||
- `openapi/agent/robot/sse_test.go` - SSE stream tests
|
||||
|
||||
### E2E Tests
|
||||
|
||||
- Full flow: create robot → trigger → stream events → get results
|
||||
|
||||
---
|
||||
|
||||
## 10. Files to Modify Summary
|
||||
|
||||
### Backend (`yao/agent/robot/`)
|
||||
|
||||
| File | Action | Changes |
|
||||
|------|--------|---------|
|
||||
| `types/robot.go` | Modify | Add `Bio` field, optionally `Name`/`CurrentTaskName` for Execution |
|
||||
| `types/conversation.go` | Create | `Conversation`, `ChatRequest`, `ChatResponse` types |
|
||||
| `cache/load.go` | Modify | Add `bio` to `memberFields` slice |
|
||||
| `store/conversation.go` | Create | Temporary conversation storage (redis/memory) |
|
||||
| `types/context.go` | Modify | Add `Locale` field |
|
||||
| `api/robot.go` | Modify | Add `Create()`, `Update()`, `Remove()` |
|
||||
| `api/chat.go` | Create | `Chat()` - multi-turn conversation handler |
|
||||
| `api/trigger.go` | Modify | Add `ConversationID` support |
|
||||
| `api/execution.go` | Modify | Add `RetryExecution()` |
|
||||
| `api/results.go` | Create | `ListResults()`, `GetResult()` - query from execution store |
|
||||
| `api/activities.go` | Create | `ListActivities()` - derive from execution history |
|
||||
| `events/bus.go` | Create | Event bus for SSE (Phase 5) |
|
||||
|
||||
### OpenAPI (`yao/openapi/agent/robot/`)
|
||||
|
||||
| File | Action | Description |
|
||||
|------|--------|-------------|
|
||||
| `robot.go` | Create | Route registration |
|
||||
| `types.go` | Create | Request/Response types |
|
||||
| `list.go` | Create | List robots handler |
|
||||
| `detail.go` | Create | Robot CRUD handlers |
|
||||
| `chat.go` | Create | Multi-turn chat SSE handler |
|
||||
| `execution.go` | Create | Execution handlers |
|
||||
| `trigger.go` | Create | Trigger/Intervene SSE (with conversation support) |
|
||||
| `results.go` | Create | Results handlers |
|
||||
| `activities.go` | Create | Activities handler |
|
||||
| `stream.go` | Create | SSE streams |
|
||||
| `filter.go` | Create | Query parsing |
|
||||
| `utils.go` | Create | Utilities |
|
||||
|
||||
### Parent (`yao/openapi/agent/`)
|
||||
|
||||
| File | Action | Changes |
|
||||
|------|--------|---------|
|
||||
| `agent.go` | Modify | Add `robot.Attach(group.Group("/robots"), oauth)` |
|
||||
|
||||
---
|
||||
|
||||
## 11. References
|
||||
|
||||
- Frontend API Requirements: `cui/packages/cui/pages/mission-control/API.md`
|
||||
- Backend Robot Types: `yao/agent/robot/types/`
|
||||
- Backend Robot API: `yao/agent/robot/api/`
|
||||
- OpenAPI Design: `yao/openapi/agent/robot/DESIGN.md`
|
||||
- OpenAPI TODO: `yao/openapi/agent/robot/TODO.md`
|
||||
554
openapi/agent/robot/TODO.md
Normal file
554
openapi/agent/robot/TODO.md
Normal file
|
|
@ -0,0 +1,554 @@
|
|||
# Robot OpenAPI - Implementation TODO
|
||||
|
||||
> Based on: `openapi/agent/robot/DESIGN.md`, `openapi/agent/robot/GAPS.md`
|
||||
> Depends on: `yao/agent/robot/api/` (Go API layer)
|
||||
> Base Path: `/v1/agent/robots`
|
||||
|
||||
---
|
||||
|
||||
## Implementation Strategy
|
||||
|
||||
> **Low-risk phases first. Medium-risk features can be deferred.**
|
||||
> Frontend has fallback mechanisms (polling, single-submit mode).
|
||||
|
||||
```
|
||||
🟢 Low Risk (Do First):
|
||||
Phase 1: Core CRUD (MVP)
|
||||
└─ List, Get, Create, Update, Delete robots
|
||||
|
||||
Phase 2: Execution Management
|
||||
└─ List, Get, Control executions, Trigger/Intervene (single-submit)
|
||||
|
||||
Phase 3: Results & Activities
|
||||
└─ List deliverables, Activity feed
|
||||
|
||||
Phase 4: i18n
|
||||
└─ Locale parameter support
|
||||
|
||||
🟡 Medium Risk (Deferred):
|
||||
Phase 5: Multi-turn Chat API
|
||||
└─ Conversation before execution (Frontend fallback: single-submit)
|
||||
|
||||
Phase 6: Real-time SSE Streams
|
||||
└─ Robot status stream, Execution progress (Frontend fallback: polling)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Phase 1: Core CRUD ⬜ [Low Risk]
|
||||
|
||||
**Goal:** Basic robot management endpoints
|
||||
**Risk:** 🟢 Low - All new code, no changes to existing logic
|
||||
|
||||
### 1.1 Backend Prerequisites ⬜
|
||||
|
||||
- [ ] Add `Bio` field to `types.Robot` struct in `yao/agent/robot/types/robot.go`
|
||||
- [ ] Add `bio` to `memberFields` in `yao/agent/robot/cache/load.go`
|
||||
- [ ] Implement `api.Create()` in `yao/agent/robot/api/robot.go`
|
||||
- [ ] Implement `api.Update()` in `yao/agent/robot/api/robot.go`
|
||||
- [ ] Implement `api.Remove()` in `yao/agent/robot/api/robot.go`
|
||||
|
||||
### 1.2 Setup ⬜
|
||||
|
||||
- [ ] Create `openapi/agent/robot/` directory (sub-package under agent)
|
||||
- [ ] Create `robot.go` - route registration with `Attach()` function
|
||||
- [ ] Register routes in `openapi/agent/agent.go` via `robot.Attach(group.Group("/robots"), oauth)`
|
||||
- [ ] Add OAuth guard middleware
|
||||
|
||||
### 1.3 Types ⬜
|
||||
|
||||
- [ ] `types.go` - request/response types
|
||||
- [ ] `RobotResponse` struct (with field mapping: `name` ← `member_id`, `description` ← `bio`)
|
||||
- [ ] `ConfigResponse` struct (and sub-types)
|
||||
- [ ] `ListRobotsResponse` struct
|
||||
- [ ] `CreateRobotRequest` struct
|
||||
- [ ] `UpdateRobotRequest` struct
|
||||
- [ ] `NewRobotResponse()` - conversion function
|
||||
- [ ] Error response types
|
||||
|
||||
### 1.4 List Robots ⬜
|
||||
|
||||
- [ ] `list.go` - GET /v1/robots
|
||||
- [ ] Parse query params: `locale`, `status`, `keywords`, `page`, `pagesize`
|
||||
- [ ] Call `robot/api.List()`
|
||||
- [ ] Format response with localization
|
||||
- [ ] Test: `tests/robot/list_test.go`
|
||||
|
||||
### 1.5 Get Robot ⬜
|
||||
|
||||
- [ ] `detail.go` - GET /v1/robots/:id
|
||||
- [ ] Parse path param and `locale` query
|
||||
- [ ] Call `robot/api.Get()` and `robot/api.Status()`
|
||||
- [ ] Format response with full config
|
||||
- [ ] Team access check
|
||||
- [ ] Test: `tests/robot/get_test.go`
|
||||
|
||||
### 1.6 Create Robot ⬜
|
||||
|
||||
- [ ] POST /v1/robots handler
|
||||
- [ ] Parse `CreateRobotRequest`
|
||||
- [ ] Validate required fields
|
||||
- [ ] Call `robot/api.Create()`
|
||||
- [ ] Return created robot
|
||||
- [ ] Test: `tests/robot/create_test.go`
|
||||
|
||||
### 1.7 Update Robot ⬜
|
||||
|
||||
- [ ] PUT /v1/robots/:id handler
|
||||
- [ ] Parse `UpdateRobotRequest`
|
||||
- [ ] Ownership/permission check
|
||||
- [ ] Call `robot/api.Update()`
|
||||
- [ ] Return updated robot
|
||||
- [ ] Test: `tests/robot/update_test.go`
|
||||
|
||||
### 1.8 Delete Robot ⬜
|
||||
|
||||
- [ ] DELETE /v1/robots/:id handler
|
||||
- [ ] Ownership/permission check
|
||||
- [ ] Call `robot/api.Remove()`
|
||||
- [ ] Return success response
|
||||
- [ ] Test: `tests/robot/delete_test.go`
|
||||
|
||||
### 1.9 Utilities ⬜
|
||||
|
||||
- [ ] `utils.go` - helper functions
|
||||
- [ ] `getLocale(r *http.Request)` - extract locale
|
||||
- [ ] `formatTime(t *time.Time)` - format to ISO string
|
||||
- [ ] `localizeString(value, locale)` - localization helper
|
||||
- [ ] `filter.go` - query filtering
|
||||
- [ ] Parse query params to `ListQuery`
|
||||
- [ ] Parse query params to `ExecutionQuery`
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Phase 2: Execution Management ⬜ [Low Risk]
|
||||
|
||||
**Goal:** Execution listing, details, control, and trigger/intervene (single-submit mode)
|
||||
**Risk:** 🟢 Low - Wraps existing API functions
|
||||
|
||||
### 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`
|
||||
|
||||
### 2.2 Get Execution ⬜
|
||||
|
||||
- [ ] GET /v1/robots/:id/executions/:exec_id
|
||||
- [ ] Call `robot/api.GetExecution()`
|
||||
- [ ] Full task details with localization
|
||||
- [ ] Test: `tests/robot/execution_get_test.go`
|
||||
|
||||
### 2.3 Execution Control ⬜
|
||||
|
||||
- [ ] 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.4 Execution Types ⬜
|
||||
|
||||
- [ ] Add to `types.go`:
|
||||
- [ ] `ExecutionResponse` struct
|
||||
- [ ] `TaskResponse` struct
|
||||
- [ ] `CurrentStateResponse` struct
|
||||
- [ ] `GoalsResponse` struct
|
||||
- [ ] `DeliveryResultResponse` struct
|
||||
|
||||
### 2.5 Trigger & Intervene (Single-Submit Mode) ⬜
|
||||
|
||||
> **Note:** This is single-submit mode. Multi-turn chat is deferred to Phase 5.
|
||||
|
||||
- [ ] `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`
|
||||
|
||||
- [ ] POST /v1/robots/:id/intervene
|
||||
- [ ] Parse `InterveneRequest`
|
||||
- [ ] Call `robot/api.Intervene()`
|
||||
- [ ] Return result
|
||||
- [ ] Test: `tests/robot/intervene_test.go`
|
||||
|
||||
### 2.6 Trigger Types ⬜
|
||||
|
||||
- [ ] Add to `types.go`:
|
||||
- [ ] `TriggerRequest` struct
|
||||
- [ ] `TriggerResponse` struct
|
||||
- [ ] `InterveneRequest` struct
|
||||
- [ ] `InterveneResponse` struct
|
||||
- [ ] `Message` struct
|
||||
- [ ] `Attachment` struct
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Phase 3: Results & Activities ⬜ [Low Risk]
|
||||
|
||||
**Goal:** Deliverables listing and activity feed
|
||||
**Risk:** 🟢 Low - Read-only queries, derived from existing data
|
||||
|
||||
### 3.1 Backend Prerequisites ⬜
|
||||
|
||||
Need to add in `robot/api/`:
|
||||
|
||||
- [ ] `ListResults(memberID, query)` function
|
||||
- [ ] `GetResult(resultID)` function
|
||||
- [ ] `ListActivities(query)` function
|
||||
|
||||
Need to add in `robot/store/`:
|
||||
|
||||
- [ ] Results store (query from execution delivery data)
|
||||
- [ ] Activities store (or derive from job logs)
|
||||
|
||||
### 3.2 Results Endpoints ⬜
|
||||
|
||||
- [ ] `results.go` - results handlers
|
||||
- [ ] GET /v1/robots/:id/results
|
||||
- [ ] Parse filters: `trigger_type`, `keyword`, `page`, `pagesize`
|
||||
- [ ] Call `robot/api.ListResults()`
|
||||
- [ ] Format response
|
||||
- [ ] GET /v1/robots/:id/results/:result_id
|
||||
- [ ] Call `robot/api.GetResult()`
|
||||
- [ ] Return full delivery content
|
||||
- [ ] Test: `tests/robot/results_test.go`
|
||||
|
||||
### 3.3 Results Types ⬜
|
||||
|
||||
- [ ] Add to `types.go`:
|
||||
- [ ] `ResultResponse` struct
|
||||
- [ ] `ResultDetailResponse` struct
|
||||
- [ ] `DeliveryContentResponse` struct
|
||||
- [ ] `DeliveryAttachmentResponse` struct
|
||||
|
||||
### 3.4 Activities Endpoints ⬜
|
||||
|
||||
- [ ] `activities.go` - activities handlers
|
||||
- [ ] GET /v1/robots/activities
|
||||
- [ ] Parse: `limit`, `since`
|
||||
- [ ] Call `robot/api.ListActivities()`
|
||||
- [ ] Format response
|
||||
- [ ] Test: `tests/robot/activities_test.go`
|
||||
|
||||
### 3.5 Activity Types ⬜
|
||||
|
||||
- [ ] Add to `types.go`:
|
||||
- [ ] `ActivityResponse` struct
|
||||
- [ ] `ActivityType` constants
|
||||
|
||||
---
|
||||
|
||||
## 🟢 Phase 4: i18n ⬜ [Low Risk]
|
||||
|
||||
**Goal:** Locale parameter support
|
||||
**Risk:** 🟢 Low - Additive, optional parameter
|
||||
|
||||
### 4.1 Locale Handling ⬜
|
||||
|
||||
- [ ] Add `getLocale(r *http.Request)` to utils.go
|
||||
- [ ] Parse locale from query param, body, or header
|
||||
- [ ] Add `Locale` field to context if needed
|
||||
|
||||
### 4.2 Localized Responses ⬜
|
||||
|
||||
- [ ] Localize `display_name` in RobotResponse
|
||||
- [ ] Localize `description` in RobotResponse
|
||||
- [ ] Localize `name` in ExecutionResponse (derive from goals/input)
|
||||
- [ ] Localize `current_task_name` in ExecutionResponse
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Phase 5: Multi-turn Chat API ⬜ [Medium Risk - Deferred]
|
||||
|
||||
> **Frontend Fallback:** Single-submit mode (user input → immediate execution)
|
||||
> **Risk:** 🟡 Medium - New stateful component
|
||||
|
||||
**Goal:** Multi-turn conversation before execution
|
||||
|
||||
### 5.1 Backend Prerequisites ⬜
|
||||
|
||||
- [ ] Create `store/conversation.go` - temporary conversation storage (redis/memory)
|
||||
- [ ] Create `types/conversation.go` - Conversation, ChatRequest, ChatResponse types
|
||||
- [ ] Create `api/chat.go` - Chat() handler with LLM call
|
||||
- [ ] Extend `api/trigger.go` - support `conversation_id` parameter
|
||||
|
||||
### 5.2 Chat Endpoint ⬜
|
||||
|
||||
- [ ] POST /v1/robots/:id/chat (SSE)
|
||||
- [ ] Parse ChatRequest (conversation_id, messages, attachments)
|
||||
- [ ] Create or continue conversation
|
||||
- [ ] Call LLM for response
|
||||
- [ ] Store updated conversation
|
||||
- [ ] Return assistant message + conversation_id
|
||||
- [ ] Test: `tests/robot/chat_test.go`
|
||||
|
||||
### 5.3 Trigger with Conversation ⬜
|
||||
|
||||
- [ ] Extend POST /v1/robots/:id/trigger
|
||||
- [ ] Accept `conversation_id` parameter
|
||||
- [ ] Use conversation history as execution input
|
||||
- [ ] Auto-cleanup conversation after execution starts
|
||||
|
||||
---
|
||||
|
||||
## 🟡 Phase 6: Real-time SSE Streams ⬜ [Medium Risk - Deferred]
|
||||
|
||||
> **Frontend Fallback:** Polling (GET /executions every 3-5 seconds)
|
||||
> **Risk:** 🟡 Medium - Requires modification of executor/manager
|
||||
|
||||
**Goal:** SSE streams for real-time status updates
|
||||
|
||||
### 6.1 Backend Event System ⬜
|
||||
|
||||
Need to add in `robot/`:
|
||||
|
||||
- [ ] Create `events/bus.go` - Event bus for pub/sub
|
||||
- [ ] Integrate event publishing in `manager/manager.go`
|
||||
- [ ] Integrate event publishing in `executor/standard/executor.go`
|
||||
- [ ] Publish: robot_status, execution_start, execution_complete, phase, task events
|
||||
|
||||
### 6.2 Robot Status Stream ⬜
|
||||
|
||||
- [ ] `stream.go` - stream handlers
|
||||
- [ ] GET /v1/robots/stream
|
||||
- [ ] Subscribe to manager status updates
|
||||
- [ ] Stream `robot_status` events
|
||||
- [ ] Stream `execution_start` events
|
||||
- [ ] Stream `execution_complete` events
|
||||
- [ ] Stream `activity` events
|
||||
- [ ] Test: `tests/robot/stream_test.go`
|
||||
|
||||
### 6.3 Execution Progress Stream ⬜
|
||||
|
||||
- [ ] GET /v1/robots/:id/executions/:exec_id/stream
|
||||
- [ ] Subscribe to execution updates
|
||||
- [ ] Stream `phase` events
|
||||
- [ ] Stream `task_start` / `task_complete` events
|
||||
- [ ] Stream `message` events
|
||||
- [ ] Stream `delivery` event
|
||||
- [ ] Stream `complete` / `error` events
|
||||
- [ ] Test: `tests/robot/execution_stream_test.go`
|
||||
|
||||
---
|
||||
|
||||
## Backend Extensions Required
|
||||
|
||||
### robot/types/ Extensions
|
||||
|
||||
| Type/Field | Phase | Risk | Description |
|
||||
|------------|-------|------|-------------|
|
||||
| `Robot.Bio` | 1 | 🟢 Low | Add field, maps to `__yao.member.bio` |
|
||||
| Execution name derivation | 2 | 🟢 Low | Derive in OpenAPI layer from goals or input |
|
||||
|
||||
> **Note:** `Robot.Name` is NOT needed. Frontend `name` maps to existing `Robot.MemberID`.
|
||||
|
||||
### robot/cache/ Extensions
|
||||
|
||||
| File | Phase | Risk | Description |
|
||||
|------|-------|------|-------------|
|
||||
| `load.go` | 1 | 🟢 Low | Add `bio` to `memberFields` slice |
|
||||
|
||||
### robot/api/ Extensions
|
||||
|
||||
| Function | Phase | Risk | Description |
|
||||
|----------|-------|------|-------------|
|
||||
| `Create()` | 1 | 🟢 Low | Create robot member via model |
|
||||
| `Update()` | 1 | 🟢 Low | Update robot config via model |
|
||||
| `Remove()` | 1 | 🟢 Low | Delete robot member via model |
|
||||
| `ListResults()` | 3 | 🟢 Low | Query from execution delivery data |
|
||||
| `GetResult()` | 3 | 🟢 Low | Get deliverable detail |
|
||||
| `ListActivities()` | 3 | 🟢 Low | Derive from execution history |
|
||||
| `RetryExecution()` | 2 | 🟢 Low | Re-trigger with same input |
|
||||
| `Chat()` | 5 | 🟡 Medium | Multi-turn conversation handler |
|
||||
|
||||
### robot/store/ Extensions
|
||||
|
||||
| Store | Phase | Risk | Description |
|
||||
|-------|-------|------|-------------|
|
||||
| Results query | 3 | 🟢 Low | Query from execution delivery data |
|
||||
| Activities query | 3 | 🟢 Low | Derive from execution history |
|
||||
| Conversation store | 5 | 🟡 Medium | Temporary chat history (redis/memory) |
|
||||
|
||||
### Event System (Phase 6 - Deferred)
|
||||
|
||||
| Component | Phase | Risk | Description |
|
||||
|-----------|-------|------|-------------|
|
||||
| Event bus | 6 | 🟡 Medium | Pub/sub for real-time updates |
|
||||
| Manager events | 6 | 🟡 Medium | Publish robot status changes |
|
||||
| Executor events | 6 | 🟡 Medium | Publish execution progress |
|
||||
|
||||
---
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### Test Files Structure
|
||||
|
||||
```
|
||||
yao/openapi/tests/robot/
|
||||
├── list_test.go
|
||||
├── get_test.go
|
||||
├── create_test.go
|
||||
├── update_test.go
|
||||
├── delete_test.go
|
||||
├── execution_list_test.go
|
||||
├── execution_get_test.go
|
||||
├── execution_control_test.go
|
||||
├── trigger_test.go
|
||||
├── intervene_test.go
|
||||
├── results_test.go
|
||||
├── activities_test.go
|
||||
├── stream_test.go
|
||||
└── execution_stream_test.go
|
||||
```
|
||||
|
||||
### Test Utilities
|
||||
|
||||
- [ ] Create test robot helper
|
||||
- [ ] Create test execution helper
|
||||
- [ ] SSE client for streaming tests
|
||||
- [ ] Mock data generators
|
||||
|
||||
---
|
||||
|
||||
## Progress Tracking
|
||||
|
||||
| Phase | Risk | Status | Description |
|
||||
|-------|------|--------|-------------|
|
||||
| 1. Core CRUD | 🟢 | ⬜ | Basic robot management |
|
||||
| 2. Execution | 🟢 | ⬜ | Execution listing, control, trigger/intervene |
|
||||
| 3. Results/Activities | 🟢 | ⬜ | Deliverables and activity feed |
|
||||
| 4. i18n | 🟢 | ⬜ | Locale parameter support |
|
||||
| 5. Chat API | 🟡 | ⬜ | Multi-turn conversation (Deferred) |
|
||||
| 6. SSE Streams | 🟡 | ⬜ | Real-time status updates (Deferred) |
|
||||
|
||||
Legend: ⬜ Not started | 🟡 In progress | ✅ Complete | 🟢 Low Risk | 🟡 Medium Risk
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Current Location
|
||||
|
||||
```
|
||||
yao/openapi/agent/robot/ # This directory (sub-package under agent)
|
||||
├── DESIGN.md # Design document ✅
|
||||
├── TODO.md # This file ✅
|
||||
├── robot.go # Route registration (Attach function)
|
||||
├── types.go # All request/response types
|
||||
├── list.go # GET /v1/agent/robots
|
||||
├── detail.go # GET/POST/PUT/DELETE /v1/agent/robots/:id
|
||||
├── execution.go # Execution endpoints
|
||||
├── trigger.go # Trigger/Intervene SSE
|
||||
├── results.go # Results endpoints
|
||||
├── activities.go # Activities endpoint
|
||||
├── stream.go # Real-time streams
|
||||
├── filter.go # Query filtering
|
||||
└── utils.go # Utilities
|
||||
```
|
||||
|
||||
### Parent Directory
|
||||
|
||||
```
|
||||
yao/openapi/agent/
|
||||
├── agent.go # MODIFY: add robot.Attach() call
|
||||
├── assistant.go # Existing
|
||||
├── filter.go # Existing
|
||||
├── models.go # Existing
|
||||
├── types.go # Existing
|
||||
│
|
||||
└── robot/ # NEW sub-package (this directory)
|
||||
└── ...
|
||||
```
|
||||
|
||||
### Route Registration (in agent/agent.go)
|
||||
|
||||
```go
|
||||
import "github.com/yaoapp/yao/openapi/agent/robot"
|
||||
|
||||
func Attach(group *gin.RouterGroup, oauth types.OAuth) {
|
||||
group.Use(oauth.Guard)
|
||||
|
||||
// Existing assistant routes
|
||||
group.GET("/assistants", ListAssistants)
|
||||
group.POST("/assistants", CreateAssistant)
|
||||
group.GET("/assistants/tags", ListAssistantTags)
|
||||
group.GET("/assistants/:id", GetAssistant)
|
||||
group.GET("/assistants/:id/info", GetAssistantInfo)
|
||||
group.PUT("/assistants/:id", UpdateAssistant)
|
||||
|
||||
// Robot routes (NEW)
|
||||
robot.Attach(group.Group("/robots"), oauth)
|
||||
}
|
||||
```
|
||||
|
||||
### Dependencies
|
||||
|
||||
| Package | Usage |
|
||||
|---------|-------|
|
||||
| `yao/agent/robot/api` | Go API functions (Get, List, Trigger, etc.) |
|
||||
| `yao/agent/robot/types` | Robot types (Robot, Execution, etc.) |
|
||||
| `yao/openapi/oauth` | Authentication, Guard middleware |
|
||||
| `yao/openapi/oauth/types` | OAuth types (AuthorizedInfo) |
|
||||
| `yao/openapi/response` | Response helpers |
|
||||
|
||||
### Import Path
|
||||
|
||||
```go
|
||||
package robot
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
robotapi "github.com/yaoapp/yao/agent/robot/api"
|
||||
robottypes "github.com/yaoapp/yao/agent/robot/types"
|
||||
"github.com/yaoapp/yao/openapi/oauth/types"
|
||||
)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Notes
|
||||
|
||||
### Priority
|
||||
|
||||
| Priority | Phase | Required For | Risk |
|
||||
|----------|-------|--------------|------|
|
||||
| 1 | Phase 1 (CRUD) | Basic UI functionality | 🟢 Low |
|
||||
| 2 | Phase 2 (Execution) | Active/History tabs, Assign Task | 🟢 Low |
|
||||
| 3 | Phase 3 (Results) | Results tab | 🟢 Low |
|
||||
| 4 | Phase 4 (i18n) | Multi-language support | 🟢 Low |
|
||||
| 5 | Phase 5 (Chat) | Enhanced UX (deferred) | 🟡 Medium |
|
||||
| 6 | Phase 6 (SSE) | Real-time updates (deferred) | 🟡 Medium |
|
||||
|
||||
### Frontend Fallbacks
|
||||
|
||||
| Feature | Full Implementation | Fallback |
|
||||
|---------|---------------------|----------|
|
||||
| Assign Task | Multi-turn chat → Confirm → Execute | Single-submit → Execute |
|
||||
| Real-time Status | SSE push | Polling every 3-5s |
|
||||
|
||||
### Frontend Integration
|
||||
|
||||
After each phase:
|
||||
1. Test endpoints manually
|
||||
2. Update frontend `openapi/robot.ts` to use real API
|
||||
3. Remove mock data usage
|
||||
4. Test end-to-end flow
|
||||
|
||||
### Incremental Deployment
|
||||
|
||||
Each phase can be deployed independently:
|
||||
- Phase 1: Basic management works
|
||||
- Phase 2: Execution history + trigger works
|
||||
- Phase 3: Results listing works
|
||||
- Phase 4: Multi-language works
|
||||
- Phase 5: Enhanced chat UX (optional)
|
||||
- Phase 6: Real-time updates (optional)
|
||||
Loading…
Add table
Reference in a new issue