mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-06-11 20:10:59 +00:00
517e6c89ab
- Read existing IDENTITY.md before overwriting to preserve emoji field - Config tab: merge existing other_config to prevent wiping emoji on save - Emoji input: validate single emoji only with extractSingleEmoji() - Select-all on focus for easy emoji replacement
186 lines
5.8 KiB
Go
186 lines
5.8 KiB
Go
package methods
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"log/slog"
|
|
"os"
|
|
"path/filepath"
|
|
|
|
"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.update ---
|
|
// Matching TS src/gateway/server-methods/agents.ts:288-346
|
|
|
|
func (m *AgentsMethods) handleUpdate(ctx context.Context, client *gateway.Client, req *protocol.RequestFrame) {
|
|
locale := store.LocaleFromContext(ctx)
|
|
var params struct {
|
|
AgentID string `json:"agentId"`
|
|
Name string `json:"name"`
|
|
Workspace string `json:"workspace"`
|
|
Model string `json:"model"`
|
|
Avatar string `json:"avatar"`
|
|
// 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.AgentID == "" {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInvalidRequest, i18n.T(locale, i18n.MsgRequired, "agentId")))
|
|
return
|
|
}
|
|
|
|
if m.agentStore != nil {
|
|
// --- DB-backed: update agent in store ---
|
|
ctx := context.Background()
|
|
ag, err := m.agentStore.GetByKey(ctx, params.AgentID)
|
|
if err != nil {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrNotFound, i18n.T(locale, i18n.MsgAgentNotFound, params.AgentID)))
|
|
return
|
|
}
|
|
|
|
updates := map[string]any{}
|
|
if params.Name != "" {
|
|
updates["display_name"] = params.Name
|
|
}
|
|
if params.Workspace != "" {
|
|
ws := config.ExpandHome(params.Workspace)
|
|
updates["workspace"] = ws
|
|
os.MkdirAll(ws, 0755)
|
|
}
|
|
if params.Model != "" {
|
|
updates["model"] = params.Model
|
|
}
|
|
// Per-agent JSONB config overrides
|
|
if len(params.ToolsConfig) > 0 {
|
|
updates["tools_config"] = []byte(params.ToolsConfig)
|
|
}
|
|
if len(params.SubagentsConfig) > 0 {
|
|
updates["subagents_config"] = []byte(params.SubagentsConfig)
|
|
}
|
|
if len(params.SandboxConfig) > 0 {
|
|
updates["sandbox_config"] = []byte(params.SandboxConfig)
|
|
}
|
|
if len(params.MemoryConfig) > 0 {
|
|
updates["memory_config"] = []byte(params.MemoryConfig)
|
|
}
|
|
if len(params.CompactionConfig) > 0 {
|
|
updates["compaction_config"] = []byte(params.CompactionConfig)
|
|
}
|
|
if len(params.ContextPruning) > 0 {
|
|
updates["context_pruning"] = []byte(params.ContextPruning)
|
|
}
|
|
if len(params.OtherConfig) > 0 {
|
|
updates["other_config"] = []byte(params.OtherConfig)
|
|
}
|
|
|
|
if len(updates) > 0 {
|
|
if err := m.agentStore.Update(ctx, ag.ID, updates); err != nil {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrInternal, i18n.T(locale, i18n.MsgFailedToUpdate, "agent", fmt.Sprintf("%v", err))))
|
|
return
|
|
}
|
|
}
|
|
|
|
// Update identity in DB bootstrap — preserve existing fields not being changed.
|
|
if params.Avatar != "" || params.Name != "" {
|
|
// Read existing identity to preserve emoji and other fields.
|
|
existingEmoji, existingAvatar, existingName := "", "", ""
|
|
if dbFiles, err := m.agentStore.GetAgentContextFiles(ctx, ag.ID); err == nil {
|
|
for _, f := range dbFiles {
|
|
if f.FileName == "IDENTITY.md" {
|
|
if identity := parseIdentityContent(f.Content); identity != nil {
|
|
existingEmoji = identity["Emoji"]
|
|
existingAvatar = identity["Avatar"]
|
|
existingName = identity["Name"]
|
|
}
|
|
break
|
|
}
|
|
}
|
|
}
|
|
name := params.Name
|
|
if name == "" {
|
|
name = existingName
|
|
}
|
|
avatar := params.Avatar
|
|
if avatar == "" {
|
|
avatar = existingAvatar
|
|
}
|
|
content := buildIdentityContent(name, existingEmoji, avatar)
|
|
if err := m.agentStore.SetAgentContextFile(ctx, ag.ID, "IDENTITY.md", content); err != nil {
|
|
slog.Warn("failed to update IDENTITY.md", "agent", params.AgentID, "error", err)
|
|
}
|
|
// Invalidate interceptor cache so updated IDENTITY.md is served immediately
|
|
if m.interceptor != nil {
|
|
m.interceptor.InvalidateAgent(ag.ID)
|
|
}
|
|
}
|
|
|
|
m.agents.InvalidateAgent(params.AgentID)
|
|
} else {
|
|
// --- Fallback: config.json ---
|
|
spec, ok := m.cfg.Agents.List[params.AgentID]
|
|
if !ok {
|
|
if params.AgentID != "default" {
|
|
client.SendResponse(protocol.NewErrorResponse(req.ID, protocol.ErrNotFound, i18n.T(locale, i18n.MsgAgentNotFound, params.AgentID)))
|
|
return
|
|
}
|
|
}
|
|
|
|
if params.Name != "" {
|
|
spec.DisplayName = params.Name
|
|
}
|
|
if params.Workspace != "" {
|
|
spec.Workspace = config.ExpandHome(params.Workspace)
|
|
os.MkdirAll(spec.Workspace, 0755)
|
|
}
|
|
if params.Model != "" {
|
|
spec.Model = params.Model
|
|
}
|
|
|
|
if params.AgentID == "default" {
|
|
if params.Model != "" {
|
|
m.cfg.Agents.Defaults.Model = params.Model
|
|
}
|
|
if params.Workspace != "" {
|
|
m.cfg.Agents.Defaults.Workspace = params.Workspace
|
|
}
|
|
} else {
|
|
m.cfg.Agents.List[params.AgentID] = spec
|
|
}
|
|
|
|
if params.Avatar != "" {
|
|
ws := spec.Workspace
|
|
if ws == "" {
|
|
ws = config.ExpandHome(m.cfg.Agents.Defaults.Workspace)
|
|
}
|
|
identityPath := filepath.Join(ws, "IDENTITY.md")
|
|
appendIdentityFields(identityPath, "", "", params.Avatar)
|
|
}
|
|
|
|
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
|
|
}
|
|
}
|
|
|
|
client.SendResponse(protocol.NewOKResponse(req.ID, map[string]any{
|
|
"ok": true,
|
|
"agentId": params.AgentID,
|
|
}))
|
|
emitAudit(m.eventBus, client, "agent.updated", "agent", params.AgentID)
|
|
}
|