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",
|
||||
"system_prompt", "robot_status", "autonomous_mode",
|
||||
"robot_config", "robot_email", "agents", "mcp_servers",
|
||||
"manager_id", "language_model",
|
||||
"manager_id", "language_model", "workspace",
|
||||
},
|
||||
Wheres: []model.QueryWhere{
|
||||
{Column: "member_id", Value: memberID},
|
||||
|
|
@ -268,7 +268,7 @@ func ListRobotsFromDB(query *ListQuery) (*ListResult, error) {
|
|||
"id", "member_id", "team_id", "display_name", "bio",
|
||||
"system_prompt", "robot_status", "autonomous_mode",
|
||||
"robot_config", "robot_email", "agents", "mcp_servers",
|
||||
"language_model",
|
||||
"language_model", "workspace",
|
||||
},
|
||||
Wheres: wheres,
|
||||
Orders: orders,
|
||||
|
|
@ -444,6 +444,7 @@ func CreateRobot(ctx *types.Context, req *CreateRobotRequest) (*RobotResponse, e
|
|||
Agents: req.Agents,
|
||||
MCPServers: req.MCPServers,
|
||||
LanguageModel: req.LanguageModel,
|
||||
Workspace: req.Workspace,
|
||||
|
||||
// Limits
|
||||
CostLimit: req.CostLimit,
|
||||
|
|
@ -559,6 +560,9 @@ func UpdateRobot(ctx *types.Context, memberID string, req *UpdateRobotRequest) (
|
|||
if req.LanguageModel != nil {
|
||||
existing.LanguageModel = *req.LanguageModel
|
||||
}
|
||||
if req.Workspace != nil {
|
||||
existing.Workspace = *req.Workspace
|
||||
}
|
||||
|
||||
// Limits
|
||||
if req.CostLimit != nil {
|
||||
|
|
@ -684,6 +688,7 @@ func recordToResponse(record *store.RobotRecord) *RobotResponse {
|
|||
Agents: record.Agents,
|
||||
MCPServers: record.MCPServers,
|
||||
LanguageModel: record.LanguageModel,
|
||||
Workspace: record.Workspace,
|
||||
|
||||
CostLimit: record.CostLimit,
|
||||
InvitedBy: record.InvitedBy,
|
||||
|
|
|
|||
|
|
@ -144,7 +144,7 @@ type CreateRobotRequest struct {
|
|||
AutonomousMode *bool `json:"autonomous_mode,omitempty"` // Whether autonomous mode is enabled
|
||||
|
||||
// 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)
|
||||
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)
|
||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
||||
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
||||
Workspace string `json:"workspace,omitempty"` // Workspace ID bound to this robot
|
||||
|
||||
// Limits
|
||||
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
|
||||
|
||||
// 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
|
||||
EmailFilterRules interface{} `json:"email_filter_rules,omitempty"` // Email filter rules
|
||||
|
||||
|
|
@ -188,6 +189,7 @@ type UpdateRobotRequest struct {
|
|||
Agents interface{} `json:"agents,omitempty"` // Accessible agents
|
||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers
|
||||
LanguageModel *string `json:"language_model,omitempty"` // Language model name
|
||||
Workspace *string `json:"workspace,omitempty"` // Workspace ID (nil=no change, ""=unbind)
|
||||
|
||||
// Limits
|
||||
CostLimit *float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||
|
|
@ -226,6 +228,7 @@ type RobotResponse struct {
|
|||
Agents interface{} `json:"agents,omitempty"`
|
||||
MCPServers interface{} `json:"mcp_servers,omitempty"`
|
||||
LanguageModel string `json:"language_model,omitempty"`
|
||||
Workspace string `json:"workspace,omitempty"`
|
||||
|
||||
// Limits
|
||||
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",
|
||||
"manager_id",
|
||||
"language_model",
|
||||
"workspace",
|
||||
}
|
||||
|
||||
// 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.
|
||||
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 *execLogger
|
||||
}
|
||||
|
|
@ -444,6 +448,13 @@ func (c *AgentCaller) buildAgentContext(ctx *robottypes.Context, assistantID str
|
|||
}
|
||||
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)
|
||||
return agentCtx
|
||||
}
|
||||
|
|
|
|||
|
|
@ -43,6 +43,7 @@ func (e *Executor) RunDelivery(ctx *robottypes.Context, exec *robottypes.Executi
|
|||
|
||||
caller := NewAgentCaller()
|
||||
caller.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||
if err != nil {
|
||||
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
|
||||
caller := NewAgentCaller()
|
||||
caller.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
caller := NewConversationCaller(chatID)
|
||||
caller.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessages(ctx, agentID, string(inputJSON))
|
||||
if err != nil {
|
||||
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
|
||||
caller := NewAgentCaller()
|
||||
caller.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||
if err != nil {
|
||||
return fmt.Errorf("inspiration agent (%s) call failed: %w", agentID, err)
|
||||
|
|
|
|||
|
|
@ -37,6 +37,13 @@ func (l *execLogger) connector() string {
|
|||
return ""
|
||||
}
|
||||
|
||||
func (l *execLogger) workspace() string {
|
||||
if l.robot != nil {
|
||||
return l.robot.Workspace
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// P2: Task Overview
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -51,6 +58,7 @@ func (l *execLogger) logTaskOverview(tasks []robottypes.Task) {
|
|||
"phase": "tasks",
|
||||
"task_count": len(tasks),
|
||||
"language_model": l.connector(),
|
||||
"workspace": l.workspace(),
|
||||
}).Info("P2 task overview: %d tasks generated", len(tasks))
|
||||
}
|
||||
|
||||
|
|
@ -69,6 +77,9 @@ func (l *execLogger) devTaskOverview(tasks []robottypes.Task) {
|
|||
if l.connector() != "" {
|
||||
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))
|
||||
for i, t := range tasks {
|
||||
desc := t.Description
|
||||
|
|
|
|||
|
|
@ -134,6 +134,7 @@ func (r *Runner) executeAssistantTask(task *robottypes.Task, taskCtx *RunnerCont
|
|||
caller := NewAgentCaller()
|
||||
caller.log = r.log
|
||||
caller.Connector = r.robot.LanguageModel
|
||||
caller.Workspace = r.robot.Workspace
|
||||
caller.ChatID = r.chatID
|
||||
|
||||
messages := r.BuildAssistantMessages(task, taskCtx)
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ func (e *Executor) RunTasks(ctx *robottypes.Context, exec *robottypes.Execution,
|
|||
caller := NewAgentCaller()
|
||||
caller.log = newExecLogger(robot, exec.ID)
|
||||
caller.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessages(ctx, agentID, userContent)
|
||||
if err != nil {
|
||||
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
|
||||
caller := NewAgentCaller()
|
||||
caller.Connector = v.robot.LanguageModel
|
||||
caller.Workspace = v.robot.Workspace
|
||||
result, err := caller.CallWithMessages(v.ctx, validationAgentID, validationPrompt)
|
||||
if err != nil {
|
||||
return &robottypes.ValidationResult{
|
||||
|
|
@ -641,6 +642,7 @@ func (av *robotAgentValidator) Validate(agentID string, output, input, criteria
|
|||
// Call agent
|
||||
caller := NewAgentCaller()
|
||||
caller.Connector = av.v.robot.LanguageModel
|
||||
caller.Workspace = av.v.robot.Workspace
|
||||
callResult, err := caller.CallWithMessages(av.v.ctx, agentID, string(inputJSON))
|
||||
if err != nil {
|
||||
result.Passed = false
|
||||
|
|
|
|||
|
|
@ -326,17 +326,19 @@ func (m *Manager) callHostAgentForScenario(ctx *types.Context, robot *types.Robo
|
|||
Scenario: scenario,
|
||||
Messages: []agentcontext.Message{{Role: "user", Content: message}},
|
||||
Context: hostCtx,
|
||||
}, chatID)
|
||||
}, chatID, robot)
|
||||
}
|
||||
|
||||
// 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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
||||
}
|
||||
|
||||
caller := standard.NewConversationCaller(chatID)
|
||||
caller.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessages(ctx, agentID, string(inputJSON))
|
||||
if err != nil {
|
||||
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,
|
||||
Messages: []agentcontext.Message{{Role: "user", Content: msg}},
|
||||
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)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to marshal host input: %w", err)
|
||||
}
|
||||
|
||||
caller := standard.NewConversationCaller(chatID)
|
||||
caller.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessagesStream(ctx, agentID, string(inputJSON), streamFn)
|
||||
if err != nil {
|
||||
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,
|
||||
Messages: []agentcontext.Message{{Role: "user", Content: msg}},
|
||||
Context: hostCtx,
|
||||
}, chatID, onMessage)
|
||||
}, chatID, robot, onMessage)
|
||||
}
|
||||
|
||||
// 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,
|
||||
// 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.
|
||||
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)
|
||||
if err != nil {
|
||||
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.Connector = robot.LanguageModel
|
||||
caller.Workspace = robot.Workspace
|
||||
result, err := caller.CallWithMessagesStreamRaw(ctx, agentID, string(inputJSON), wrappedOnMessage)
|
||||
if err != nil {
|
||||
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)
|
||||
|
||||
// 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)
|
||||
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)
|
||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
||||
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
||||
Workspace string `json:"workspace,omitempty"` // Workspace ID bound to this robot
|
||||
|
||||
// Limits
|
||||
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||
|
|
@ -117,6 +118,7 @@ var robotFields = []interface{}{
|
|||
"agents",
|
||||
"mcp_servers",
|
||||
"language_model",
|
||||
"workspace",
|
||||
|
||||
// Limits
|
||||
"cost_limit",
|
||||
|
|
@ -435,6 +437,9 @@ func (s *RobotStore) recordToMap(record *RobotRecord) map[string]interface{} {
|
|||
if record.LanguageModel != "" {
|
||||
data["language_model"] = record.LanguageModel
|
||||
}
|
||||
if record.Workspace != "" {
|
||||
data["workspace"] = record.Workspace
|
||||
}
|
||||
|
||||
// Limits
|
||||
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 {
|
||||
record.LanguageModel = v
|
||||
}
|
||||
if v, ok := row["workspace"].(string); ok {
|
||||
record.Workspace = v
|
||||
}
|
||||
|
||||
// Limits
|
||||
if v := row["cost_limit"]; v != nil {
|
||||
|
|
@ -596,6 +604,8 @@ func (r *RobotRecord) ToRobot() (*types.Robot, error) {
|
|||
SystemPrompt: r.SystemPrompt,
|
||||
AutonomousMode: r.AutonomousMode,
|
||||
RobotEmail: r.RobotEmail,
|
||||
LanguageModel: r.LanguageModel,
|
||||
Workspace: r.Workspace,
|
||||
}
|
||||
|
||||
// Parse robot_status
|
||||
|
|
@ -678,6 +688,8 @@ func FromRobot(robot *types.Robot) *RobotRecord {
|
|||
RobotStatus: string(robot.Status),
|
||||
AutonomousMode: robot.AutonomousMode,
|
||||
RobotEmail: robot.RobotEmail,
|
||||
LanguageModel: robot.LanguageModel,
|
||||
Workspace: robot.Workspace,
|
||||
MemberType: "robot",
|
||||
Status: "active",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,8 +21,9 @@ type Robot struct {
|
|||
SystemPrompt string `json:"system_prompt"`
|
||||
Status RobotStatus `json:"robot_status"`
|
||||
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)
|
||||
Workspace string `json:"workspace"` // Workspace ID bound to this robot (nullable in DB)
|
||||
|
||||
// Manager info (from __yao.member)
|
||||
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"),
|
||||
ManagerEmail: getString(m, "manager_email"),
|
||||
LanguageModel: getString(m, "language_model"),
|
||||
Workspace: getString(m, "workspace"),
|
||||
}
|
||||
|
||||
// Parse robot_status
|
||||
|
|
|
|||
|
|
@ -60,6 +60,8 @@ func ResolveNodeID(ctx *agentContext.Context, cfg *types.SandboxConfig, manager
|
|||
if err == nil && wsNode != "" {
|
||||
log.Trace("[sandbox/v2] ResolveNodeID: workspace %s -> node %s", workspaceID, 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)
|
||||
}
|
||||
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()
|
||||
}
|
||||
|
||||
// 加载数据模型
|
||||
loadWarnings, err := engine.Load(config.Conf, engine.LoadOption{Action: "migrate"})
|
||||
// 仅加载 Application、DB 连接和 Model(含自动 migrate),不启动完整 Engine
|
||||
loadWarnings, err := engine.LoadForMigrate(config.Conf)
|
||||
if err != nil {
|
||||
fmt.Println(color.RedString(L("Fatal: %s"), err.Error()))
|
||||
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
|
||||
}
|
||||
|
||||
// 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
|
||||
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)
|
||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers (JSON array)
|
||||
LanguageModel string `json:"language_model,omitempty"` // Language model name
|
||||
Workspace string `json:"workspace,omitempty"` // Workspace ID bound to this robot
|
||||
|
||||
// Limits
|
||||
CostLimit float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||
|
|
@ -73,6 +74,7 @@ type UpdateRobotRequest struct {
|
|||
Agents interface{} `json:"agents,omitempty"` // Accessible agents
|
||||
MCPServers interface{} `json:"mcp_servers,omitempty"` // MCP servers
|
||||
LanguageModel *string `json:"language_model,omitempty"` // Language model name
|
||||
Workspace *string `json:"workspace,omitempty"` // Workspace ID (nil=no change, ""=unbind)
|
||||
|
||||
// Limits
|
||||
CostLimit *float64 `json:"cost_limit,omitempty"` // Monthly cost limit USD
|
||||
|
|
@ -115,6 +117,7 @@ type Response struct {
|
|||
Agents interface{} `json:"agents,omitempty"`
|
||||
MCPServers interface{} `json:"mcp_servers,omitempty"`
|
||||
LanguageModel string `json:"language_model,omitempty"`
|
||||
Workspace string `json:"workspace,omitempty"`
|
||||
|
||||
// Limits
|
||||
CostLimit float64 `json:"cost_limit,omitempty"`
|
||||
|
|
@ -186,6 +189,7 @@ func NewResponse(r *robotapi.RobotResponse) *Response {
|
|||
Agents: r.Agents,
|
||||
MCPServers: r.MCPServers,
|
||||
LanguageModel: r.LanguageModel,
|
||||
Workspace: r.Workspace,
|
||||
CostLimit: r.CostLimit,
|
||||
InvitedBy: r.InvitedBy,
|
||||
JoinedAt: r.JoinedAt,
|
||||
|
|
@ -215,6 +219,7 @@ func (r *CreateRobotRequest) ToAPICreateRequest() *robotapi.CreateRobotRequest {
|
|||
Agents: r.Agents,
|
||||
MCPServers: r.MCPServers,
|
||||
LanguageModel: r.LanguageModel,
|
||||
Workspace: r.Workspace,
|
||||
CostLimit: r.CostLimit,
|
||||
}
|
||||
}
|
||||
|
|
@ -238,6 +243,7 @@ func (r *UpdateRobotRequest) ToAPIUpdateRequest() *robotapi.UpdateRobotRequest {
|
|||
Agents: r.Agents,
|
||||
MCPServers: r.MCPServers,
|
||||
LanguageModel: r.LanguageModel,
|
||||
Workspace: r.Workspace,
|
||||
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",
|
||||
"system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
||||
"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_expires_at", "last_active_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{
|
||||
"role_id", "system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
||||
"robot_config", "agents", "mcp_servers",
|
||||
"language_model", "cost_limit", "autonomous_mode", "robot_status",
|
||||
"language_model", "workspace", "cost_limit", "autonomous_mode", "robot_status",
|
||||
"notes", "metadata",
|
||||
"__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{
|
||||
"role_id", "system_prompt", "manager_id", "robot_email", "authorized_senders", "email_filter_rules",
|
||||
"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",
|
||||
"__yao_updated_by", "__yao_team_id", "__yao_tenant_id",
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1152,6 +1152,7 @@ func TestMemberCreateRobot(t *testing.T) {
|
|||
"mcp_tools": []string{"filesystem", "database"},
|
||||
"autonomous_mode": "enabled",
|
||||
"cost_limit": 100.50,
|
||||
"workspace": "ws-test-create",
|
||||
},
|
||||
map[string]string{
|
||||
"Authorization": "Bearer " + tokenInfo.AccessToken,
|
||||
|
|
@ -1374,6 +1375,9 @@ func TestMemberCreateRobot(t *testing.T) {
|
|||
if tc.body["prompt"] != nil {
|
||||
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",
|
||||
"autonomous_mode": "disabled",
|
||||
"cost_limit": 50.0,
|
||||
"workspace": "ws-initial",
|
||||
}
|
||||
robotBodyBytes, _ := json.Marshal(robotBody)
|
||||
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,
|
||||
"status": "active",
|
||||
"robot_status": "working",
|
||||
"workspace": "ws-updated",
|
||||
},
|
||||
map[string]string{
|
||||
"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, "Updated system prompt", member["system_prompt"])
|
||||
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 {
|
||||
|
|
|
|||
|
|
@ -315,6 +315,9 @@ func GinMemberCreateRobot(c *gin.Context) {
|
|||
if req.LanguageModel != "" {
|
||||
baseData["language_model"] = req.LanguageModel
|
||||
}
|
||||
if req.Workspace != "" {
|
||||
baseData["workspace"] = req.Workspace
|
||||
}
|
||||
if len(req.Agents) > 0 {
|
||||
baseData["agents"] = req.Agents
|
||||
}
|
||||
|
|
@ -431,6 +434,9 @@ func GinMemberUpdateRobot(c *gin.Context) {
|
|||
if req.LanguageModel != "" {
|
||||
updateData["language_model"] = req.LanguageModel
|
||||
}
|
||||
if req.Workspace != nil {
|
||||
updateData["workspace"] = *req.Workspace
|
||||
}
|
||||
if req.Status != "" {
|
||||
updateData["status"] = req.Status
|
||||
}
|
||||
|
|
@ -1591,6 +1597,7 @@ func mapToMemberDetailResponse(data maps.MapStr) MemberDetailResponse {
|
|||
SystemPrompt: utils.ToString(data["system_prompt"]),
|
||||
ManagerID: utils.ToString(data["manager_id"]),
|
||||
LanguageModel: utils.ToString(data["language_model"]),
|
||||
Workspace: utils.ToString(data["workspace"]),
|
||||
CostLimit: utils.ToFloat64(data["cost_limit"]),
|
||||
AutonomousMode: data["autonomous_mode"], // Keep original type (bool or string)
|
||||
LastRobotActivity: utils.ToTimeString(data["last_robot_activity"]),
|
||||
|
|
|
|||
|
|
@ -457,6 +457,7 @@ type MemberDetailResponse struct {
|
|||
Agents []string `json:"agents,omitempty"`
|
||||
MCPServers []string `json:"mcp_servers,omitempty"`
|
||||
LanguageModel string `json:"language_model,omitempty"`
|
||||
Workspace string `json:"workspace,omitempty"`
|
||||
CostLimit float64 `json:"cost_limit,omitempty"`
|
||||
AutonomousMode interface{} `json:"autonomous_mode,omitempty"` // Can be bool or string
|
||||
LastRobotActivity string `json:"last_robot_activity,omitempty"`
|
||||
|
|
@ -480,6 +481,7 @@ type CreateRobotMemberRequest struct {
|
|||
ManagerID string `json:"report_to,omitempty"` // Direct manager user ID
|
||||
SystemPrompt string `json:"prompt" binding:"required"` // Identity & role prompt
|
||||
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
|
||||
MCPServers []string `json:"mcp_tools,omitempty"` // MCP servers/tools
|
||||
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
|
||||
SystemPrompt string `json:"prompt,omitempty"` // Identity & role prompt
|
||||
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
|
||||
MCPServers []string `json:"mcp_tools,omitempty"` // MCP servers/tools
|
||||
AutonomousMode string `json:"autonomous_mode,omitempty"` // "enabled" or "disabled"
|
||||
|
|
|
|||
|
|
@ -214,6 +214,15 @@
|
|||
"length": 100,
|
||||
"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",
|
||||
"type": "decimal",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue