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:
Max 2026-01-22 10:49:37 +08:00
parent 0a38c2b592
commit a0fa0e9eff
3 changed files with 2023 additions and 0 deletions

View 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
View 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
View 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)