feat(robot): add workspace support to robot management
- Introduced a new `workspace` field across various robot-related structures, including `CreateRobotRequest`, `UpdateRobotRequest`, and `RobotResponse`, allowing for better organization and management of robots within specific workspaces. - Updated database queries and response mappings to accommodate the new workspace field, ensuring seamless integration with existing functionalities. - Enhanced agent execution context to include workspace information, improving the contextual awareness of agents during operations. - Added tests to validate the creation and updating of robots with workspace data, ensuring robust functionality and backward compatibility.
This commit is contained in:
parent
79cd95e6cc
commit
8fdd1a3a6c
26 changed files with 538 additions and 392 deletions
|
|
@ -199,7 +199,7 @@ func loadRobotFromDB(memberID string) (*types.Robot, error) {
|
||||||
"id", "member_id", "team_id", "display_name", "bio",
|
"id", "member_id", "team_id", "display_name", "bio",
|
||||||
"system_prompt", "robot_status", "autonomous_mode",
|
"system_prompt", "robot_status", "autonomous_mode",
|
||||||
"robot_config", "robot_email", "agents", "mcp_servers",
|
"robot_config", "robot_email", "agents", "mcp_servers",
|
||||||
"manager_id", "language_model",
|
"manager_id", "language_model", "workspace",
|
||||||
},
|
},
|
||||||
Wheres: []model.QueryWhere{
|
Wheres: []model.QueryWhere{
|
||||||
{Column: "member_id", Value: memberID},
|
{Column: "member_id", Value: memberID},
|
||||||
|
|
@ -268,7 +268,7 @@ func ListRobotsFromDB(query *ListQuery) (*ListResult, error) {
|
||||||
"id", "member_id", "team_id", "display_name", "bio",
|
"id", "member_id", "team_id", "display_name", "bio",
|
||||||
"system_prompt", "robot_status", "autonomous_mode",
|
"system_prompt", "robot_status", "autonomous_mode",
|
||||||
"robot_config", "robot_email", "agents", "mcp_servers",
|
"robot_config", "robot_email", "agents", "mcp_servers",
|
||||||
"language_model",
|
"language_model", "workspace",
|
||||||
},
|
},
|
||||||
Wheres: wheres,
|
Wheres: wheres,
|
||||||
Orders: orders,
|
Orders: orders,
|
||||||
|
|
@ -444,6 +444,7 @@ func CreateRobot(ctx *types.Context, req *CreateRobotRequest) (*RobotResponse, e
|
||||||
Agents: req.Agents,
|
Agents: req.Agents,
|
||||||
MCPServers: req.MCPServers,
|
MCPServers: req.MCPServers,
|
||||||
LanguageModel: req.LanguageModel,
|
LanguageModel: req.LanguageModel,
|
||||||
|
Workspace: req.Workspace,
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit: req.CostLimit,
|
CostLimit: req.CostLimit,
|
||||||
|
|
@ -559,6 +560,9 @@ func UpdateRobot(ctx *types.Context, memberID string, req *UpdateRobotRequest) (
|
||||||
if req.LanguageModel != nil {
|
if req.LanguageModel != nil {
|
||||||
existing.LanguageModel = *req.LanguageModel
|
existing.LanguageModel = *req.LanguageModel
|
||||||
}
|
}
|
||||||
|
if req.Workspace != nil {
|
||||||
|
existing.Workspace = *req.Workspace
|
||||||
|
}
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
if req.CostLimit != nil {
|
if req.CostLimit != nil {
|
||||||
|
|
@ -684,6 +688,7 @@ func recordToResponse(record *store.RobotRecord) *RobotResponse {
|
||||||
Agents: record.Agents,
|
Agents: record.Agents,
|
||||||
MCPServers: record.MCPServers,
|
MCPServers: record.MCPServers,
|
||||||
LanguageModel: record.LanguageModel,
|
LanguageModel: record.LanguageModel,
|
||||||
|
Workspace: record.Workspace,
|
||||||
|
|
||||||
CostLimit: record.CostLimit,
|
CostLimit: record.CostLimit,
|
||||||
InvitedBy: record.InvitedBy,
|
InvitedBy: record.InvitedBy,
|
||||||
|
|
|
||||||
|
|
@ -144,7 +144,7 @@ type CreateRobotRequest struct {
|
||||||
AutonomousMode *bool `json:"autonomous_mode,omitempty"` // Whether autonomous mode is enabled
|
AutonomousMode *bool `json:"autonomous_mode,omitempty"` // Whether autonomous mode is enabled
|
||||||
|
|
||||||
// Communication
|
// Communication
|
||||||
RobotEmail string `json:"robot_email,omitempty"` // Robot email address
|
RobotEmail string `json:"robot_email,omitempty"` // Deprecated: Robot email address
|
||||||
AuthorizedSenders interface{} `json:"authorized_senders,omitempty"` // Email whitelist (JSON array)
|
AuthorizedSenders interface{} `json:"authorized_senders,omitempty"` // Email whitelist (JSON array)
|
||||||
EmailFilterRules interface{} `json:"email_filter_rules,omitempty"` // Email filter rules (JSON array)
|
EmailFilterRules interface{} `json:"email_filter_rules,omitempty"` // Email filter rules (JSON array)
|
||||||
|
|
||||||
|
|
@ -153,6 +153,7 @@ type CreateRobotRequest struct {
|
||||||
Agents interface{} `json:"agents,omitempty"` // Accessible agents (JSON array)
|
Agents interface{} `json:"agents,omitempty"` // Accessible agents (JSON array)
|
||||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
||||||
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
||||||
|
Workspace string `json:"workspace,omitempty"` // Workspace ID bound to this robot
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||||
|
|
@ -179,7 +180,7 @@ type UpdateRobotRequest struct {
|
||||||
AutonomousMode *bool `json:"autonomous_mode,omitempty"` // Autonomous mode
|
AutonomousMode *bool `json:"autonomous_mode,omitempty"` // Autonomous mode
|
||||||
|
|
||||||
// Communication
|
// Communication
|
||||||
RobotEmail *string `json:"robot_email,omitempty"` // Robot email address
|
RobotEmail *string `json:"robot_email,omitempty"` // Deprecated: Robot email address
|
||||||
AuthorizedSenders interface{} `json:"authorized_senders,omitempty"` // Email whitelist
|
AuthorizedSenders interface{} `json:"authorized_senders,omitempty"` // Email whitelist
|
||||||
EmailFilterRules interface{} `json:"email_filter_rules,omitempty"` // Email filter rules
|
EmailFilterRules interface{} `json:"email_filter_rules,omitempty"` // Email filter rules
|
||||||
|
|
||||||
|
|
@ -188,6 +189,7 @@ type UpdateRobotRequest struct {
|
||||||
Agents interface{} `json:"agents,omitempty"` // Accessible agents
|
Agents interface{} `json:"agents,omitempty"` // Accessible agents
|
||||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers
|
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers
|
||||||
LanguageModel *string `json:"language_model,omitempty"` // Language model name
|
LanguageModel *string `json:"language_model,omitempty"` // Language model name
|
||||||
|
Workspace *string `json:"workspace,omitempty"` // Workspace ID (nil=no change, ""=unbind)
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit *float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
CostLimit *float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||||
|
|
@ -226,6 +228,7 @@ type RobotResponse struct {
|
||||||
Agents interface{} `json:"agents,omitempty"`
|
Agents interface{} `json:"agents,omitempty"`
|
||||||
MCPServers interface{} `json:"mcp_servers,omitempty"`
|
MCPServers interface{} `json:"mcp_servers,omitempty"`
|
||||||
LanguageModel string `json:"language_model,omitempty"`
|
LanguageModel string `json:"language_model,omitempty"`
|
||||||
|
Workspace string `json:"workspace,omitempty"`
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit float64 `json:"cost_limit,omitempty"`
|
CostLimit float64 `json:"cost_limit,omitempty"`
|
||||||
|
|
|
||||||
1
agent/robot/cache/load.go
vendored
1
agent/robot/cache/load.go
vendored
|
|
@ -28,6 +28,7 @@ var memberFields = []interface{}{
|
||||||
"mcp_servers",
|
"mcp_servers",
|
||||||
"manager_id",
|
"manager_id",
|
||||||
"language_model",
|
"language_model",
|
||||||
|
"workspace",
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetMemberModel sets the member model name
|
// SetMemberModel sets the member model name
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,10 @@ type AgentCaller struct {
|
||||||
// When non-empty, passed as opts.Connector to ast.Stream so the agent uses the Robot's model.
|
// When non-empty, passed as opts.Connector to ast.Stream so the agent uses the Robot's model.
|
||||||
Connector string
|
Connector string
|
||||||
|
|
||||||
|
// Workspace is the workspace ID bound to the Robot.
|
||||||
|
// When non-empty, injected into agentCtx.Metadata["workspace_id"] for sandbox node resolution.
|
||||||
|
Workspace string
|
||||||
|
|
||||||
// log is an optional structured logger; when set, Call emits agent-call logs.
|
// log is an optional structured logger; when set, Call emits agent-call logs.
|
||||||
log *execLogger
|
log *execLogger
|
||||||
}
|
}
|
||||||
|
|
@ -444,6 +448,13 @@ func (c *AgentCaller) buildAgentContext(ctx *robottypes.Context, assistantID str
|
||||||
}
|
}
|
||||||
agentCtx.Logger = agentcontext.Noop()
|
agentCtx.Logger = agentcontext.Noop()
|
||||||
|
|
||||||
|
if c.Workspace != "" {
|
||||||
|
if agentCtx.Metadata == nil {
|
||||||
|
agentCtx.Metadata = map[string]interface{}{}
|
||||||
|
}
|
||||||
|
agentCtx.Metadata["workspace_id"] = c.Workspace
|
||||||
|
}
|
||||||
|
|
||||||
kunlog.Trace("[robot-agent] context built: assistantID=%s chatID=%s contextID=%s", assistantID, c.ChatID, agentCtx.ID)
|
kunlog.Trace("[robot-agent] context built: assistantID=%s chatID=%s contextID=%s", assistantID, c.ChatID, agentCtx.ID)
|
||||||
return agentCtx
|
return agentCtx
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,7 @@ func (e *Executor) RunDelivery(ctx *robottypes.Context, exec *robottypes.Executi
|
||||||
|
|
||||||
caller := NewAgentCaller()
|
caller := NewAgentCaller()
|
||||||
caller.Connector = robot.LanguageModel
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("delivery agent (%s) call failed: %w", agentID, err)
|
return fmt.Errorf("delivery agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,7 @@ func (e *Executor) RunGoals(ctx *robottypes.Context, exec *robottypes.Execution,
|
||||||
// Call agent
|
// Call agent
|
||||||
caller := NewAgentCaller()
|
caller := NewAgentCaller()
|
||||||
caller.Connector = robot.LanguageModel
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("goals agent (%s) call failed: %w", agentID, err)
|
return fmt.Errorf("goals agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,8 @@ func (e *Executor) CallHostAgent(ctx *robottypes.Context, robot *robottypes.Robo
|
||||||
kunlog.Info("calling Host Agent %s for scenario=%s chatID=%s", agentID, input.Scenario, chatID)
|
kunlog.Info("calling Host Agent %s for scenario=%s chatID=%s", agentID, input.Scenario, chatID)
|
||||||
|
|
||||||
caller := NewConversationCaller(chatID)
|
caller := NewConversationCaller(chatID)
|
||||||
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessages(ctx, agentID, string(inputJSON))
|
result, err := caller.CallWithMessages(ctx, agentID, string(inputJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
|
||||||
|
|
@ -54,6 +54,7 @@ func (e *Executor) RunInspiration(ctx *robottypes.Context, exec *robottypes.Exec
|
||||||
// Call agent
|
// Call agent
|
||||||
caller := NewAgentCaller()
|
caller := NewAgentCaller()
|
||||||
caller.Connector = robot.LanguageModel
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("inspiration agent (%s) call failed: %w", agentID, err)
|
return fmt.Errorf("inspiration agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
|
||||||
|
|
@ -37,6 +37,13 @@ func (l *execLogger) connector() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (l *execLogger) workspace() string {
|
||||||
|
if l.robot != nil {
|
||||||
|
return l.robot.Workspace
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
// P2: Task Overview
|
// P2: Task Overview
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|
@ -51,6 +58,7 @@ func (l *execLogger) logTaskOverview(tasks []robottypes.Task) {
|
||||||
"phase": "tasks",
|
"phase": "tasks",
|
||||||
"task_count": len(tasks),
|
"task_count": len(tasks),
|
||||||
"language_model": l.connector(),
|
"language_model": l.connector(),
|
||||||
|
"workspace": l.workspace(),
|
||||||
}).Info("P2 task overview: %d tasks generated", len(tasks))
|
}).Info("P2 task overview: %d tasks generated", len(tasks))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -69,6 +77,9 @@ func (l *execLogger) devTaskOverview(tasks []robottypes.Task) {
|
||||||
if l.connector() != "" {
|
if l.connector() != "" {
|
||||||
sb.WriteString(fmt.Sprintf("%s Model: %s%s%s\n", w, v, l.connector(), r))
|
sb.WriteString(fmt.Sprintf("%s Model: %s%s%s\n", w, v, l.connector(), r))
|
||||||
}
|
}
|
||||||
|
if l.workspace() != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf("%s Workspace: %s%s%s\n", w, v, l.workspace(), r))
|
||||||
|
}
|
||||||
sb.WriteString(fmt.Sprintf("%s%s%s\n", w, strings.Repeat("─", 60), r))
|
sb.WriteString(fmt.Sprintf("%s%s%s\n", w, strings.Repeat("─", 60), r))
|
||||||
for i, t := range tasks {
|
for i, t := range tasks {
|
||||||
desc := t.Description
|
desc := t.Description
|
||||||
|
|
|
||||||
|
|
@ -134,6 +134,7 @@ func (r *Runner) executeAssistantTask(task *robottypes.Task, taskCtx *RunnerCont
|
||||||
caller := NewAgentCaller()
|
caller := NewAgentCaller()
|
||||||
caller.log = r.log
|
caller.log = r.log
|
||||||
caller.Connector = r.robot.LanguageModel
|
caller.Connector = r.robot.LanguageModel
|
||||||
|
caller.Workspace = r.robot.Workspace
|
||||||
caller.ChatID = r.chatID
|
caller.ChatID = r.chatID
|
||||||
|
|
||||||
messages := r.BuildAssistantMessages(task, taskCtx)
|
messages := r.BuildAssistantMessages(task, taskCtx)
|
||||||
|
|
|
||||||
|
|
@ -55,6 +55,7 @@ func (e *Executor) RunTasks(ctx *robottypes.Context, exec *robottypes.Execution,
|
||||||
caller := NewAgentCaller()
|
caller := NewAgentCaller()
|
||||||
caller.log = newExecLogger(robot, exec.ID)
|
caller.log = newExecLogger(robot, exec.ID)
|
||||||
caller.Connector = robot.LanguageModel
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("tasks agent (%s) call failed: %w", agentID, err)
|
return fmt.Errorf("tasks agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
|
||||||
|
|
@ -434,6 +434,7 @@ func (v *Validator) validateSemantic(task *robottypes.Task, output interface{})
|
||||||
// Call validation agent
|
// Call validation agent
|
||||||
caller := NewAgentCaller()
|
caller := NewAgentCaller()
|
||||||
caller.Connector = v.robot.LanguageModel
|
caller.Connector = v.robot.LanguageModel
|
||||||
|
caller.Workspace = v.robot.Workspace
|
||||||
result, err := caller.CallWithMessages(v.ctx, validationAgentID, validationPrompt)
|
result, err := caller.CallWithMessages(v.ctx, validationAgentID, validationPrompt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &robottypes.ValidationResult{
|
return &robottypes.ValidationResult{
|
||||||
|
|
@ -641,6 +642,7 @@ func (av *robotAgentValidator) Validate(agentID string, output, input, criteria
|
||||||
// Call agent
|
// Call agent
|
||||||
caller := NewAgentCaller()
|
caller := NewAgentCaller()
|
||||||
caller.Connector = av.v.robot.LanguageModel
|
caller.Connector = av.v.robot.LanguageModel
|
||||||
|
caller.Workspace = av.v.robot.Workspace
|
||||||
callResult, err := caller.CallWithMessages(av.v.ctx, agentID, string(inputJSON))
|
callResult, err := caller.CallWithMessages(av.v.ctx, agentID, string(inputJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
result.Passed = false
|
result.Passed = false
|
||||||
|
|
|
||||||
|
|
@ -326,17 +326,19 @@ func (m *Manager) callHostAgentForScenario(ctx *types.Context, robot *types.Robo
|
||||||
Scenario: scenario,
|
Scenario: scenario,
|
||||||
Messages: []agentcontext.Message{{Role: "user", Content: message}},
|
Messages: []agentcontext.Message{{Role: "user", Content: message}},
|
||||||
Context: hostCtx,
|
Context: hostCtx,
|
||||||
}, chatID)
|
}, chatID, robot)
|
||||||
}
|
}
|
||||||
|
|
||||||
// callHostAgent calls the Host Agent assistant and parses output.
|
// callHostAgent calls the Host Agent assistant and parses output.
|
||||||
func (m *Manager) callHostAgent(ctx *types.Context, agentID string, input *types.HostInput, chatID string) (*types.HostOutput, error) {
|
func (m *Manager) callHostAgent(ctx *types.Context, agentID string, input *types.HostInput, chatID string, robot *types.Robot) (*types.HostOutput, error) {
|
||||||
inputJSON, err := json.Marshal(input)
|
inputJSON, err := json.Marshal(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
caller := standard.NewConversationCaller(chatID)
|
caller := standard.NewConversationCaller(chatID)
|
||||||
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessages(ctx, agentID, string(inputJSON))
|
result, err := caller.CallWithMessages(ctx, agentID, string(inputJSON))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
@ -728,16 +730,18 @@ func (m *Manager) callHostAgentForScenarioStream(ctx *types.Context, robot *type
|
||||||
Scenario: scenario,
|
Scenario: scenario,
|
||||||
Messages: []agentcontext.Message{{Role: "user", Content: msg}},
|
Messages: []agentcontext.Message{{Role: "user", Content: msg}},
|
||||||
Context: hostCtx,
|
Context: hostCtx,
|
||||||
}, chatID, streamFn)
|
}, chatID, robot, streamFn)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Manager) callHostAgentStream(ctx *types.Context, agentID string, input *types.HostInput, chatID string, streamFn standard.StreamCallback) (*types.HostOutput, error) {
|
func (m *Manager) callHostAgentStream(ctx *types.Context, agentID string, input *types.HostInput, chatID string, robot *types.Robot, streamFn standard.StreamCallback) (*types.HostOutput, error) {
|
||||||
inputJSON, err := json.Marshal(input)
|
inputJSON, err := json.Marshal(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
caller := standard.NewConversationCaller(chatID)
|
caller := standard.NewConversationCaller(chatID)
|
||||||
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessagesStream(ctx, agentID, string(inputJSON), streamFn)
|
result, err := caller.CallWithMessagesStream(ctx, agentID, string(inputJSON), streamFn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
@ -895,7 +899,7 @@ func (m *Manager) callHostAgentForScenarioStreamRaw(ctx *types.Context, robot *t
|
||||||
Scenario: scenario,
|
Scenario: scenario,
|
||||||
Messages: []agentcontext.Message{{Role: "user", Content: msg}},
|
Messages: []agentcontext.Message{{Role: "user", Content: msg}},
|
||||||
Context: hostCtx,
|
Context: hostCtx,
|
||||||
}, chatID, onMessage)
|
}, chatID, robot, onMessage)
|
||||||
}
|
}
|
||||||
|
|
||||||
// callHostAgentStreamRaw calls the Host Agent with CUI raw message streaming.
|
// callHostAgentStreamRaw calls the Host Agent with CUI raw message streaming.
|
||||||
|
|
@ -903,7 +907,7 @@ func (m *Manager) callHostAgentForScenarioStreamRaw(ctx *types.Context, robot *t
|
||||||
// so the frontend never sees raw decision JSON. If the final result is a decision,
|
// so the frontend never sees raw decision JSON. If the final result is a decision,
|
||||||
// the buffered chunks are discarded and a clean reply is sent instead. If the
|
// the buffered chunks are discarded and a clean reply is sent instead. If the
|
||||||
// result is a normal conversation turn, buffered chunks are flushed through.
|
// result is a normal conversation turn, buffered chunks are flushed through.
|
||||||
func (m *Manager) callHostAgentStreamRaw(ctx *types.Context, agentID string, input *types.HostInput, chatID string, onMessage agentcontext.OnMessageFunc) (*types.HostOutput, error) {
|
func (m *Manager) callHostAgentStreamRaw(ctx *types.Context, agentID string, input *types.HostInput, chatID string, robot *types.Robot, onMessage agentcontext.OnMessageFunc) (*types.HostOutput, error) {
|
||||||
inputJSON, err := json.Marshal(input)
|
inputJSON, err := json.Marshal(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
||||||
|
|
@ -956,6 +960,8 @@ func (m *Manager) callHostAgentStreamRaw(ctx *types.Context, agentID string, inp
|
||||||
}
|
}
|
||||||
|
|
||||||
caller := standard.NewConversationCaller(chatID)
|
caller := standard.NewConversationCaller(chatID)
|
||||||
|
caller.Connector = robot.LanguageModel
|
||||||
|
caller.Workspace = robot.Workspace
|
||||||
result, err := caller.CallWithMessagesStreamRaw(ctx, agentID, string(inputJSON), wrappedOnMessage)
|
result, err := caller.CallWithMessagesStreamRaw(ctx, agentID, string(inputJSON), wrappedOnMessage)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
return nil, fmt.Errorf("host agent (%s) call failed: %w", agentID, err)
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ type RobotRecord struct {
|
||||||
ManagerID string `json:"manager_id"` // Direct manager user_id (who manages this robot)
|
ManagerID string `json:"manager_id"` // Direct manager user_id (who manages this robot)
|
||||||
|
|
||||||
// Communication
|
// Communication
|
||||||
RobotEmail string `json:"robot_email"` // Robot email address
|
RobotEmail string `json:"robot_email"` // Deprecated: Robot email address
|
||||||
AuthorizedSenders interface{} `json:"authorized_senders,omitempty"` // Email whitelist (JSON array)
|
AuthorizedSenders interface{} `json:"authorized_senders,omitempty"` // Email whitelist (JSON array)
|
||||||
EmailFilterRules interface{} `json:"email_filter_rules,omitempty"` // Email filter rules (JSON array)
|
EmailFilterRules interface{} `json:"email_filter_rules,omitempty"` // Email filter rules (JSON array)
|
||||||
|
|
||||||
|
|
@ -42,6 +42,7 @@ type RobotRecord struct {
|
||||||
Agents interface{} `json:"agents,omitempty"` // Accessible agents (JSON array)
|
Agents interface{} `json:"agents,omitempty"` // Accessible agents (JSON array)
|
||||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
||||||
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
||||||
|
Workspace string `json:"workspace,omitempty"` // Workspace ID bound to this robot
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||||
|
|
@ -117,6 +118,7 @@ var robotFields = []interface{}{
|
||||||
"agents",
|
"agents",
|
||||||
"mcp_servers",
|
"mcp_servers",
|
||||||
"language_model",
|
"language_model",
|
||||||
|
"workspace",
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
"cost_limit",
|
"cost_limit",
|
||||||
|
|
@ -435,6 +437,9 @@ func (s *RobotStore) recordToMap(record *RobotRecord) map[string]interface{} {
|
||||||
if record.LanguageModel != "" {
|
if record.LanguageModel != "" {
|
||||||
data["language_model"] = record.LanguageModel
|
data["language_model"] = record.LanguageModel
|
||||||
}
|
}
|
||||||
|
if record.Workspace != "" {
|
||||||
|
data["workspace"] = record.Workspace
|
||||||
|
}
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
if record.CostLimit > 0 {
|
if record.CostLimit > 0 {
|
||||||
|
|
@ -547,6 +552,9 @@ func (s *RobotStore) mapToRecord(row map[string]interface{}) (*RobotRecord, erro
|
||||||
if v, ok := row["language_model"].(string); ok {
|
if v, ok := row["language_model"].(string); ok {
|
||||||
record.LanguageModel = v
|
record.LanguageModel = v
|
||||||
}
|
}
|
||||||
|
if v, ok := row["workspace"].(string); ok {
|
||||||
|
record.Workspace = v
|
||||||
|
}
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
if v := row["cost_limit"]; v != nil {
|
if v := row["cost_limit"]; v != nil {
|
||||||
|
|
@ -596,6 +604,8 @@ func (r *RobotRecord) ToRobot() (*types.Robot, error) {
|
||||||
SystemPrompt: r.SystemPrompt,
|
SystemPrompt: r.SystemPrompt,
|
||||||
AutonomousMode: r.AutonomousMode,
|
AutonomousMode: r.AutonomousMode,
|
||||||
RobotEmail: r.RobotEmail,
|
RobotEmail: r.RobotEmail,
|
||||||
|
LanguageModel: r.LanguageModel,
|
||||||
|
Workspace: r.Workspace,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse robot_status
|
// Parse robot_status
|
||||||
|
|
@ -678,6 +688,8 @@ func FromRobot(robot *types.Robot) *RobotRecord {
|
||||||
RobotStatus: string(robot.Status),
|
RobotStatus: string(robot.Status),
|
||||||
AutonomousMode: robot.AutonomousMode,
|
AutonomousMode: robot.AutonomousMode,
|
||||||
RobotEmail: robot.RobotEmail,
|
RobotEmail: robot.RobotEmail,
|
||||||
|
LanguageModel: robot.LanguageModel,
|
||||||
|
Workspace: robot.Workspace,
|
||||||
MemberType: "robot",
|
MemberType: "robot",
|
||||||
Status: "active",
|
Status: "active",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -21,8 +21,9 @@ type Robot struct {
|
||||||
SystemPrompt string `json:"system_prompt"`
|
SystemPrompt string `json:"system_prompt"`
|
||||||
Status RobotStatus `json:"robot_status"`
|
Status RobotStatus `json:"robot_status"`
|
||||||
AutonomousMode bool `json:"autonomous_mode"`
|
AutonomousMode bool `json:"autonomous_mode"`
|
||||||
RobotEmail string `json:"robot_email"` // Robot's email address for sending emails
|
RobotEmail string `json:"robot_email"` // Deprecated: Robot's email address for sending emails
|
||||||
LanguageModel string `json:"language_model"` // LLM connector override (from __yao.member.language_model)
|
LanguageModel string `json:"language_model"` // LLM connector override (from __yao.member.language_model)
|
||||||
|
Workspace string `json:"workspace"` // Workspace ID bound to this robot (nullable in DB)
|
||||||
|
|
||||||
// Manager info (from __yao.member)
|
// Manager info (from __yao.member)
|
||||||
ManagerID string `json:"manager_id"` // Direct manager user_id (who manages this robot)
|
ManagerID string `json:"manager_id"` // Direct manager user_id (who manages this robot)
|
||||||
|
|
@ -509,6 +510,7 @@ func NewRobotFromMap(m map[string]interface{}) (*Robot, error) {
|
||||||
ManagerID: getString(m, "manager_id"),
|
ManagerID: getString(m, "manager_id"),
|
||||||
ManagerEmail: getString(m, "manager_email"),
|
ManagerEmail: getString(m, "manager_email"),
|
||||||
LanguageModel: getString(m, "language_model"),
|
LanguageModel: getString(m, "language_model"),
|
||||||
|
Workspace: getString(m, "workspace"),
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse robot_status
|
// Parse robot_status
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,8 @@ func ResolveNodeID(ctx *agentContext.Context, cfg *types.SandboxConfig, manager
|
||||||
if err == nil && wsNode != "" {
|
if err == nil && wsNode != "" {
|
||||||
log.Trace("[sandbox/v2] ResolveNodeID: workspace %s -> node %s", workspaceID, wsNode)
|
log.Trace("[sandbox/v2] ResolveNodeID: workspace %s -> node %s", workspaceID, wsNode)
|
||||||
computerID = wsNode
|
computerID = wsNode
|
||||||
|
} else if err != nil {
|
||||||
|
log.Warn("[sandbox/v2] ResolveNodeID: workspace %s not found or deleted, falling back to auto-select: %v", workspaceID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -132,6 +134,8 @@ func GetComputer(ctx *agentContext.Context, cfg *types.SandboxConfig, manager *i
|
||||||
log.Trace("[sandbox/v2] workspace %s bound to node %s overrides computer_id %s", workspaceID, wsNode, computerID)
|
log.Trace("[sandbox/v2] workspace %s bound to node %s overrides computer_id %s", workspaceID, wsNode, computerID)
|
||||||
}
|
}
|
||||||
computerID = wsNode
|
computerID = wsNode
|
||||||
|
} else if err != nil {
|
||||||
|
log.Warn("[sandbox/v2] GetComputer: workspace %s not found or deleted, falling back: %v", workspaceID, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ var migrateCmd = &cobra.Command{
|
||||||
exception.New(L("Migrate is not allowed on production mode."), 403).Throw()
|
exception.New(L("Migrate is not allowed on production mode."), 403).Throw()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载数据模型
|
// 仅加载 Application、DB 连接和 Model(含自动 migrate),不启动完整 Engine
|
||||||
loadWarnings, err := engine.Load(config.Conf, engine.LoadOption{Action: "migrate"})
|
loadWarnings, err := engine.LoadForMigrate(config.Conf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
fmt.Println(color.RedString(L("Fatal: %s"), err.Error()))
|
fmt.Println(color.RedString(L("Fatal: %s"), err.Error()))
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
|
|
|
||||||
750
data/bindata.go
750
data/bindata.go
File diff suppressed because one or more lines are too long
|
|
@ -95,6 +95,27 @@ func loadStep(name string, loadFunc func() error, callback func(string, string))
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// LoadForMigrate loads only the minimal modules needed for schema migration:
|
||||||
|
// application config, database connection, and models (with auto-migrate).
|
||||||
|
func LoadForMigrate(cfg config.Config) (warnings []Warning, err error) {
|
||||||
|
defer func() { err = exception.Catch(recover()) }()
|
||||||
|
exception.Mode = cfg.Mode
|
||||||
|
|
||||||
|
if err = loadApp(cfg.AppSource); err != nil {
|
||||||
|
return append(warnings, Warning{Widget: "Load Application", Error: err}), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = share.DBConnect(cfg.DB); err != nil {
|
||||||
|
return append(warnings, Warning{Widget: "DB", Error: err}), err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err = model.Load(cfg); err != nil {
|
||||||
|
warnings = append(warnings, Warning{Widget: "Model", Error: err})
|
||||||
|
}
|
||||||
|
|
||||||
|
return warnings, err
|
||||||
|
}
|
||||||
|
|
||||||
// Load application engine
|
// Load application engine
|
||||||
func Load(cfg config.Config, options LoadOption, progressCallback ...func(string, string)) (warnings []Warning, err error) {
|
func Load(cfg config.Config, options LoadOption, progressCallback ...func(string, string)) (warnings []Warning, err error) {
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ type CreateRobotRequest struct {
|
||||||
Agents interface{} `json:"agents,omitempty"` // Accessible agents (JSON array)
|
Agents interface{} `json:"agents,omitempty"` // Accessible agents (JSON array)
|
||||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
||||||
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
||||||
|
Workspace string `json:"workspace,omitempty"` // Workspace ID bound to this robot
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||||
|
|
@ -73,6 +74,7 @@ type UpdateRobotRequest struct {
|
||||||
Agents interface{} `json:"agents,omitempty"` // Accessible agents
|
Agents interface{} `json:"agents,omitempty"` // Accessible agents
|
||||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers
|
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers
|
||||||
LanguageModel *string `json:"language_model,omitempty"` // Language model name
|
LanguageModel *string `json:"language_model,omitempty"` // Language model name
|
||||||
|
Workspace *string `json:"workspace,omitempty"` // Workspace ID (nil=no change, ""=unbind)
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit *float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
CostLimit *float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||||
|
|
@ -115,6 +117,7 @@ type Response struct {
|
||||||
Agents interface{} `json:"agents,omitempty"`
|
Agents interface{} `json:"agents,omitempty"`
|
||||||
MCPServers interface{} `json:"mcp_servers,omitempty"`
|
MCPServers interface{} `json:"mcp_servers,omitempty"`
|
||||||
LanguageModel string `json:"language_model,omitempty"`
|
LanguageModel string `json:"language_model,omitempty"`
|
||||||
|
Workspace string `json:"workspace,omitempty"`
|
||||||
|
|
||||||
// Limits
|
// Limits
|
||||||
CostLimit float64 `json:"cost_limit,omitempty"`
|
CostLimit float64 `json:"cost_limit,omitempty"`
|
||||||
|
|
@ -186,6 +189,7 @@ func NewResponse(r *robotapi.RobotResponse) *Response {
|
||||||
Agents: r.Agents,
|
Agents: r.Agents,
|
||||||
MCPServers: r.MCPServers,
|
MCPServers: r.MCPServers,
|
||||||
LanguageModel: r.LanguageModel,
|
LanguageModel: r.LanguageModel,
|
||||||
|
Workspace: r.Workspace,
|
||||||
CostLimit: r.CostLimit,
|
CostLimit: r.CostLimit,
|
||||||
InvitedBy: r.InvitedBy,
|
InvitedBy: r.InvitedBy,
|
||||||
JoinedAt: r.JoinedAt,
|
JoinedAt: r.JoinedAt,
|
||||||
|
|
@ -215,6 +219,7 @@ func (r *CreateRobotRequest) ToAPICreateRequest() *robotapi.CreateRobotRequest {
|
||||||
Agents: r.Agents,
|
Agents: r.Agents,
|
||||||
MCPServers: r.MCPServers,
|
MCPServers: r.MCPServers,
|
||||||
LanguageModel: r.LanguageModel,
|
LanguageModel: r.LanguageModel,
|
||||||
|
Workspace: r.Workspace,
|
||||||
CostLimit: r.CostLimit,
|
CostLimit: r.CostLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -238,6 +243,7 @@ func (r *UpdateRobotRequest) ToAPIUpdateRequest() *robotapi.UpdateRobotRequest {
|
||||||
Agents: r.Agents,
|
Agents: r.Agents,
|
||||||
MCPServers: r.MCPServers,
|
MCPServers: r.MCPServers,
|
||||||
LanguageModel: r.LanguageModel,
|
LanguageModel: r.LanguageModel,
|
||||||
|
Workspace: r.Workspace,
|
||||||
CostLimit: r.CostLimit,
|
CostLimit: r.CostLimit,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -167,7 +167,7 @@ var (
|
||||||
"member_id", "team_id", "user_id", "member_type", "display_name", "bio", "avatar", "email", "role_id", "is_owner", "status",
|
"member_id", "team_id", "user_id", "member_type", "display_name", "bio", "avatar", "email", "role_id", "is_owner", "status",
|
||||||
"system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
"system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
||||||
"robot_config", "agents", "mcp_servers",
|
"robot_config", "agents", "mcp_servers",
|
||||||
"language_model", "cost_limit", "autonomous_mode", "last_robot_activity", "robot_status",
|
"language_model", "workspace", "cost_limit", "autonomous_mode", "last_robot_activity", "robot_status",
|
||||||
"invitation_id", "invited_by", "invited_at", "joined_at", "invitation_token",
|
"invitation_id", "invited_by", "invited_at", "joined_at", "invitation_token",
|
||||||
"invitation_expires_at", "last_active_at",
|
"invitation_expires_at", "last_active_at",
|
||||||
"login_count", "notes", "metadata", "created_at", "updated_at",
|
"login_count", "notes", "metadata", "created_at", "updated_at",
|
||||||
|
|
|
||||||
|
|
@ -362,7 +362,7 @@ func (u *DefaultUser) CreateRobotMember(ctx context.Context, teamID string, robo
|
||||||
robotFields := []string{
|
robotFields := []string{
|
||||||
"role_id", "system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
"role_id", "system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
||||||
"robot_config", "agents", "mcp_servers",
|
"robot_config", "agents", "mcp_servers",
|
||||||
"language_model", "cost_limit", "autonomous_mode", "robot_status",
|
"language_model", "workspace", "cost_limit", "autonomous_mode", "robot_status",
|
||||||
"notes", "metadata",
|
"notes", "metadata",
|
||||||
"__yao_created_by", "__yao_updated_by", "__yao_team_id", "__yao_tenant_id",
|
"__yao_created_by", "__yao_updated_by", "__yao_team_id", "__yao_tenant_id",
|
||||||
}
|
}
|
||||||
|
|
@ -444,7 +444,7 @@ func (u *DefaultUser) UpdateRobotMember(ctx context.Context, memberID string, ro
|
||||||
robotFields := []string{
|
robotFields := []string{
|
||||||
"role_id", "system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
"role_id", "system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
||||||
"robot_config", "agents", "mcp_servers",
|
"robot_config", "agents", "mcp_servers",
|
||||||
"language_model", "cost_limit", "autonomous_mode", "robot_status",
|
"language_model", "workspace", "cost_limit", "autonomous_mode", "robot_status",
|
||||||
"notes", "metadata", "status",
|
"notes", "metadata", "status",
|
||||||
"__yao_updated_by", "__yao_team_id", "__yao_tenant_id",
|
"__yao_updated_by", "__yao_team_id", "__yao_tenant_id",
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1152,6 +1152,7 @@ func TestMemberCreateRobot(t *testing.T) {
|
||||||
"mcp_tools": []string{"filesystem", "database"},
|
"mcp_tools": []string{"filesystem", "database"},
|
||||||
"autonomous_mode": "enabled",
|
"autonomous_mode": "enabled",
|
||||||
"cost_limit": 100.50,
|
"cost_limit": 100.50,
|
||||||
|
"workspace": "ws-test-create",
|
||||||
},
|
},
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
||||||
|
|
@ -1374,6 +1375,9 @@ func TestMemberCreateRobot(t *testing.T) {
|
||||||
if tc.body["prompt"] != nil {
|
if tc.body["prompt"] != nil {
|
||||||
assert.Equal(t, tc.body["prompt"], member["system_prompt"], "Should have correct system_prompt")
|
assert.Equal(t, tc.body["prompt"], member["system_prompt"], "Should have correct system_prompt")
|
||||||
}
|
}
|
||||||
|
if tc.body["workspace"] != nil {
|
||||||
|
assert.Equal(t, "ws-test-create", member["workspace"], "Should have correct workspace")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1595,6 +1599,7 @@ func TestMemberUpdateRobot(t *testing.T) {
|
||||||
"llm": "gpt-3.5-turbo",
|
"llm": "gpt-3.5-turbo",
|
||||||
"autonomous_mode": "disabled",
|
"autonomous_mode": "disabled",
|
||||||
"cost_limit": 50.0,
|
"cost_limit": 50.0,
|
||||||
|
"workspace": "ws-initial",
|
||||||
}
|
}
|
||||||
robotBodyBytes, _ := json.Marshal(robotBody)
|
robotBodyBytes, _ := json.Marshal(robotBody)
|
||||||
robotReq, _ := http.NewRequest("POST", serverURL+baseURL+"/user/teams/"+teamID+"/members/robots", bytes.NewBuffer(robotBodyBytes))
|
robotReq, _ := http.NewRequest("POST", serverURL+baseURL+"/user/teams/"+teamID+"/members/robots", bytes.NewBuffer(robotBodyBytes))
|
||||||
|
|
@ -1655,6 +1660,7 @@ func TestMemberUpdateRobot(t *testing.T) {
|
||||||
"cost_limit": 100.0,
|
"cost_limit": 100.0,
|
||||||
"status": "active",
|
"status": "active",
|
||||||
"robot_status": "working",
|
"robot_status": "working",
|
||||||
|
"workspace": "ws-updated",
|
||||||
},
|
},
|
||||||
map[string]string{
|
map[string]string{
|
||||||
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
||||||
|
|
@ -1679,6 +1685,7 @@ func TestMemberUpdateRobot(t *testing.T) {
|
||||||
assert.Equal(t, fmt.Sprintf("https://example.com/avatars/full-%s.png", testUUID), member["avatar"])
|
assert.Equal(t, fmt.Sprintf("https://example.com/avatars/full-%s.png", testUUID), member["avatar"])
|
||||||
assert.Equal(t, "Updated system prompt", member["system_prompt"])
|
assert.Equal(t, "Updated system prompt", member["system_prompt"])
|
||||||
assert.Equal(t, "gpt-4", member["language_model"])
|
assert.Equal(t, "gpt-4", member["language_model"])
|
||||||
|
assert.Equal(t, "ws-updated", member["workspace"], "Should have correct workspace")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -1973,6 +1980,35 @@ func TestMemberUpdateRobot(t *testing.T) {
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"update workspace to unbind",
|
||||||
|
func() (string, string) { return createTestRobot("15") },
|
||||||
|
map[string]interface{}{
|
||||||
|
"workspace": "",
|
||||||
|
},
|
||||||
|
map[string]string{
|
||||||
|
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
||||||
|
},
|
||||||
|
200,
|
||||||
|
"should unbind workspace by setting to empty string",
|
||||||
|
func(t *testing.T, memberID string) {
|
||||||
|
getMemberURL := serverURL + baseURL + "/user/teams/" + teamID + "/members/" + memberID
|
||||||
|
getReq, _ := http.NewRequest("GET", getMemberURL, nil)
|
||||||
|
getReq.Header.Set("Authorization", "Bearer "+tokenInfo.AccessToken)
|
||||||
|
client := &http.Client{}
|
||||||
|
getResp, err := client.Do(getReq)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
if getResp != nil {
|
||||||
|
defer getResp.Body.Close()
|
||||||
|
if getResp.StatusCode == 200 {
|
||||||
|
var member map[string]interface{}
|
||||||
|
body, _ := io.ReadAll(getResp.Body)
|
||||||
|
json.Unmarshal(body, &member)
|
||||||
|
assert.Empty(t, member["workspace"], "Workspace should be empty after unbinding")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
|
|
||||||
|
|
@ -315,6 +315,9 @@ func GinMemberCreateRobot(c *gin.Context) {
|
||||||
if req.LanguageModel != "" {
|
if req.LanguageModel != "" {
|
||||||
baseData["language_model"] = req.LanguageModel
|
baseData["language_model"] = req.LanguageModel
|
||||||
}
|
}
|
||||||
|
if req.Workspace != "" {
|
||||||
|
baseData["workspace"] = req.Workspace
|
||||||
|
}
|
||||||
if len(req.Agents) > 0 {
|
if len(req.Agents) > 0 {
|
||||||
baseData["agents"] = req.Agents
|
baseData["agents"] = req.Agents
|
||||||
}
|
}
|
||||||
|
|
@ -431,6 +434,9 @@ func GinMemberUpdateRobot(c *gin.Context) {
|
||||||
if req.LanguageModel != "" {
|
if req.LanguageModel != "" {
|
||||||
updateData["language_model"] = req.LanguageModel
|
updateData["language_model"] = req.LanguageModel
|
||||||
}
|
}
|
||||||
|
if req.Workspace != nil {
|
||||||
|
updateData["workspace"] = *req.Workspace
|
||||||
|
}
|
||||||
if req.Status != "" {
|
if req.Status != "" {
|
||||||
updateData["status"] = req.Status
|
updateData["status"] = req.Status
|
||||||
}
|
}
|
||||||
|
|
@ -1591,6 +1597,7 @@ func mapToMemberDetailResponse(data maps.MapStr) MemberDetailResponse {
|
||||||
SystemPrompt: utils.ToString(data["system_prompt"]),
|
SystemPrompt: utils.ToString(data["system_prompt"]),
|
||||||
ManagerID: utils.ToString(data["manager_id"]),
|
ManagerID: utils.ToString(data["manager_id"]),
|
||||||
LanguageModel: utils.ToString(data["language_model"]),
|
LanguageModel: utils.ToString(data["language_model"]),
|
||||||
|
Workspace: utils.ToString(data["workspace"]),
|
||||||
CostLimit: utils.ToFloat64(data["cost_limit"]),
|
CostLimit: utils.ToFloat64(data["cost_limit"]),
|
||||||
AutonomousMode: data["autonomous_mode"], // Keep original type (bool or string)
|
AutonomousMode: data["autonomous_mode"], // Keep original type (bool or string)
|
||||||
LastRobotActivity: utils.ToTimeString(data["last_robot_activity"]),
|
LastRobotActivity: utils.ToTimeString(data["last_robot_activity"]),
|
||||||
|
|
|
||||||
|
|
@ -457,6 +457,7 @@ type MemberDetailResponse struct {
|
||||||
Agents []string `json:"agents,omitempty"`
|
Agents []string `json:"agents,omitempty"`
|
||||||
MCPServers []string `json:"mcp_servers,omitempty"`
|
MCPServers []string `json:"mcp_servers,omitempty"`
|
||||||
LanguageModel string `json:"language_model,omitempty"`
|
LanguageModel string `json:"language_model,omitempty"`
|
||||||
|
Workspace string `json:"workspace,omitempty"`
|
||||||
CostLimit float64 `json:"cost_limit,omitempty"`
|
CostLimit float64 `json:"cost_limit,omitempty"`
|
||||||
AutonomousMode interface{} `json:"autonomous_mode,omitempty"` // Can be bool or string
|
AutonomousMode interface{} `json:"autonomous_mode,omitempty"` // Can be bool or string
|
||||||
LastRobotActivity string `json:"last_robot_activity,omitempty"`
|
LastRobotActivity string `json:"last_robot_activity,omitempty"`
|
||||||
|
|
@ -480,6 +481,7 @@ type CreateRobotMemberRequest struct {
|
||||||
ManagerID string `json:"report_to,omitempty"` // Direct manager user ID
|
ManagerID string `json:"report_to,omitempty"` // Direct manager user ID
|
||||||
SystemPrompt string `json:"prompt" binding:"required"` // Identity & role prompt
|
SystemPrompt string `json:"prompt" binding:"required"` // Identity & role prompt
|
||||||
LanguageModel string `json:"llm,omitempty"` // Language model (e.g., "gpt-4")
|
LanguageModel string `json:"llm,omitempty"` // Language model (e.g., "gpt-4")
|
||||||
|
Workspace string `json:"workspace,omitempty"` // Workspace ID bound to this robot
|
||||||
Agents []string `json:"agents,omitempty"` // Accessible agents
|
Agents []string `json:"agents,omitempty"` // Accessible agents
|
||||||
MCPServers []string `json:"mcp_tools,omitempty"` // MCP servers/tools
|
MCPServers []string `json:"mcp_tools,omitempty"` // MCP servers/tools
|
||||||
AutonomousMode string `json:"autonomous_mode,omitempty"` // "enabled" or "disabled"
|
AutonomousMode string `json:"autonomous_mode,omitempty"` // "enabled" or "disabled"
|
||||||
|
|
@ -499,6 +501,7 @@ type UpdateRobotMemberRequest struct {
|
||||||
ManagerID string `json:"report_to,omitempty"` // Direct manager user ID
|
ManagerID string `json:"report_to,omitempty"` // Direct manager user ID
|
||||||
SystemPrompt string `json:"prompt,omitempty"` // Identity & role prompt
|
SystemPrompt string `json:"prompt,omitempty"` // Identity & role prompt
|
||||||
LanguageModel string `json:"llm,omitempty"` // Language model (e.g., "gpt-4")
|
LanguageModel string `json:"llm,omitempty"` // Language model (e.g., "gpt-4")
|
||||||
|
Workspace *string `json:"workspace"` // Workspace ID (nil=no change, ""=unbind)
|
||||||
Agents []string `json:"agents,omitempty"` // Accessible agents
|
Agents []string `json:"agents,omitempty"` // Accessible agents
|
||||||
MCPServers []string `json:"mcp_tools,omitempty"` // MCP servers/tools
|
MCPServers []string `json:"mcp_tools,omitempty"` // MCP servers/tools
|
||||||
AutonomousMode string `json:"autonomous_mode,omitempty"` // "enabled" or "disabled"
|
AutonomousMode string `json:"autonomous_mode,omitempty"` // "enabled" or "disabled"
|
||||||
|
|
|
||||||
|
|
@ -214,6 +214,15 @@
|
||||||
"length": 100,
|
"length": 100,
|
||||||
"nullable": true
|
"nullable": true
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "workspace",
|
||||||
|
"type": "string",
|
||||||
|
"label": "Workspace",
|
||||||
|
"comment": "Workspace ID bound to this robot member (nullable = not bound)",
|
||||||
|
"length": 255,
|
||||||
|
"nullable": true,
|
||||||
|
"index": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "cost_limit",
|
"name": "cost_limit",
|
||||||
"type": "decimal",
|
"type": "decimal",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue