diff --git a/internal/http/agents.go b/internal/http/agents.go index 5741ad05..48bc3e67 100644 --- a/internal/http/agents.go +++ b/internal/http/agents.go @@ -347,16 +347,15 @@ func (h *AgentsHandler) handleUpdate(w http.ResponseWriter, r *http.Request) { return } - // Only owner can update + // Tenant admins can update any agent in their tenant (adminMiddleware already + // verified RoleAdmin). System owners can update any agent across tenants. + // GetByID respects tenant scoping from context, so if the agent is returned + // it belongs to the caller's tenant. ag, err := h.agents.GetByID(r.Context(), id) if err != nil { writeError(w, http.StatusNotFound, protocol.ErrNotFound, i18n.T(locale, i18n.MsgNotFound, "agent", id.String())) return } - if userID != "" && ag.OwnerID != userID && !h.isOwnerUser(userID) { - writeError(w, http.StatusForbidden, protocol.ErrUnauthorized, i18n.T(locale, i18n.MsgOwnerOnly, "update agent")) - return - } var updates map[string]any if !bindJSON(w, r, locale, &updates) { @@ -426,8 +425,9 @@ func (h *AgentsHandler) handleUpdate(w http.ResponseWriter, r *http.Request) { } if err := h.agents.Update(r.Context(), id, allowed); err != nil { - slog.Error("agents.update", "id", id, "error", err) - writeError(w, http.StatusInternalServerError, protocol.ErrInternal, i18n.T(locale, i18n.MsgFailedToUpdate, "agent", "internal error")) + slog.Error("agents.update", "id", id, "user_id", userID, + "tenant_id", store.TenantIDFromContext(r.Context()), "error", err) + writeError(w, http.StatusInternalServerError, protocol.ErrInternal, i18n.T(locale, i18n.MsgFailedToUpdate, "agent", err.Error())) return } diff --git a/internal/tools/policy.go b/internal/tools/policy.go index e799feba..df3cff0a 100644 --- a/internal/tools/policy.go +++ b/internal/tools/policy.go @@ -28,12 +28,16 @@ var toolGroups = map[string][]string{ "goclaw": { "read_file", "write_file", "list_files", "edit", "exec", "web_search", "web_fetch", "browser", - "memory_search", "memory_get", + "memory_search", "memory_get", "memory_expand", + "knowledge_graph_search", "vault_search", "sessions_list", "sessions_history", "sessions_send", "spawn", "session_status", - "cron", "message", "create_forum_topic", "list_group_members", + "delegate", + "cron", "datetime", "heartbeat", + "message", "create_forum_topic", "list_group_members", "read_image", "read_document", "read_audio", "read_video", - "create_image", "create_video", - "skill_search", "mcp_tool_search", "tts", + "create_image", "create_video", "create_audio", + "skill_search", "skill_manage", "publish_skill", "use_skill", + "mcp_tool_search", "tts", "team_tasks", }, }