Refactor Execution Record Model and Update Context Handling

- Removed RobotID from ExecutionRecord and DeliveryContext structures, emphasizing the use of MemberID as the globally unique identifier.
- Updated relevant documentation in DESIGN.md, TECHNICAL.md, and TODO.md to reflect these changes, ensuring clarity on the new context handling.
- Revised methods in ExecutionStore and Executor to align with the updated model, enhancing data management and execution tracking.
- Improved test cases to validate the new structure and ensure comprehensive coverage of execution scenarios.
This commit is contained in:
Max 2026-01-18 16:54:39 +08:00
parent 480063f5b2
commit ef7f32b428
10 changed files with 428 additions and 239 deletions

View file

@ -504,7 +504,7 @@ P4 generates delivery content and pushes to Delivery Center. **Agent only genera
┌─────────────────────────────────────────────────────────────┐
│ DeliveryRequest │
│ - Content: Summary, Body, Attachments │
│ - Context: robot_id, member_id, execution_id, trigger, team│
│ - Context: member_id, execution_id, trigger, team
│ (No Channels - Delivery Center decides) │
└─────────────────────┬───────────────────────────────────────┘
@ -554,8 +554,7 @@ type DeliveryAttachment struct {
// DeliveryContext - tracking and audit info
type DeliveryContext struct {
RobotID string `json:"robot_id"` // Robot config ID
MemberID string `json:"member_id"` // Robot member ID (user identity)
MemberID string `json:"member_id"` // Robot member ID (globally unique)
ExecutionID string `json:"execution_id"`
TriggerType TriggerType `json:"trigger_type"`
TeamID string `json:"team_id"`

View file

@ -1237,8 +1237,7 @@ func (r *Robot) GetExecutions() []*Execution {
// Relationship: 1 Execution = 1 job.Job
type Execution struct {
ID string `json:"id"` // unique execution ID
RobotID string `json:"robot_id"` // robot config ID
MemberID string `json:"member_id"` // robot member ID (user identity)
MemberID string `json:"member_id"` // robot member ID (globally unique)
TeamID string `json:"team_id"`
TriggerType TriggerType `json:"trigger_type"` // clock | human | event
StartTime time.Time `json:"start_time"`
@ -1426,8 +1425,7 @@ type DeliveryAttachment struct {
// DeliveryContext - tracking and audit info
type DeliveryContext struct {
RobotID string `json:"robot_id"` // Robot config ID
MemberID string `json:"member_id"` // Robot member ID (user identity)
MemberID string `json:"member_id"` // Robot member ID (globally unique)
ExecutionID string `json:"execution_id"`
TriggerType TriggerType `json:"trigger_type"`
TeamID string `json:"team_id"`
@ -2036,8 +2034,7 @@ type DeliveryAttachment struct {
// DeliveryContext - tracking and audit info
type DeliveryContext struct {
RobotID string `json:"robot_id"` // Robot config ID
MemberID string `json:"member_id"` // Robot member ID (user identity)
MemberID string `json:"member_id"` // Robot member ID (globally unique)
ExecutionID string `json:"execution_id"`
TriggerType TriggerType `json:"trigger_type"`
TeamID string `json:"team_id"`
@ -2054,7 +2051,6 @@ type DeliveryContext struct {
"attachments": [{"title": "Report.pdf", "file": "__yao.attachment://abc123"}]
},
"context": {
"robot_id": "robot_sales_001",
"member_id": "mem_abc123",
"execution_id": "exec_xyz789",
"trigger_type": "clock",
@ -2182,7 +2178,7 @@ type DeliveryCenter struct {
// Deliver - main entry point
func (dc *DeliveryCenter) Deliver(ctx context.Context, req *DeliveryRequest) *DeliveryResult {
requestID := generateID()
prefs := dc.getDeliveryPreferences(ctx, req.Context.RobotID)
prefs := dc.getDeliveryPreferences(ctx, req.Context.MemberID)
var results []ChannelResult
allSuccess := true
@ -2355,8 +2351,7 @@ Robot execution history is stored in `__yao.agent_execution` table for UI displa
type ExecutionRecord struct {
ID int64 `json:"id,omitempty"` // Auto-increment primary key
ExecutionID string `json:"execution_id"` // Unique execution identifier
RobotID string `json:"robot_id"` // Robot config ID
MemberID string `json:"member_id"` // Robot member ID (user identity)
MemberID string `json:"member_id"` // Robot member ID (globally unique)
TeamID string `json:"team_id"` // Team ID
JobID string `json:"job_id,omitempty"` // Linked job.Job ID
TriggerType TriggerType `json:"trigger_type"` // clock | human | event
@ -2424,12 +2419,11 @@ func (s *ExecutionStore) UpdateCurrent(ctx context.Context, executionID string,
func (s *ExecutionStore) Delete(ctx context.Context, executionID string) error
// Conversion helpers
func FromExecution(exec *Execution, robotID string) *ExecutionRecord
func FromExecution(exec *Execution) *ExecutionRecord
func (r *ExecutionRecord) ToExecution() *Execution
type ListOptions struct {
RobotID string // Filter by robot config ID
MemberID string // Filter by robot member ID
MemberID string // Filter by robot member ID (globally unique)
TeamID string // Filter by team
Status ExecStatus // Filter by status
TriggerType TriggerType // Filter by trigger

View file

@ -875,7 +875,7 @@ Created new `yao/assert` package for universal assertion/validation:
- [x] `yao/models/agent/execution.mod.yao` - Execution record model (`agent_execution` table)
- [x] id, execution_id (unique)
- [x] robot_id, member_id, team_id, job_id
- [x] member_id (globally unique), team_id, job_id
- [x] trigger_type (enum: clock, human, event)
- [x] **Status tracking** (synced with runtime Execution):
- [x] status (enum: pending, running, completed, failed, cancelled)
@ -903,7 +903,12 @@ Created new `yao/assert` package for universal assertion/validation:
- [x] `FromExecution(exec, robotID)` - convert runtime Execution to record
- [x] `ToExecution()` - convert record to runtime Execution
- [x] Tests: `agent/robot/store/execution_test.go` (9 test groups, all passing)
- [ ] Integrate into Executor - call `UpdatePhase()` after each phase completes
- [x] Integrate into Executor - call `UpdatePhase()` after each phase completes
- [x] Added `SkipPersistence` config option to `executor/types/Config`
- [x] Added `ExecutionStore` to `executor/standard/Executor`
- [x] Save execution record at start of `Execute()`
- [x] Call `UpdatePhase()` after each phase completes in `runPhase()`
- [x] Call `UpdateStatus()` on status changes (running, completed, failed)
### 10.2 Messenger Attachment Support ✅
@ -979,8 +984,7 @@ type DeliveryAttachment struct {
// DeliveryContext - tracking info
type DeliveryContext struct {
RobotID string `json:"robot_id"` // Robot config ID
MemberID string `json:"member_id"` // Robot member ID (user identity)
MemberID string `json:"member_id"` // Robot member ID (globally unique)
ExecutionID string `json:"execution_id"`
TriggerType TriggerType `json:"trigger_type"` // clock | human | event
TeamID string `json:"team_id"`

View file

@ -7,16 +7,19 @@ import (
"github.com/yaoapp/yao/agent/robot/executor/types"
"github.com/yaoapp/yao/agent/robot/job"
"github.com/yaoapp/yao/agent/robot/store"
robottypes "github.com/yaoapp/yao/agent/robot/types"
)
// Executor implements the standard executor with real Agent calls
// This is the production executor that:
// - Creates Job records for tracking
// - Persists execution history to database
// - Calls real Agents via Assistant.Stream()
// - Logs phase transitions and errors
type Executor struct {
config types.Config
store *store.ExecutionStore
execCount atomic.Int32
currentCount atomic.Int32
onStart func()
@ -25,13 +28,16 @@ type Executor struct {
// New creates a new standard executor
func New() *Executor {
return &Executor{}
return &Executor{
store: store.NewExecutionStore(),
}
}
// NewWithConfig creates a new standard executor with configuration
func NewWithConfig(config types.Config) *Executor {
return &Executor{
config: config,
store: store.NewExecutionStore(),
}
}
@ -76,6 +82,18 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
// Set robot reference for phase methods
exec.SetRobot(robot)
// Persist execution record to database
// Robot is identified by member_id (globally unique in __yao.member table)
if !e.config.SkipPersistence && e.store != nil {
record := store.FromExecution(exec)
if err := e.store.Save(ctx.Context, record); err != nil {
// Log warning but don't fail execution
if !e.config.SkipJobIntegration {
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist execution record: %v", err))
}
}
}
// Acquire execution slot
if !robot.TryAcquireSlot(exec) {
if !e.config.SkipJobIntegration && exec.JobID != "" {
@ -105,6 +123,14 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to update status to running: %v", err))
}
}
// Persist running status
if !e.config.SkipPersistence && e.store != nil {
if err := e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecRunning, ""); err != nil {
if !e.config.SkipJobIntegration {
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist running status: %v", err))
}
}
}
// Check for simulated failure (for testing)
if dataStr, ok := data.(string); ok && dataStr == "simulate_failure" {
@ -113,6 +139,10 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
if !e.config.SkipJobIntegration {
_ = job.FailExecution(ctx, exec, fmt.Errorf("simulated failure"))
}
// Persist failed status
if !e.config.SkipPersistence && e.store != nil {
_ = e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecFailed, "simulated failure")
}
return exec, nil
}
@ -125,6 +155,10 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
if !e.config.SkipJobIntegration {
_ = job.FailExecution(ctx, exec, err)
}
// Persist failed status
if !e.config.SkipPersistence && e.store != nil {
_ = e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecFailed, err.Error())
}
return exec, nil
}
}
@ -139,6 +173,14 @@ func (e *Executor) Execute(ctx *robottypes.Context, robot *robottypes.Robot, tri
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to mark execution as completed: %v", err))
}
}
// Persist completed status
if !e.config.SkipPersistence && e.store != nil {
if err := e.store.UpdateStatus(ctx.Context, exec.ID, robottypes.ExecCompleted, ""); err != nil {
if !e.config.SkipJobIntegration {
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist completed status: %v", err))
}
}
}
return exec, nil
}
@ -183,6 +225,19 @@ func (e *Executor) runPhase(ctx *robottypes.Context, exec *robottypes.Execution,
return err
}
// Persist phase output to database
if !e.config.SkipPersistence && e.store != nil {
phaseData := e.getPhaseData(exec, phase)
if phaseData != nil {
if err := e.store.UpdatePhase(ctx.Context, exec.ID, phase, phaseData); err != nil {
// Log warning but don't fail execution
if !e.config.SkipJobIntegration {
_ = job.LogWarn(ctx, exec, fmt.Sprintf("Failed to persist phase %s data: %v", phase, err))
}
}
}
}
if e.config.OnPhaseEnd != nil {
e.config.OnPhaseEnd(phase)
}
@ -195,6 +250,26 @@ func (e *Executor) runPhase(ctx *robottypes.Context, exec *robottypes.Execution,
return nil
}
// getPhaseData extracts the output data for a specific phase from execution
func (e *Executor) getPhaseData(exec *robottypes.Execution, phase robottypes.Phase) interface{} {
switch phase {
case robottypes.PhaseInspiration:
return exec.Inspiration
case robottypes.PhaseGoals:
return exec.Goals
case robottypes.PhaseTasks:
return exec.Tasks
case robottypes.PhaseRun:
return exec.Results
case robottypes.PhaseDelivery:
return exec.Delivery
case robottypes.PhaseLearning:
return exec.Learning
default:
return nil
}
}
// ExecCount returns total execution count
func (e *Executor) ExecCount() int {
return int(e.execCount.Load())

View file

@ -0,0 +1,158 @@
package standard_test
import (
"context"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/yaoapp/yao/agent/robot/executor/standard"
"github.com/yaoapp/yao/agent/robot/executor/types"
"github.com/yaoapp/yao/agent/robot/store"
robottypes "github.com/yaoapp/yao/agent/robot/types"
"github.com/yaoapp/yao/agent/testutils"
oauthTypes "github.com/yaoapp/yao/openapi/oauth/types"
)
// ============================================================================
// Executor Persistence Integration Tests
// ============================================================================
func TestExecutorPersistence(t *testing.T) {
if testing.Short() {
t.Skip("Skipping integration test")
}
testutils.Prepare(t)
defer testutils.Clean(t)
t.Run("persists_execution_record_on_start", func(t *testing.T) {
ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
UserID: "user_persist_001",
TeamID: "team_persist_001",
})
robot := createPersistenceTestRobot("member_persist_001", "team_persist_001")
// Create executor with persistence enabled but skip job integration
e := standard.NewWithConfig(types.Config{
SkipJobIntegration: true,
SkipPersistence: false,
})
// Execute with simulated failure to ensure we get a result
exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
require.NoError(t, err)
require.NotNil(t, exec)
// Verify execution record was persisted
s := store.NewExecutionStore()
record, err := s.Get(context.Background(), exec.ID)
require.NoError(t, err)
require.NotNil(t, record)
assert.Equal(t, exec.ID, record.ExecutionID)
assert.Equal(t, "member_persist_001", record.MemberID)
assert.Equal(t, "team_persist_001", record.TeamID)
assert.Equal(t, robottypes.TriggerHuman, record.TriggerType)
assert.Equal(t, robottypes.ExecFailed, record.Status)
assert.Equal(t, "simulated failure", record.Error)
// Cleanup
_ = s.Delete(context.Background(), exec.ID)
})
t.Run("persists_failed_status_with_error", func(t *testing.T) {
ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
UserID: "user_persist_002",
TeamID: "team_persist_002",
})
robot := createPersistenceTestRobot("member_persist_002", "team_persist_002")
e := standard.NewWithConfig(types.Config{
SkipJobIntegration: true,
SkipPersistence: false,
})
// Execute with simulated failure
exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
require.NoError(t, err)
require.NotNil(t, exec)
// Verify the record has failed status with error message
s := store.NewExecutionStore()
record, err := s.Get(context.Background(), exec.ID)
require.NoError(t, err)
require.NotNil(t, record)
assert.Equal(t, robottypes.ExecFailed, record.Status)
assert.Equal(t, "simulated failure", record.Error)
assert.NotNil(t, record.StartTime)
// Cleanup
_ = s.Delete(context.Background(), exec.ID)
})
t.Run("skips_persistence_when_disabled", func(t *testing.T) {
ctx := robottypes.NewContext(context.Background(), &oauthTypes.AuthorizedInfo{
UserID: "user_persist_003",
TeamID: "team_persist_003",
})
robot := createPersistenceTestRobot("member_persist_003", "team_persist_003")
// Create executor with persistence disabled
e := standard.NewWithConfig(types.Config{
SkipJobIntegration: true,
SkipPersistence: true,
})
exec, err := e.Execute(ctx, robot, robottypes.TriggerHuman, "simulate_failure")
require.NoError(t, err)
require.NotNil(t, exec)
// Verify no record was created
s := store.NewExecutionStore()
record, err := s.Get(context.Background(), exec.ID)
require.NoError(t, err)
assert.Nil(t, record) // Should not exist
})
}
// ============================================================================
// Helper Functions
// ============================================================================
func createPersistenceTestRobot(memberID, teamID string) *robottypes.Robot {
return &robottypes.Robot{
MemberID: memberID,
TeamID: teamID,
DisplayName: "Persistence Test Robot",
Status: robottypes.RobotIdle,
AutonomousMode: true,
Config: &robottypes.Config{
Identity: &robottypes.Identity{
Role: "Test Robot",
Duties: []string{"Testing persistence"},
},
Quota: &robottypes.Quota{
Max: 5,
Queue: 10,
},
Triggers: &robottypes.Triggers{
Intervene: &robottypes.TriggerSwitch{Enabled: true},
},
Resources: &robottypes.Resources{
Phases: map[robottypes.Phase]string{
robottypes.PhaseInspiration: "robot.inspiration",
robottypes.PhaseGoals: "robot.goals",
robottypes.PhaseTasks: "robot.tasks",
robottypes.PhaseRun: "robot.validation",
"validation": "robot.validation",
},
Agents: []string{"experts.text-writer", "experts.data-analyst"},
},
},
}
}

View file

@ -53,6 +53,9 @@ type Config struct {
// SkipJobIntegration skips job system integration (for testing)
SkipJobIntegration bool
// SkipPersistence skips execution record persistence (for testing)
SkipPersistence bool
// OnPhaseStart callback when a phase starts
OnPhaseStart func(phase robottypes.Phase)

View file

@ -15,8 +15,7 @@ import (
type ExecutionRecord struct {
ID int64 `json:"id,omitempty"` // Auto-increment primary key
ExecutionID string `json:"execution_id"` // Unique execution identifier
RobotID string `json:"robot_id"` // Robot config ID
MemberID string `json:"member_id"` // Robot member ID (user identity)
MemberID string `json:"member_id"` // Robot member ID (globally unique)
TeamID string `json:"team_id"` // Team ID
JobID string `json:"job_id,omitempty"` // Linked job.Job ID
TriggerType types.TriggerType `json:"trigger_type"` // clock | human | event
@ -53,8 +52,7 @@ type CurrentState struct {
// ListOptions - options for listing execution records
type ListOptions struct {
RobotID string `json:"robot_id,omitempty"`
MemberID string `json:"member_id,omitempty"`
MemberID string `json:"member_id,omitempty"` // Filter by robot member ID
TeamID string `json:"team_id,omitempty"`
Status types.ExecStatus `json:"status,omitempty"`
TriggerType types.TriggerType `json:"trigger_type,omitempty"`
@ -146,9 +144,6 @@ func (s *ExecutionStore) List(ctx context.Context, opts *ListOptions) ([]*Execut
// Build where conditions
var wheres []model.QueryWhere
if opts != nil {
if opts.RobotID != "" {
wheres = append(wheres, model.QueryWhere{Column: "robot_id", Value: opts.RobotID})
}
if opts.MemberID != "" {
wheres = append(wheres, model.QueryWhere{Column: "member_id", Value: opts.MemberID})
}
@ -349,9 +344,6 @@ func (s *ExecutionStore) recordToMap(record *ExecutionRecord) map[string]interfa
"phase": string(record.Phase),
}
if record.RobotID != "" {
data["robot_id"] = record.RobotID
}
if record.JobID != "" {
data["job_id"] = record.JobID
}
@ -410,9 +402,6 @@ func (s *ExecutionStore) mapToRecord(row map[string]interface{}) (*ExecutionReco
if v, ok := row["execution_id"].(string); ok {
record.ExecutionID = v
}
if v, ok := row["robot_id"].(string); ok {
record.RobotID = v
}
if v, ok := row["member_id"].(string); ok {
record.MemberID = v
}
@ -640,10 +629,9 @@ func (s *ExecutionStore) parseTime(v interface{}) *time.Time {
}
// FromExecution creates an ExecutionRecord from a runtime Execution
func FromExecution(exec *types.Execution, robotID string) *ExecutionRecord {
func FromExecution(exec *types.Execution) *ExecutionRecord {
record := &ExecutionRecord{
ExecutionID: exec.ID,
RobotID: robotID,
MemberID: exec.MemberID,
TeamID: exec.TeamID,
JobID: exec.JobID,

View file

@ -33,7 +33,6 @@ func TestExecutionStoreSave(t *testing.T) {
startTime := time.Now()
record := &store.ExecutionRecord{
ExecutionID: "exec_test_save_001",
RobotID: "robot_config_001",
MemberID: "member_test_001",
TeamID: "team_test_001",
JobID: "job_test_001",
@ -52,7 +51,6 @@ func TestExecutionStoreSave(t *testing.T) {
require.NotNil(t, saved)
assert.Equal(t, "exec_test_save_001", saved.ExecutionID)
assert.Equal(t, "robot_config_001", saved.RobotID)
assert.Equal(t, "member_test_001", saved.MemberID)
assert.Equal(t, "team_test_001", saved.TeamID)
assert.Equal(t, "job_test_001", saved.JobID)
@ -68,7 +66,6 @@ func TestExecutionStoreSave(t *testing.T) {
startTime := time.Now()
record := &store.ExecutionRecord{
ExecutionID: "exec_test_save_002",
RobotID: "robot_config_002",
MemberID: "member_test_002",
TeamID: "team_test_002",
TriggerType: types.TriggerHuman,
@ -124,7 +121,6 @@ func TestExecutionStoreGet(t *testing.T) {
require.NotNil(t, record)
assert.Equal(t, "exec_test_get_001", record.ExecutionID)
assert.Equal(t, "robot_config_get", record.RobotID)
assert.Equal(t, "member_test_get", record.MemberID)
assert.Equal(t, "team_test_get", record.TeamID)
assert.Equal(t, types.TriggerClock, record.TriggerType)
@ -184,17 +180,6 @@ func TestExecutionStoreList(t *testing.T) {
}
})
t.Run("filters_by_robot_id", func(t *testing.T) {
records, err := s.List(ctx, &store.ListOptions{
RobotID: "robot_list_001",
})
require.NoError(t, err)
assert.Equal(t, 2, len(records))
for _, r := range records {
assert.Equal(t, "robot_list_001", r.RobotID)
}
})
t.Run("filters_by_team_id", func(t *testing.T) {
records, err := s.List(ctx, &store.ListOptions{
TeamID: "team_list_001",
@ -269,7 +254,6 @@ func TestExecutionStoreUpdatePhase(t *testing.T) {
startTime := time.Now()
record := &store.ExecutionRecord{
ExecutionID: "exec_test_phase_001",
RobotID: "robot_phase_001",
MemberID: "member_phase_001",
TeamID: "team_phase_001",
TriggerType: types.TriggerClock,
@ -611,10 +595,9 @@ func TestExecutionRecordConversion(t *testing.T) {
},
}
record := store.FromExecution(exec, "robot_convert_001")
record := store.FromExecution(exec)
assert.Equal(t, "exec_convert_001", record.ExecutionID)
assert.Equal(t, "robot_convert_001", record.RobotID)
assert.Equal(t, "member_convert_001", record.MemberID)
assert.Equal(t, "team_convert_001", record.TeamID)
assert.Equal(t, "job_convert_001", record.JobID)
@ -636,7 +619,6 @@ func TestExecutionRecordConversion(t *testing.T) {
endTime := now.Add(time.Hour)
record := &store.ExecutionRecord{
ExecutionID: "exec_convert_002",
RobotID: "robot_convert_002",
MemberID: "member_convert_002",
TeamID: "team_convert_002",
JobID: "job_convert_002",
@ -702,7 +684,6 @@ func setupTestExecution(t *testing.T, s *store.ExecutionStore, ctx context.Conte
record := &store.ExecutionRecord{
ExecutionID: "exec_test_get_001",
RobotID: "robot_config_get",
MemberID: "member_test_get",
TeamID: "team_test_get",
JobID: "job_test_get",
@ -743,7 +724,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
records := []*store.ExecutionRecord{
{
ExecutionID: "exec_test_list_001",
RobotID: "robot_list_001",
MemberID: "member_list_001",
TeamID: "team_list_001",
TriggerType: types.TriggerClock,
@ -753,7 +733,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
},
{
ExecutionID: "exec_test_list_002",
RobotID: "robot_list_001",
MemberID: "member_list_001",
TeamID: "team_list_001",
TriggerType: types.TriggerClock,
@ -763,7 +742,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
},
{
ExecutionID: "exec_test_list_003",
RobotID: "robot_list_002",
MemberID: "member_list_002",
TeamID: "team_list_001",
TriggerType: types.TriggerHuman,
@ -773,7 +751,6 @@ func setupTestExecutionsForList(t *testing.T, s *store.ExecutionStore, ctx conte
},
{
ExecutionID: "exec_test_list_004",
RobotID: "robot_list_002",
MemberID: "member_list_002",
TeamID: "team_list_002",
TriggerType: types.TriggerEvent,

File diff suppressed because it is too large Load diff

View file

@ -24,15 +24,6 @@
"unique": true,
"index": true
},
{
"name": "robot_id",
"type": "string",
"label": "Robot ID",
"comment": "Robot configuration ID (from robot_config)",
"length": 64,
"nullable": true,
"index": true
},
{
"name": "member_id",
"type": "string",
@ -197,10 +188,10 @@
"comment": "Index for trigger type analysis"
},
{
"name": "idx_agent_execution_robot_start",
"columns": ["robot_id", "start_time"],
"name": "idx_agent_execution_member_start",
"columns": ["member_id", "start_time"],
"type": "index",
"comment": "Index for robot execution history"
"comment": "Index for robot execution history by member"
}
],
"option": { "timestamps": true, "soft_deletes": false, "permission": true }