diff --git a/client/agent_manage.go b/client/agent_manage.go index aba7787e..74cfea17 100644 --- a/client/agent_manage.go +++ b/client/agent_manage.go @@ -15,76 +15,75 @@ import ( // Agent represents an agent entity type Agent struct { - ID string `json:"id"` - Name string `json:"name"` - Description string `json:"description"` - Avatar string `json:"avatar"` - IsBuiltin bool `json:"is_builtin"` - TenantID uint64 `json:"tenant_id"` - CreatedBy string `json:"created_by"` - RunnableByViewer bool `json:"runnable_by_viewer"` - Config *AgentConfig `json:"config"` - CreatedAt time.Time `json:"created_at"` - UpdatedAt time.Time `json:"updated_at"` - CreatorName string `json:"creator_name,omitempty"` + ID string `json:"id"` + Name string `json:"name"` + Description string `json:"description"` + Avatar string `json:"avatar"` + IsBuiltin bool `json:"is_builtin"` + TenantID uint64 `json:"tenant_id"` + CreatedBy string `json:"created_by"` + Config *AgentConfig `json:"config"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + CreatorName string `json:"creator_name,omitempty"` } // AgentConfig represents the configuration for an agent. // Field names and JSON tags mirror internal/types.CustomAgentConfig. type AgentConfig struct { - AgentMode string `json:"agent_mode"` - AgentType string `json:"agent_type,omitempty"` - SystemPrompt string `json:"system_prompt"` - SystemPromptID string `json:"system_prompt_id,omitempty"` - ContextTemplate string `json:"context_template"` - ContextTemplateID string `json:"context_template_id,omitempty"` - ModelID string `json:"model_id"` - RerankModelID string `json:"rerank_model_id"` - Temperature float64 `json:"temperature"` - MaxCompletionTokens int `json:"max_completion_tokens"` - Thinking *bool `json:"thinking"` - MaxIterations int `json:"max_iterations"` - LLMCallTimeout int `json:"llm_call_timeout,omitempty"` - AllowedTools []string `json:"allowed_tools"` - MCPSelectionMode string `json:"mcp_selection_mode"` - MCPServices []string `json:"mcp_services"` - SkillsSelectionMode string `json:"skills_selection_mode"` - SelectedSkills []string `json:"selected_skills"` - KBSelectionMode string `json:"kb_selection_mode"` - KnowledgeBases []string `json:"knowledge_bases"` - RetrieveKBOnlyWhenMentioned bool `json:"retrieve_kb_only_when_mentioned"` - RetainRetrievalHistory bool `json:"retain_retrieval_history"` - ImageUploadEnabled bool `json:"image_upload_enabled"` - VLMModelID string `json:"vlm_model_id"` - AudioUploadEnabled bool `json:"audio_upload_enabled"` - ASRModelID string `json:"asr_model_id"` - ImageStorageProvider string `json:"image_storage_provider"` - SupportedFileTypes []string `json:"supported_file_types"` - DataAnalysisEnabled bool `json:"data_analysis_enabled"` - FAQPriorityEnabled bool `json:"faq_priority_enabled"` - FAQDirectAnswerThreshold float64 `json:"faq_direct_answer_threshold"` - FAQScoreBoost float64 `json:"faq_score_boost"` - WebSearchEnabled bool `json:"web_search_enabled"` - WebSearchMaxResults int `json:"web_search_max_results"` - WebSearchProviderID string `json:"web_search_provider_id,omitempty"` - WebFetchEnabled bool `json:"web_fetch_enabled"` - WebFetchTopN int `json:"web_fetch_top_n,omitempty"` - MultiTurnEnabled bool `json:"multi_turn_enabled"` - HistoryTurns int `json:"history_turns"` - EmbeddingTopK int `json:"embedding_top_k"` - KeywordThreshold float64 `json:"keyword_threshold"` - VectorThreshold float64 `json:"vector_threshold"` - RerankTopK int `json:"rerank_top_k"` - RerankThreshold float64 `json:"rerank_threshold"` - EnableQueryExpansion bool `json:"enable_query_expansion"` - EnableRewrite bool `json:"enable_rewrite"` - RewritePromptSystem string `json:"rewrite_prompt_system"` - RewritePromptUser string `json:"rewrite_prompt_user"` - QueryUnderstandModelID string `json:"query_understand_model_id,omitempty"` - FallbackStrategy string `json:"fallback_strategy"` - FallbackResponse string `json:"fallback_response"` - FallbackPrompt string `json:"fallback_prompt"` - SuggestedPrompts []string `json:"suggested_prompts,omitempty"` + AgentMode string `json:"agent_mode"` + AgentType string `json:"agent_type,omitempty"` + SystemPrompt string `json:"system_prompt"` + SystemPromptID string `json:"system_prompt_id,omitempty"` + ContextTemplate string `json:"context_template"` + ContextTemplateID string `json:"context_template_id,omitempty"` + ModelID string `json:"model_id"` + RerankModelID string `json:"rerank_model_id"` + Temperature float64 `json:"temperature"` + MaxCompletionTokens int `json:"max_completion_tokens"` + Thinking *bool `json:"thinking"` + MaxIterations int `json:"max_iterations"` + LLMCallTimeout int `json:"llm_call_timeout,omitempty"` + AllowedTools []string `json:"allowed_tools"` + MCPSelectionMode string `json:"mcp_selection_mode"` + MCPServices []string `json:"mcp_services"` + SkillsSelectionMode string `json:"skills_selection_mode"` + SelectedSkills []string `json:"selected_skills"` + KBSelectionMode string `json:"kb_selection_mode"` + KnowledgeBases []string `json:"knowledge_bases"` + RetrieveKBOnlyWhenMentioned bool `json:"retrieve_kb_only_when_mentioned"` + RetainRetrievalHistory bool `json:"retain_retrieval_history"` + ImageUploadEnabled bool `json:"image_upload_enabled"` + VLMModelID string `json:"vlm_model_id"` + AudioUploadEnabled bool `json:"audio_upload_enabled"` + ASRModelID string `json:"asr_model_id"` + ImageStorageProvider string `json:"image_storage_provider"` + SupportedFileTypes []string `json:"supported_file_types"` + DataAnalysisEnabled bool `json:"data_analysis_enabled"` + FAQPriorityEnabled bool `json:"faq_priority_enabled"` + FAQDirectAnswerThreshold float64 `json:"faq_direct_answer_threshold"` + FAQScoreBoost float64 `json:"faq_score_boost"` + WebSearchEnabled bool `json:"web_search_enabled"` + WebSearchMaxResults int `json:"web_search_max_results"` + WebSearchProviderID string `json:"web_search_provider_id,omitempty"` + WebFetchEnabled bool `json:"web_fetch_enabled"` + WebFetchTopN int `json:"web_fetch_top_n,omitempty"` + MultiTurnEnabled bool `json:"multi_turn_enabled"` + HistoryTurns int `json:"history_turns"` + EmbeddingTopK int `json:"embedding_top_k"` + KeywordThreshold float64 `json:"keyword_threshold"` + VectorThreshold float64 `json:"vector_threshold"` + RerankTopK int `json:"rerank_top_k"` + RerankThreshold float64 `json:"rerank_threshold"` + EnableQueryExpansion bool `json:"enable_query_expansion"` + EnableRewrite bool `json:"enable_rewrite"` + RewritePromptSystem string `json:"rewrite_prompt_system"` + RewritePromptUser string `json:"rewrite_prompt_user"` + QueryUnderstandModelID string `json:"query_understand_model_id,omitempty"` + FallbackStrategy string `json:"fallback_strategy"` + FallbackResponse string `json:"fallback_response"` + FallbackPrompt string `json:"fallback_prompt"` + SuggestedPrompts []string `json:"suggested_prompts,omitempty"` } // CreateAgentRequest represents the request to create an agent. diff --git a/internal/handler/session/qa.go b/internal/handler/session/qa.go index be216c55..916b9301 100644 --- a/internal/handler/session/qa.go +++ b/internal/handler/session/qa.go @@ -583,28 +583,6 @@ func (h *Handler) AgentQA(c *gin.Context) { agentModeEnabled, reqCtx.customAgent.Config.AgentMode) } - // Tenant-RBAC gate: block Viewers from running agents whose author - // has cleared RunnableByViewer. The flag exists so an admin can mark - // an agent as "internal tools only" without turning every viewer - // into a contributor. Gated behind cfg.Tenant.EnableRBAC so the - // check is dormant during the rollout window — same pattern as - // middleware/rbac.go. - if agentModeEnabled && reqCtx.customAgent != nil && !reqCtx.customAgent.RunnableByViewer { - role := types.TenantRoleFromContext(reqCtx.ctx) - if !role.HasPermission(types.TenantRoleContributor) { - if h.config != nil && h.config.Tenant.IsRBACEnforced() { - logger.Warnf(reqCtx.ctx, - "[rbac] agent run blocked: viewer cannot run runnable_by_viewer=false agent: agent=%s role=%s", - reqCtx.customAgent.ID, role) - c.Error(errors.NewForbiddenError("Forbidden: this agent is restricted to contributors and above")) - return - } - logger.Warnf(reqCtx.ctx, - "[rbac] agent run would be blocked (logged, not enforced): agent=%s role=%s", - reqCtx.customAgent.ID, role) - } - } - // Sanity gate: agent mode requires a resolved CustomAgent. If we got // here with agent_enabled=true but agent_id missing/unresolvable, the // AgentQA service will fail deep inside the async goroutine with a diff --git a/internal/logger/logger.go b/internal/logger/logger.go index 06a6df16..2f0ff8d0 100644 --- a/internal/logger/logger.go +++ b/internal/logger/logger.go @@ -503,9 +503,8 @@ func CloneContext(ctx context.Context) context.Context { // active tenant (PR 2 #1303). Must be propagated for the same // reason as TenantIDContextKey — any handler that does // `ctx := logger.CloneContext(c.Request.Context())` and then - // reads role via TenantRoleFromContext (e.g. session/qa.go's - // "viewer cannot run runnable_by_viewer=false agent" gate) - // would otherwise see the type-zero TenantRole and fall back + // reads role via TenantRoleFromContext would otherwise see the + // type-zero TenantRole and fall back // to Viewer, blocking even Owners. types.TenantRoleContextKey, types.LanguageContextKey, diff --git a/internal/types/custom_agent.go b/internal/types/custom_agent.go index 43d9a2ce..10e7b078 100644 --- a/internal/types/custom_agent.go +++ b/internal/types/custom_agent.go @@ -75,25 +75,6 @@ type CustomAgent struct { TenantID uint64 `yaml:"tenant_id" json:"tenant_id" gorm:"primaryKey"` // Created by user ID CreatedBy string `yaml:"created_by" json:"created_by" gorm:"type:varchar(36)"` - // RunnableByViewer controls whether users with TenantRoleViewer can - // run this agent IN AGENT MODE (i.e. with tools, MCP, sandboxed code - // execution, etc.). Plain RAG / Wiki QA sessions against the same - // agent are unaffected — they don't invoke tools, so restricting - // them buys no security and would just block Viewer consumption of - // published agents. Defaults to true; admins toggle it off for - // agents whose tools should be restricted to contributors and above. - // - // Enforcement lives in handler/session/qa.go's AgentQA, gated behind - // cfg.Tenant.EnableRBAC like every other PR2 check. - // - // Note: no GORM `default:true` tag here. With a plain `bool` field that - // tag causes GORM to treat Go's zero value (false) as "unspecified" and - // silently rewrite it to the DB default (true) on insert — preventing - // admins from ever creating an agent with viewer-runtime disabled. The - // column-level DEFAULT TRUE on the database side still covers rows - // inserted by raw SQL / migrations; GORM Create always writes the - // explicit struct value. - RunnableByViewer bool `yaml:"runnable_by_viewer" json:"runnable_by_viewer"` // Agent configuration Config CustomAgentConfig `yaml:"config" json:"config" gorm:"type:json"`