Enhance Robot Configuration with Agents and MCP Servers Integration
- Updated database queries to include 'agents' and 'mcp_servers' fields in robot data retrieval. - Enhanced the robot configuration structure to merge agents and MCP servers from the member table into the robot's resources. - Improved the input formatter to display time markers with check/cross indicators for better context awareness. - Added tests to validate the new functionality and ensure proper formatting of robot identity when identity is nil.
This commit is contained in:
parent
6e68b42128
commit
0f8287a51b
6 changed files with 182 additions and 31 deletions
|
|
@ -166,7 +166,7 @@ func loadRobotFromDB(memberID string) (*types.Robot, error) {
|
|||
Select: []interface{}{
|
||||
"id", "member_id", "team_id", "display_name", "bio",
|
||||
"system_prompt", "robot_status", "autonomous_mode",
|
||||
"robot_config", "robot_email",
|
||||
"robot_config", "robot_email", "agents", "mcp_servers",
|
||||
},
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "member_id", Value: memberID},
|
||||
|
|
@ -228,7 +228,7 @@ func listRobotsFromDB(query *ListQuery) (*ListResult, error) {
|
|||
Select: []interface{}{
|
||||
"id", "member_id", "team_id", "display_name", "bio",
|
||||
"system_prompt", "robot_status", "autonomous_mode",
|
||||
"robot_config", "robot_email",
|
||||
"robot_config", "robot_email", "agents", "mcp_servers",
|
||||
},
|
||||
Wheres: wheres,
|
||||
Orders: orders,
|
||||
|
|
|
|||
2
agent/robot/cache/load.go
vendored
2
agent/robot/cache/load.go
vendored
|
|
@ -24,6 +24,8 @@ var memberFields = []interface{}{
|
|||
"autonomous_mode",
|
||||
"robot_config",
|
||||
"robot_email",
|
||||
"agents",
|
||||
"mcp_servers",
|
||||
}
|
||||
|
||||
// SetMemberModel sets the member model name
|
||||
|
|
|
|||
|
|
@ -43,40 +43,47 @@ func (f *InputFormatter) FormatClockContext(clock *robottypes.ClockContext, robo
|
|||
sb.WriteString(fmt.Sprintf("- **Day**: %s\n", clock.DayOfWeek))
|
||||
sb.WriteString(fmt.Sprintf("- **Date**: %d/%d/%d\n", clock.Year, clock.Month, clock.DayOfMonth))
|
||||
sb.WriteString(fmt.Sprintf("- **Week**: %d of year\n", clock.WeekOfYear))
|
||||
sb.WriteString(fmt.Sprintf("- **Hour**: %d\n", clock.Hour))
|
||||
sb.WriteString(fmt.Sprintf("- **Timezone**: %s\n", clock.TZ))
|
||||
|
||||
// Time markers
|
||||
// Time markers - show all markers with check/cross for context awareness
|
||||
sb.WriteString("\n### Time Markers\n")
|
||||
if clock.IsWeekend {
|
||||
sb.WriteString("- ✓ Weekend\n")
|
||||
}
|
||||
if clock.IsMonthStart {
|
||||
sb.WriteString("- ✓ Month Start (1st-3rd)\n")
|
||||
}
|
||||
if clock.IsMonthEnd {
|
||||
sb.WriteString("- ✓ Month End (last 3 days)\n")
|
||||
}
|
||||
if clock.IsQuarterEnd {
|
||||
sb.WriteString("- ✓ Quarter End\n")
|
||||
}
|
||||
if clock.IsYearEnd {
|
||||
sb.WriteString("- ✓ Year End\n")
|
||||
}
|
||||
sb.WriteString(fmt.Sprintf("- %s Weekend\n", boolMark(clock.IsWeekend)))
|
||||
sb.WriteString(fmt.Sprintf("- %s Month Start (1st-3rd)\n", boolMark(clock.IsMonthStart)))
|
||||
sb.WriteString(fmt.Sprintf("- %s Month End (last 3 days)\n", boolMark(clock.IsMonthEnd)))
|
||||
sb.WriteString(fmt.Sprintf("- %s Quarter End\n", boolMark(clock.IsQuarterEnd)))
|
||||
sb.WriteString(fmt.Sprintf("- %s Year End\n", boolMark(clock.IsYearEnd)))
|
||||
|
||||
// Robot identity section (if available)
|
||||
if robot != nil && robot.Config != nil && robot.Config.Identity != nil {
|
||||
sb.WriteString("\n## Robot Identity\n\n")
|
||||
sb.WriteString(fmt.Sprintf("- **Role**: %s\n", robot.Config.Identity.Role))
|
||||
if len(robot.Config.Identity.Duties) > 0 {
|
||||
sb.WriteString("- **Duties**:\n")
|
||||
for _, duty := range robot.Config.Identity.Duties {
|
||||
sb.WriteString(fmt.Sprintf(" - %s\n", duty))
|
||||
// Robot identity section
|
||||
// Priority: Config.Identity > Robot fields (DisplayName, Bio, SystemPrompt)
|
||||
if robot != nil {
|
||||
if robot.Config != nil && robot.Config.Identity != nil {
|
||||
// Use structured identity from config
|
||||
sb.WriteString("\n## Robot Identity\n\n")
|
||||
sb.WriteString(fmt.Sprintf("- **Role**: %s\n", robot.Config.Identity.Role))
|
||||
if len(robot.Config.Identity.Duties) > 0 {
|
||||
sb.WriteString("- **Duties**:\n")
|
||||
for _, duty := range robot.Config.Identity.Duties {
|
||||
sb.WriteString(fmt.Sprintf(" - %s\n", duty))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(robot.Config.Identity.Rules) > 0 {
|
||||
sb.WriteString("- **Rules**:\n")
|
||||
for _, rule := range robot.Config.Identity.Rules {
|
||||
sb.WriteString(fmt.Sprintf(" - %s\n", rule))
|
||||
if len(robot.Config.Identity.Rules) > 0 {
|
||||
sb.WriteString("- **Rules**:\n")
|
||||
for _, rule := range robot.Config.Identity.Rules {
|
||||
sb.WriteString(fmt.Sprintf(" - %s\n", rule))
|
||||
}
|
||||
}
|
||||
} else if robot.DisplayName != "" || robot.Bio != "" || robot.SystemPrompt != "" {
|
||||
// Fallback: build identity from Robot fields (from __yao.member table)
|
||||
sb.WriteString("\n## Robot Identity\n\n")
|
||||
if robot.DisplayName != "" {
|
||||
sb.WriteString(fmt.Sprintf("- **Role**: %s\n", robot.DisplayName))
|
||||
}
|
||||
if robot.Bio != "" {
|
||||
sb.WriteString(fmt.Sprintf("- **Description**: %s\n", robot.Bio))
|
||||
}
|
||||
if robot.SystemPrompt != "" {
|
||||
sb.WriteString(fmt.Sprintf("- **Instructions**:\n%s\n", robot.SystemPrompt))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -84,6 +91,14 @@ func (f *InputFormatter) FormatClockContext(clock *robottypes.ClockContext, robo
|
|||
return sb.String()
|
||||
}
|
||||
|
||||
// boolMark returns ✓ for true and ✗ for false
|
||||
func boolMark(v bool) string {
|
||||
if v {
|
||||
return "✓"
|
||||
}
|
||||
return "✗"
|
||||
}
|
||||
|
||||
// FormatRobotIdentity formats robot identity as user message content
|
||||
// Used to provide context about the robot's role and duties
|
||||
func (f *InputFormatter) FormatRobotIdentity(robot *robottypes.Robot) string {
|
||||
|
|
|
|||
|
|
@ -28,9 +28,25 @@ func TestInputFormatterFormatClockContext(t *testing.T) {
|
|||
assert.Contains(t, result, "2024-01-15 09:30:00")
|
||||
assert.Contains(t, result, "Monday")
|
||||
assert.Contains(t, result, "UTC")
|
||||
assert.Contains(t, result, "**Hour**: 9")
|
||||
assert.Contains(t, result, "### Time Markers")
|
||||
})
|
||||
|
||||
t.Run("shows all time markers with check/cross", func(t *testing.T) {
|
||||
// Regular weekday, not month start/end
|
||||
now := time.Date(2024, 1, 15, 14, 0, 0, 0, time.UTC)
|
||||
clock := types.NewClockContext(now, "UTC")
|
||||
|
||||
result := formatter.FormatClockContext(clock, nil)
|
||||
|
||||
// Should show all markers, even when false
|
||||
assert.Contains(t, result, "✗ Weekend")
|
||||
assert.Contains(t, result, "✗ Month Start")
|
||||
assert.Contains(t, result, "✗ Month End")
|
||||
assert.Contains(t, result, "✗ Quarter End")
|
||||
assert.Contains(t, result, "✗ Year End")
|
||||
})
|
||||
|
||||
t.Run("includes robot identity when provided", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
clock := types.NewClockContext(now, "UTC")
|
||||
|
|
@ -58,6 +74,26 @@ func TestInputFormatterFormatClockContext(t *testing.T) {
|
|||
assert.Empty(t, result)
|
||||
})
|
||||
|
||||
t.Run("uses DisplayName/Bio/SystemPrompt when Identity is nil", func(t *testing.T) {
|
||||
now := time.Now()
|
||||
clock := types.NewClockContext(now, "UTC")
|
||||
robot := &types.Robot{
|
||||
MemberID: "test-robot",
|
||||
DisplayName: "SEO Specialist",
|
||||
Bio: "Focuses on content optimization",
|
||||
SystemPrompt: "You are an SEO assistant.\n\n## Core Duties\n- Analyze keywords",
|
||||
Config: &types.Config{}, // Identity is nil
|
||||
}
|
||||
|
||||
result := formatter.FormatClockContext(clock, robot)
|
||||
|
||||
assert.Contains(t, result, "## Robot Identity")
|
||||
assert.Contains(t, result, "SEO Specialist")
|
||||
assert.Contains(t, result, "Focuses on content optimization")
|
||||
assert.Contains(t, result, "SEO assistant")
|
||||
assert.Contains(t, result, "Analyze keywords")
|
||||
})
|
||||
|
||||
t.Run("marks weekend correctly", func(t *testing.T) {
|
||||
// Saturday
|
||||
saturday := time.Date(2024, 1, 13, 10, 0, 0, 0, time.UTC)
|
||||
|
|
|
|||
|
|
@ -614,9 +614,59 @@ func (r *RobotRecord) ToRobot() (*types.Robot, error) {
|
|||
robot.Config = config
|
||||
}
|
||||
|
||||
// Ensure Config exists for merging agents/mcp_servers
|
||||
if robot.Config == nil {
|
||||
robot.Config = &types.Config{}
|
||||
}
|
||||
if robot.Config.Resources == nil {
|
||||
robot.Config.Resources = &types.Resources{}
|
||||
}
|
||||
|
||||
// Merge agents from member table into Config.Resources.Agents
|
||||
if r.Agents != nil {
|
||||
agents := parseStringSlice(r.Agents)
|
||||
if len(agents) > 0 {
|
||||
robot.Config.Resources.Agents = agents
|
||||
}
|
||||
}
|
||||
|
||||
// Merge mcp_servers from member table into Config.Resources.MCP
|
||||
if r.MCPServers != nil {
|
||||
mcpServers := parseStringSlice(r.MCPServers)
|
||||
if len(mcpServers) > 0 {
|
||||
// Convert string slice to MCPConfig slice (each server ID becomes an MCPConfig)
|
||||
for _, serverID := range mcpServers {
|
||||
robot.Config.Resources.MCP = append(robot.Config.Resources.MCP, types.MCPConfig{
|
||||
ID: serverID,
|
||||
// Tools empty means all tools available
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return robot, nil
|
||||
}
|
||||
|
||||
// parseStringSlice converts interface{} to []string
|
||||
func parseStringSlice(v interface{}) []string {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case []string:
|
||||
return val
|
||||
case []interface{}:
|
||||
result := make([]string, 0, len(val))
|
||||
for _, item := range val {
|
||||
if s, ok := item.(string); ok {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// FromRobot creates a RobotRecord from types.Robot
|
||||
func FromRobot(robot *types.Robot) *RobotRecord {
|
||||
record := &RobotRecord{
|
||||
|
|
|
|||
|
|
@ -407,9 +407,57 @@ func NewRobotFromMap(m map[string]interface{}) (*Robot, error) {
|
|||
robot.Config = config
|
||||
}
|
||||
|
||||
// Ensure Config exists for merging agents/mcp_servers
|
||||
if robot.Config == nil {
|
||||
robot.Config = &Config{}
|
||||
}
|
||||
if robot.Config.Resources == nil {
|
||||
robot.Config.Resources = &Resources{}
|
||||
}
|
||||
|
||||
// Merge agents from member table into Config.Resources.Agents
|
||||
if agentsData, ok := m["agents"]; ok && agentsData != nil {
|
||||
agents := getStringSlice(agentsData)
|
||||
if len(agents) > 0 {
|
||||
robot.Config.Resources.Agents = agents
|
||||
}
|
||||
}
|
||||
|
||||
// Merge mcp_servers from member table into Config.Resources.MCP
|
||||
if mcpData, ok := m["mcp_servers"]; ok && mcpData != nil {
|
||||
mcpServers := getStringSlice(mcpData)
|
||||
if len(mcpServers) > 0 {
|
||||
for _, serverID := range mcpServers {
|
||||
robot.Config.Resources.MCP = append(robot.Config.Resources.MCP, MCPConfig{
|
||||
ID: serverID,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return robot, nil
|
||||
}
|
||||
|
||||
// getStringSlice converts interface{} to []string
|
||||
func getStringSlice(v interface{}) []string {
|
||||
if v == nil {
|
||||
return nil
|
||||
}
|
||||
switch val := v.(type) {
|
||||
case []string:
|
||||
return val
|
||||
case []interface{}:
|
||||
result := make([]string, 0, len(val))
|
||||
for _, item := range val {
|
||||
if s, ok := item.(string); ok {
|
||||
result = append(result, s)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// getString safely gets a string value from map
|
||||
func getString(m map[string]interface{}, key string) string {
|
||||
if m == nil {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue