mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-07-31 12:20:48 +00:00
* refactor: remove managed/standalone mode distinction from codebase Standalone mode is deprecated; managed mode is now the only mode. Remove redundant "managed mode" qualifiers from comments, docs, and error messages. Error strings now reference "database stores" instead of "managed mode" for clarity. * improve(onboard): streamline onboard process and env setup Simplify onboard wizard, extract helpers to dedicated file, update env example and entrypoint for default managed mode, clean up prepare-env script, update i18n catalogs.
167 lines
5.8 KiB
Go
167 lines
5.8 KiB
Go
package methods
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"github.com/nextlevelbuilder/goclaw/internal/bootstrap"
|
|
"github.com/nextlevelbuilder/goclaw/internal/config"
|
|
"github.com/nextlevelbuilder/goclaw/internal/gateway"
|
|
"github.com/nextlevelbuilder/goclaw/internal/i18n"
|
|
"github.com/nextlevelbuilder/goclaw/internal/store"
|
|
"github.com/nextlevelbuilder/goclaw/pkg/protocol"
|
|
)
|
|
|
|
// --- agents.create ---
|
|
// Matching TS src/gateway/server-methods/agents.ts:216-287
|
|
|
|
func (m *AgentsMethods) handleCreate(ctx context.Context, client *gateway.Client, req *protocol.RequestFrame) {
|
|
locale := store.LocaleFromContext(ctx)
|
|
var params struct {
|
|
Name string `json:"name"`
|
|
Workspace string `json:"workspace"`
|
|
Emoji string `json:"emoji"`
|
|
Avatar string `json:"avatar"`
|
|
AgentType string `json:"agent_type"` // "open" (default) or "predefined"
|
|
OwnerIDs []string `json:"owner_ids,omitempty"` // first entry used as DB owner_id; falls back to "system"
|
|
// Per-agent config overrides
|
|
ToolsConfig json.RawMessage `json:"tools_config,omitempty"`
|
|
SubagentsConfig json.RawMessage `json:"subagents_config,omitempty"`
|
|
SandboxConfig json.RawMessage `json:"sandbox_config,omitempty"`
|
|
MemoryConfig json.RawMessage `json:"memory_config,omitempty"`
|
|
CompactionConfig json.RawMessage `json:"compaction_config,omitempty"`
|
|
ContextPruning json.RawMessage `json:"context_pruning,omitempty"`
|
|
OtherConfig json.RawMessage `json:"other_config,omitempty"`
|
|
}
|
|
if req.Params != nil {
|
|
json.Unmarshal(req.Params, ¶ms)
|
|
}
|
|
|
|
if params.Name == "" {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgRequired, "name")))
|
|
return
|
|
}
|
|
|
|
agentType := params.AgentType
|
|
if agentType == "" {
|
|
agentType = store.AgentTypeOpen
|
|
}
|
|
|
|
agentID := config.NormalizeAgentID(params.Name)
|
|
if agentID == "default" {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgInvalidRequest, "cannot create agent with reserved id 'default'")))
|
|
return
|
|
}
|
|
|
|
// Resolve workspace
|
|
ws := params.Workspace
|
|
if ws == "" {
|
|
ws = filepath.Join(m.workspace, "agents", agentID)
|
|
} else {
|
|
ws = config.ExpandHome(ws)
|
|
}
|
|
|
|
if m.agentStore != nil {
|
|
// --- DB-backed: create agent in store ---
|
|
ctx := context.Background()
|
|
|
|
// Check if agent already exists in DB
|
|
if existing, _ := m.agentStore.GetByKey(ctx, agentID); existing != nil {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgAlreadyExists, "agent", agentID)))
|
|
return
|
|
}
|
|
|
|
// Resolve owner: use first provided ID so external provisioning tools (e.g. goclaw-wizards)
|
|
// can set a real user as owner at creation time. Falls back to "system" for backward compat.
|
|
ownerID := "system"
|
|
if len(params.OwnerIDs) > 0 && params.OwnerIDs[0] != "" {
|
|
ownerID = params.OwnerIDs[0]
|
|
}
|
|
|
|
agentData := &store.AgentData{
|
|
AgentKey: agentID,
|
|
DisplayName: params.Name,
|
|
OwnerID: ownerID,
|
|
AgentType: agentType,
|
|
Provider: m.cfg.Agents.Defaults.Provider,
|
|
Model: m.cfg.Agents.Defaults.Model,
|
|
Workspace: ws,
|
|
Status: store.AgentStatusActive,
|
|
ToolsConfig: params.ToolsConfig,
|
|
SubagentsConfig: params.SubagentsConfig,
|
|
SandboxConfig: params.SandboxConfig,
|
|
MemoryConfig: params.MemoryConfig,
|
|
CompactionConfig: params.CompactionConfig,
|
|
ContextPruning: params.ContextPruning,
|
|
OtherConfig: params.OtherConfig,
|
|
}
|
|
if err := m.agentStore.Create(ctx, agentData); err != nil {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, i18n.T(locale, i18n.MsgFailedToCreate, "agent", fmt.Sprintf("%v", err))))
|
|
return
|
|
}
|
|
|
|
// Seed context files to DB (skipped for open agents)
|
|
if _, err := bootstrap.SeedToStore(ctx, m.agentStore, agentData.ID, agentData.AgentType); err != nil {
|
|
slog.Warn("failed to seed bootstrap for agent", "agent", agentID, "error", err)
|
|
}
|
|
|
|
// Set identity in DB bootstrap
|
|
if params.Name != "" || params.Emoji != "" || params.Avatar != "" {
|
|
content := buildIdentityContent(params.Name, params.Emoji, params.Avatar)
|
|
if err := m.agentStore.SetAgentContextFile(ctx, agentData.ID, "IDENTITY.md", content); err != nil {
|
|
slog.Warn("failed to set IDENTITY.md", "agent", agentID, "error", err)
|
|
}
|
|
}
|
|
|
|
// Invalidate router cache so resolver re-loads from DB
|
|
m.agents.InvalidateAgent(agentID)
|
|
} else {
|
|
// --- Fallback: config.json + filesystem ---
|
|
if _, ok := m.cfg.Agents.List[agentID]; ok {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgAlreadyExists, "agent", agentID)))
|
|
return
|
|
}
|
|
|
|
spec := config.AgentSpec{
|
|
DisplayName: params.Name,
|
|
Workspace: ws,
|
|
}
|
|
if params.Emoji != "" || params.Avatar != "" {
|
|
spec.Identity = &config.IdentityConfig{
|
|
Emoji: params.Emoji,
|
|
}
|
|
}
|
|
|
|
if m.cfg.Agents.List == nil {
|
|
m.cfg.Agents.List = make(map[string]config.AgentSpec)
|
|
}
|
|
m.cfg.Agents.List[agentID] = spec
|
|
|
|
if err := config.Save(m.cfgPath, m.cfg); err != nil {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, i18n.T(locale, i18n.MsgFailedToSave, "config", err.Error())))
|
|
return
|
|
}
|
|
|
|
// Append identity metadata to IDENTITY.md
|
|
if params.Name != "" || params.Emoji != "" || params.Avatar != "" {
|
|
identityPath := filepath.Join(ws, "IDENTITY.md")
|
|
appendIdentityFields(identityPath, params.Name, params.Emoji, params.Avatar)
|
|
}
|
|
}
|
|
|
|
// Both modes: create workspace dir + seed filesystem backup
|
|
os.MkdirAll(ws, 0755)
|
|
bootstrap.EnsureWorkspaceFiles(ws)
|
|
|
|
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
|
|
"ok": true,
|
|
"agentId": agentID,
|
|
"name": params.Name,
|
|
"workspace": ws,
|
|
}))
|
|
}
|