mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-02 00:48:55 +02:00
Add files via upload
This commit is contained in:
@@ -341,12 +341,26 @@ type ChatRequest struct {
|
|||||||
Role string `json:"role,omitempty"` // 角色名称
|
Role string `json:"role,omitempty"` // 角色名称
|
||||||
Attachments []ChatAttachment `json:"attachments,omitempty"`
|
Attachments []ChatAttachment `json:"attachments,omitempty"`
|
||||||
WebShellConnectionID string `json:"webshellConnectionId,omitempty"` // WebShell 管理 - AI 助手:当前选中的连接 ID,仅使用 webshell_* 工具
|
WebShellConnectionID string `json:"webshellConnectionId,omitempty"` // WebShell 管理 - AI 助手:当前选中的连接 ID,仅使用 webshell_* 工具
|
||||||
|
AIChannelID string `json:"aiChannelId,omitempty"` // 会话级 AI 通道;空则使用 ai.default_channel
|
||||||
Hitl *HITLRequest `json:"hitl,omitempty"`
|
Hitl *HITLRequest `json:"hitl,omitempty"`
|
||||||
Reasoning *ChatReasoningRequest `json:"reasoning,omitempty"`
|
Reasoning *ChatReasoningRequest `json:"reasoning,omitempty"`
|
||||||
// Orchestration 仅对 /api/multi-agent、/api/multi-agent/stream:deep | plan_execute | supervisor;空则等同 deep。机器人/批量等无请求体时由服务端默认 deep。/api/eino-agent* 不使用此字段。
|
// Orchestration 仅对 /api/multi-agent、/api/multi-agent/stream:deep | plan_execute | supervisor;空则等同 deep。机器人/批量等无请求体时由服务端默认 deep。/api/eino-agent* 不使用此字段。
|
||||||
Orchestration string `json:"orchestration,omitempty"`
|
Orchestration string `json:"orchestration,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *AgentHandler) configForAIChannel(channelID string) (*config.Config, string, error) {
|
||||||
|
if h == nil || h.config == nil {
|
||||||
|
return nil, "", fmt.Errorf("服务器配置未加载")
|
||||||
|
}
|
||||||
|
oa, resolvedID, ok := h.config.ResolveAIChannel(channelID)
|
||||||
|
if !ok {
|
||||||
|
return nil, resolvedID, fmt.Errorf("AI 通道不存在: %s", resolvedID)
|
||||||
|
}
|
||||||
|
cfgCopy := *h.config
|
||||||
|
cfgCopy.OpenAI = oa
|
||||||
|
return &cfgCopy, resolvedID, nil
|
||||||
|
}
|
||||||
|
|
||||||
func chatReasoningToClientIntent(r *ChatReasoningRequest) *reasoning.ClientIntent {
|
func chatReasoningToClientIntent(r *ChatReasoningRequest) *reasoning.ClientIntent {
|
||||||
if r == nil {
|
if r == nil {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
+85
-2
@@ -60,7 +60,7 @@ func (h *C2Handler) SetManager(m *c2.Manager) {
|
|||||||
|
|
||||||
// ListListeners 获取监听器列表
|
// ListListeners 获取监听器列表
|
||||||
func (h *C2Handler) ListListeners(c *gin.Context) {
|
func (h *C2Handler) ListListeners(c *gin.Context) {
|
||||||
listeners, err := h.mgr().DB().ListC2ListenersForAccess(c2AccessFromContext(c))
|
listeners, err := h.mgr().DB().ListC2ListenersForAccess(c2AccessFromContext(c), c.Query("project_id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
@@ -77,6 +77,7 @@ func (h *C2Handler) ListListeners(c *gin.Context) {
|
|||||||
func (h *C2Handler) CreateListener(c *gin.Context) {
|
func (h *C2Handler) CreateListener(c *gin.Context) {
|
||||||
var req struct {
|
var req struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
ProjectID string `json:"project_id,omitempty"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
BindHost string `json:"bind_host"`
|
BindHost string `json:"bind_host"`
|
||||||
BindPort int `json:"bind_port"`
|
BindPort int `json:"bind_port"`
|
||||||
@@ -92,6 +93,7 @@ func (h *C2Handler) CreateListener(c *gin.Context) {
|
|||||||
|
|
||||||
input := c2.CreateListenerInput{
|
input := c2.CreateListenerInput{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
|
ProjectID: req.ProjectID,
|
||||||
Type: req.Type,
|
Type: req.Type,
|
||||||
BindHost: req.BindHost,
|
BindHost: req.BindHost,
|
||||||
BindPort: req.BindPort,
|
BindPort: req.BindPort,
|
||||||
@@ -100,6 +102,10 @@ func (h *C2Handler) CreateListener(c *gin.Context) {
|
|||||||
Config: req.Config,
|
Config: req.Config,
|
||||||
CallbackHost: strings.TrimSpace(req.CallbackHost),
|
CallbackHost: strings.TrimSpace(req.CallbackHost),
|
||||||
}
|
}
|
||||||
|
if !h.canAccessProject(c, input.ProjectID) {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{"error": "project access denied"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
listener, err := h.mgr().CreateListener(input)
|
listener, err := h.mgr().CreateListener(input)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -158,6 +164,7 @@ func (h *C2Handler) UpdateListener(c *gin.Context) {
|
|||||||
|
|
||||||
var req struct {
|
var req struct {
|
||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
|
ProjectID string `json:"project_id"`
|
||||||
BindHost string `json:"bind_host"`
|
BindHost string `json:"bind_host"`
|
||||||
BindPort int `json:"bind_port"`
|
BindPort int `json:"bind_port"`
|
||||||
ProfileID string `json:"profile_id"`
|
ProfileID string `json:"profile_id"`
|
||||||
@@ -179,6 +186,7 @@ func (h *C2Handler) UpdateListener(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
listener.Name = req.Name
|
listener.Name = req.Name
|
||||||
|
listener.ProjectID = strings.TrimSpace(req.ProjectID)
|
||||||
listener.BindHost = req.BindHost
|
listener.BindHost = req.BindHost
|
||||||
listener.BindPort = req.BindPort
|
listener.BindPort = req.BindPort
|
||||||
listener.ProfileID = req.ProfileID
|
listener.ProfileID = req.ProfileID
|
||||||
@@ -187,6 +195,10 @@ func (h *C2Handler) UpdateListener(c *gin.Context) {
|
|||||||
cfgJSON, _ := json.Marshal(req.Config)
|
cfgJSON, _ := json.Marshal(req.Config)
|
||||||
listener.ConfigJSON = string(cfgJSON)
|
listener.ConfigJSON = string(cfgJSON)
|
||||||
}
|
}
|
||||||
|
if !h.canAccessProject(c, listener.ProjectID) {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{"error": "project access denied"})
|
||||||
|
return
|
||||||
|
}
|
||||||
if req.CallbackHost != nil {
|
if req.CallbackHost != nil {
|
||||||
cfg := &c2.ListenerConfig{}
|
cfg := &c2.ListenerConfig{}
|
||||||
raw := strings.TrimSpace(listener.ConfigJSON)
|
raw := strings.TrimSpace(listener.ConfigJSON)
|
||||||
@@ -275,6 +287,7 @@ func (h *C2Handler) StopListener(c *gin.Context) {
|
|||||||
func (h *C2Handler) ListSessions(c *gin.Context) {
|
func (h *C2Handler) ListSessions(c *gin.Context) {
|
||||||
filter := database.ListC2SessionsFilter{
|
filter := database.ListC2SessionsFilter{
|
||||||
ListenerID: c.Query("listener_id"),
|
ListenerID: c.Query("listener_id"),
|
||||||
|
ProjectID: c.Query("project_id"),
|
||||||
Status: c.Query("status"),
|
Status: c.Query("status"),
|
||||||
OS: c.Query("os"),
|
OS: c.Query("os"),
|
||||||
Search: c.Query("search"),
|
Search: c.Query("search"),
|
||||||
@@ -404,6 +417,47 @@ func (h *C2Handler) SetSessionSleep(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, out)
|
c.JSON(http.StatusOK, out)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// SetSessionNote 更新会话备注(仅服务端元数据,不下发植入体)
|
||||||
|
func (h *C2Handler) SetSessionNote(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
var req struct {
|
||||||
|
Note string `json:"note"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&req); err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
note := strings.TrimSpace(req.Note)
|
||||||
|
if len(note) > 2000 {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": "note too long (max 2000 characters)"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
session, err := h.mgr().DB().GetC2Session(id)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if session == nil {
|
||||||
|
c.JSON(http.StatusNotFound, gin.H{"error": "session not found"})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.mgr().DB().SetC2SessionNote(id, note); err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if h.audit != nil {
|
||||||
|
h.audit.RecordOK(c, "c2", "session_note", "更新 C2 会话备注", "c2_session", id, map[string]interface{}{
|
||||||
|
"note_len": len(note),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
c.JSON(http.StatusOK, gin.H{
|
||||||
|
"updated": true,
|
||||||
|
"note": note,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// 任务 API
|
// 任务 API
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -412,7 +466,14 @@ func (h *C2Handler) SetSessionSleep(c *gin.Context) {
|
|||||||
func (h *C2Handler) ListTasks(c *gin.Context) {
|
func (h *C2Handler) ListTasks(c *gin.Context) {
|
||||||
filter := database.ListC2TasksFilter{
|
filter := database.ListC2TasksFilter{
|
||||||
SessionID: c.Query("session_id"),
|
SessionID: c.Query("session_id"),
|
||||||
|
ProjectID: c.Query("project_id"),
|
||||||
Status: c.Query("status"),
|
Status: c.Query("status"),
|
||||||
|
TaskType: c.Query("task_type"),
|
||||||
|
}
|
||||||
|
if since := c.Query("since"); since != "" {
|
||||||
|
if t, err := database.ParseRFC3339Time(since); err == nil {
|
||||||
|
filter.Since = &t
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
paginated := false
|
paginated := false
|
||||||
@@ -447,7 +508,7 @@ func (h *C2Handler) ListTasks(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 仪表盘「待审任务」为全局 queued/pending 数量,与列表 session 过滤无关
|
// 仪表盘「待审任务」为全局 queued/pending 数量,与列表 session 过滤无关
|
||||||
pendingN, _ := h.mgr().DB().CountC2TasksQueuedOrPendingForAccess("", access)
|
pendingN, _ := h.mgr().DB().CountC2TasksQueuedOrPendingForAccess("", filter.ProjectID, access)
|
||||||
|
|
||||||
if !paginated {
|
if !paginated {
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
@@ -462,9 +523,15 @@ func (h *C2Handler) ListTasks(c *gin.Context) {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
statusCounts, err := h.mgr().DB().CountC2TasksByStatusForAccess(filter, access)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
c.JSON(http.StatusOK, gin.H{
|
c.JSON(http.StatusOK, gin.H{
|
||||||
"tasks": tasks,
|
"tasks": tasks,
|
||||||
"total": total,
|
"total": total,
|
||||||
|
"status_counts": statusCounts,
|
||||||
"page": page,
|
"page": page,
|
||||||
"page_size": pageSize,
|
"page_size": pageSize,
|
||||||
"pending_queued_count": pendingN,
|
"pending_queued_count": pendingN,
|
||||||
@@ -784,6 +851,7 @@ func (h *C2Handler) ListEvents(c *gin.Context) {
|
|||||||
filter := database.ListC2EventsFilter{
|
filter := database.ListC2EventsFilter{
|
||||||
Level: c.Query("level"),
|
Level: c.Query("level"),
|
||||||
Category: c.Query("category"),
|
Category: c.Query("category"),
|
||||||
|
ProjectID: c.Query("project_id"),
|
||||||
SessionID: c.Query("session_id"),
|
SessionID: c.Query("session_id"),
|
||||||
TaskID: c.Query("task_id"),
|
TaskID: c.Query("task_id"),
|
||||||
}
|
}
|
||||||
@@ -1121,6 +1189,21 @@ func c2AccessFromContext(c *gin.Context) database.RBACListAccess {
|
|||||||
return database.RBACListAccess{UserID: session.UserID, Scope: session.Scope}
|
return database.RBACListAccess{UserID: session.UserID, Scope: session.Scope}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *C2Handler) canAccessProject(c *gin.Context, projectID string) bool {
|
||||||
|
projectID = strings.TrimSpace(projectID)
|
||||||
|
if projectID == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
session, ok := security.CurrentSession(c)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if session.Scope == database.RBACScopeAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return h.mgr().DB().UserCanAccessResource(session.UserID, session.Scope, "project", projectID)
|
||||||
|
}
|
||||||
|
|
||||||
func (h *C2Handler) c2ResourceAllowed(c *gin.Context, resourceType, resourceID string) bool {
|
func (h *C2Handler) c2ResourceAllowed(c *gin.Context, resourceType, resourceID string) bool {
|
||||||
session, ok := security.CurrentSession(c)
|
session, ok := security.CurrentSession(c)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|||||||
@@ -258,6 +258,7 @@ func (h *ConfigHandler) ApplyWechatRobotBinding(wc config.RobotWechatConfig) err
|
|||||||
|
|
||||||
// GetConfigResponse 获取配置响应
|
// GetConfigResponse 获取配置响应
|
||||||
type GetConfigResponse struct {
|
type GetConfigResponse struct {
|
||||||
|
AI config.AIConfig `json:"ai"`
|
||||||
OpenAI config.OpenAIConfig `json:"openai"`
|
OpenAI config.OpenAIConfig `json:"openai"`
|
||||||
Vision config.VisionConfig `json:"vision"`
|
Vision config.VisionConfig `json:"vision"`
|
||||||
FOFA config.FofaConfig `json:"fofa"`
|
FOFA config.FofaConfig `json:"fofa"`
|
||||||
@@ -363,6 +364,7 @@ func (h *ConfigHandler) GetConfig(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
c.JSON(http.StatusOK, GetConfigResponse{
|
c.JSON(http.StatusOK, GetConfigResponse{
|
||||||
|
AI: h.config.AI,
|
||||||
OpenAI: h.config.OpenAI,
|
OpenAI: h.config.OpenAI,
|
||||||
Vision: h.config.Vision,
|
Vision: h.config.Vision,
|
||||||
FOFA: h.config.FOFA,
|
FOFA: h.config.FOFA,
|
||||||
@@ -706,6 +708,7 @@ func (h *ConfigHandler) GetTools(c *gin.Context) {
|
|||||||
|
|
||||||
// UpdateConfigRequest 更新配置请求
|
// UpdateConfigRequest 更新配置请求
|
||||||
type UpdateConfigRequest struct {
|
type UpdateConfigRequest struct {
|
||||||
|
AI *config.AIConfig `json:"ai,omitempty"`
|
||||||
OpenAI *config.OpenAIConfig `json:"openai,omitempty"`
|
OpenAI *config.OpenAIConfig `json:"openai,omitempty"`
|
||||||
Vision *config.VisionConfig `json:"vision,omitempty"`
|
Vision *config.VisionConfig `json:"vision,omitempty"`
|
||||||
FOFA *config.FofaConfig `json:"fofa,omitempty"`
|
FOFA *config.FofaConfig `json:"fofa,omitempty"`
|
||||||
@@ -785,8 +788,20 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
|||||||
defer h.mu.Unlock()
|
defer h.mu.Unlock()
|
||||||
|
|
||||||
// 更新OpenAI配置
|
// 更新OpenAI配置
|
||||||
|
if req.AI != nil {
|
||||||
|
h.config.AI = *req.AI
|
||||||
|
h.config.ApplyDefaultAIChannel()
|
||||||
|
h.logger.Info("更新 AI 通道配置",
|
||||||
|
zap.String("default_channel", h.config.AI.DefaultChannel),
|
||||||
|
zap.Int("channels", len(h.config.AI.Channels)),
|
||||||
|
)
|
||||||
|
}
|
||||||
if req.OpenAI != nil {
|
if req.OpenAI != nil {
|
||||||
h.config.OpenAI = *req.OpenAI
|
h.config.OpenAI = *req.OpenAI
|
||||||
|
h.config.AI.EnsureDefaultFromOpenAI(h.config.OpenAI)
|
||||||
|
if def := config.NormalizeAIChannelID(h.config.AI.DefaultChannel); def != "" {
|
||||||
|
h.config.AI.Channels[def] = config.AIChannelFromOpenAI(def, "Default", h.config.OpenAI)
|
||||||
|
}
|
||||||
h.logger.Info("更新OpenAI配置",
|
h.logger.Info("更新OpenAI配置",
|
||||||
zap.String("base_url", h.config.OpenAI.BaseURL),
|
zap.String("base_url", h.config.OpenAI.BaseURL),
|
||||||
zap.String("model", h.config.OpenAI.Model),
|
zap.String("model", h.config.OpenAI.Model),
|
||||||
@@ -1683,7 +1698,8 @@ func (h *ConfigHandler) saveConfig() error {
|
|||||||
|
|
||||||
updateAgentConfig(root, h.config.Agent)
|
updateAgentConfig(root, h.config.Agent)
|
||||||
updateMCPConfig(root, h.config.MCP)
|
updateMCPConfig(root, h.config.MCP)
|
||||||
updateOpenAIConfig(root, h.config.OpenAI)
|
updateAIConfig(root, h.config.AI)
|
||||||
|
removeKeyFromMap(root.Content[0], "openai")
|
||||||
updateVisionConfig(root, h.config.Vision)
|
updateVisionConfig(root, h.config.Vision)
|
||||||
updateFOFAConfig(root, h.config.FOFA)
|
updateFOFAConfig(root, h.config.FOFA)
|
||||||
updateSpaceSearchConfig(root, "zoomeye", h.config.ZoomEye)
|
updateSpaceSearchConfig(root, "zoomeye", h.config.ZoomEye)
|
||||||
@@ -1877,6 +1893,69 @@ func updateOpenAIConfig(doc *yaml.Node, cfg config.OpenAIConfig) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func updateAIConfig(doc *yaml.Node, cfg config.AIConfig) {
|
||||||
|
root := doc.Content[0]
|
||||||
|
aiNode := ensureMap(root, "ai")
|
||||||
|
if strings.TrimSpace(cfg.DefaultChannel) != "" {
|
||||||
|
setStringInMap(aiNode, "default_channel", config.NormalizeAIChannelID(cfg.DefaultChannel))
|
||||||
|
}
|
||||||
|
channelsNode := ensureMap(aiNode, "channels")
|
||||||
|
channelsNode.Content = nil
|
||||||
|
normalized := make(map[string]config.AIChannelConfig, len(cfg.Channels))
|
||||||
|
ids := make([]string, 0, len(cfg.Channels))
|
||||||
|
for id, ch := range cfg.Channels {
|
||||||
|
nid := config.NormalizeAIChannelID(id)
|
||||||
|
if nid == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if _, exists := normalized[nid]; !exists {
|
||||||
|
ids = append(ids, nid)
|
||||||
|
}
|
||||||
|
normalized[nid] = ch
|
||||||
|
}
|
||||||
|
sort.Strings(ids)
|
||||||
|
seen := make(map[string]bool, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
if seen[id] {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
seen[id] = true
|
||||||
|
ch := normalized[id]
|
||||||
|
keyNode := &yaml.Node{Kind: yaml.ScalarNode, Tag: "!!str", Value: id}
|
||||||
|
channelNode := &yaml.Node{Kind: yaml.MappingNode, Tag: "!!map"}
|
||||||
|
channelsNode.Content = append(channelsNode.Content, keyNode, channelNode)
|
||||||
|
setStringInMap(channelNode, "name", ch.Name)
|
||||||
|
if strings.TrimSpace(ch.Provider) != "" {
|
||||||
|
setStringInMap(channelNode, "provider", ch.Provider)
|
||||||
|
}
|
||||||
|
setStringInMap(channelNode, "api_key", ch.APIKey)
|
||||||
|
setStringInMap(channelNode, "base_url", ch.BaseURL)
|
||||||
|
setStringInMap(channelNode, "model", ch.Model)
|
||||||
|
if ch.MaxTotalTokens > 0 {
|
||||||
|
setIntInMap(channelNode, "max_total_tokens", ch.MaxTotalTokens)
|
||||||
|
}
|
||||||
|
if ch.MaxCompletionTokens > 0 {
|
||||||
|
setIntInMap(channelNode, "max_completion_tokens", ch.MaxCompletionTokens)
|
||||||
|
}
|
||||||
|
rn := ensureMap(channelNode, "reasoning")
|
||||||
|
if strings.TrimSpace(ch.Reasoning.Mode) != "" {
|
||||||
|
setStringInMap(rn, "mode", ch.Reasoning.Mode)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(ch.Reasoning.Effort) != "" {
|
||||||
|
setStringInMap(rn, "effort", ch.Reasoning.Effort)
|
||||||
|
}
|
||||||
|
if ch.Reasoning.AllowClientReasoning != nil {
|
||||||
|
setBoolInMap(rn, "allow_client_reasoning", *ch.Reasoning.AllowClientReasoning)
|
||||||
|
}
|
||||||
|
if strings.TrimSpace(ch.Reasoning.Profile) != "" {
|
||||||
|
setStringInMap(rn, "profile", ch.Reasoning.Profile)
|
||||||
|
}
|
||||||
|
if len(rn.Content) == 0 {
|
||||||
|
removeKeyFromMap(channelNode, "reasoning")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func updateFOFAConfig(doc *yaml.Node, cfg config.FofaConfig) {
|
func updateFOFAConfig(doc *yaml.Node, cfg config.FofaConfig) {
|
||||||
root := doc.Content[0]
|
root := doc.Content[0]
|
||||||
fofaNode := ensureMap(root, "fofa")
|
fofaNode := ensureMap(root, "fofa")
|
||||||
|
|||||||
@@ -149,6 +149,14 @@ func (h *AgentHandler) EinoSingleAgentLoopStream(c *gin.Context) {
|
|||||||
sendEvent("done", "", map[string]interface{}{"conversationId": conversationID})
|
sendEvent("done", "", map[string]interface{}{"conversationId": conversationID})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
runCfg, resolvedAIChannelID, err := h.configForAIChannel(req.AIChannelID)
|
||||||
|
if err != nil {
|
||||||
|
taskStatus = "failed"
|
||||||
|
h.tasks.UpdateTaskStatus(conversationID, taskStatus)
|
||||||
|
sendEvent("error", err.Error(), nil)
|
||||||
|
sendEvent("done", "", map[string]interface{}{"conversationId": conversationID})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var result *multiagent.RunResult
|
var result *multiagent.RunResult
|
||||||
var runErr error
|
var runErr error
|
||||||
@@ -222,8 +230,8 @@ func (h *AgentHandler) EinoSingleAgentLoopStream(c *gin.Context) {
|
|||||||
|
|
||||||
result, runErr = multiagent.RunEinoSingleChatModelAgent(
|
result, runErr = multiagent.RunEinoSingleChatModelAgent(
|
||||||
taskCtxLoop,
|
taskCtxLoop,
|
||||||
h.config,
|
runCfg,
|
||||||
&h.config.MultiAgent,
|
&runCfg.MultiAgent,
|
||||||
h.agent,
|
h.agent,
|
||||||
h.db,
|
h.db,
|
||||||
h.logger,
|
h.logger,
|
||||||
@@ -236,6 +244,7 @@ func (h *AgentHandler) EinoSingleAgentLoopStream(c *gin.Context) {
|
|||||||
chatReasoningToClientIntent(req.Reasoning),
|
chatReasoningToClientIntent(req.Reasoning),
|
||||||
h.agentSessionContextBlock(conversationID),
|
h.agentSessionContextBlock(conversationID),
|
||||||
)
|
)
|
||||||
|
_ = resolvedAIChannelID
|
||||||
|
|
||||||
if result != nil && len(result.MCPExecutionIDs) > 0 {
|
if result != nil && len(result.MCPExecutionIDs) > 0 {
|
||||||
cumulativeMCPExecutionIDs = mergeMCPExecutionIDLists(cumulativeMCPExecutionIDs, result.MCPExecutionIDs)
|
cumulativeMCPExecutionIDs = mergeMCPExecutionIDLists(cumulativeMCPExecutionIDs, result.MCPExecutionIDs)
|
||||||
@@ -410,6 +419,11 @@ func (h *AgentHandler) EinoSingleAgentLoop(c *gin.Context) {
|
|||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器配置未加载"})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "服务器配置未加载"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
runCfg, _, err := h.configForAIChannel(req.AIChannelID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
curHist := prep.History
|
curHist := prep.History
|
||||||
curMsg := prep.FinalMessage
|
curMsg := prep.FinalMessage
|
||||||
@@ -418,8 +432,8 @@ func (h *AgentHandler) EinoSingleAgentLoop(c *gin.Context) {
|
|||||||
for {
|
for {
|
||||||
result, runErr = multiagent.RunEinoSingleChatModelAgent(
|
result, runErr = multiagent.RunEinoSingleChatModelAgent(
|
||||||
taskCtx,
|
taskCtx,
|
||||||
h.config,
|
runCfg,
|
||||||
&h.config.MultiAgent,
|
&runCfg.MultiAgent,
|
||||||
h.agent,
|
h.agent,
|
||||||
h.db,
|
h.db,
|
||||||
h.logger,
|
h.logger,
|
||||||
|
|||||||
@@ -158,6 +158,12 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
|
|||||||
|
|
||||||
stopKeepalive := runSSEKeepalive(c, &sseWriteMu)
|
stopKeepalive := runSSEKeepalive(c, &sseWriteMu)
|
||||||
defer stopKeepalive()
|
defer stopKeepalive()
|
||||||
|
runCfg, _, err := h.configForAIChannel(req.AIChannelID)
|
||||||
|
if err != nil {
|
||||||
|
sendEvent("error", err.Error(), nil)
|
||||||
|
sendEvent("done", "", map[string]interface{}{"conversationId": conversationID})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var result *multiagent.RunResult
|
var result *multiagent.RunResult
|
||||||
var runErr error
|
var runErr error
|
||||||
@@ -232,8 +238,8 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
|
|||||||
|
|
||||||
result, runErr = multiagent.RunDeepAgent(
|
result, runErr = multiagent.RunDeepAgent(
|
||||||
taskCtxLoop,
|
taskCtxLoop,
|
||||||
h.config,
|
runCfg,
|
||||||
&h.config.MultiAgent,
|
&runCfg.MultiAgent,
|
||||||
h.agent,
|
h.agent,
|
||||||
h.db,
|
h.db,
|
||||||
h.logger,
|
h.logger,
|
||||||
@@ -421,6 +427,11 @@ func (h *AgentHandler) MultiAgentLoop(c *gin.Context) {
|
|||||||
taskCtx = multiagent.WithHITLToolInterceptor(taskCtx, func(ctx context.Context, toolName, arguments string) (string, error) {
|
taskCtx = multiagent.WithHITLToolInterceptor(taskCtx, func(ctx context.Context, toolName, arguments string) (string, error) {
|
||||||
return h.interceptHITLForEinoTool(ctx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, nil, toolName, arguments)
|
return h.interceptHITLForEinoTool(ctx, cancelWithCause, prep.ConversationID, prep.AssistantMessageID, nil, toolName, arguments)
|
||||||
})
|
})
|
||||||
|
runCfg, _, err := h.configForAIChannel(req.AIChannelID)
|
||||||
|
if err != nil {
|
||||||
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
curHist := prep.History
|
curHist := prep.History
|
||||||
curMsg := prep.FinalMessage
|
curMsg := prep.FinalMessage
|
||||||
@@ -429,8 +440,8 @@ func (h *AgentHandler) MultiAgentLoop(c *gin.Context) {
|
|||||||
for {
|
for {
|
||||||
result, runErr = multiagent.RunDeepAgent(
|
result, runErr = multiagent.RunDeepAgent(
|
||||||
taskCtx,
|
taskCtx,
|
||||||
h.config,
|
runCfg,
|
||||||
&h.config.MultiAgent,
|
&runCfg.MultiAgent,
|
||||||
h.agent,
|
h.agent,
|
||||||
h.db,
|
h.db,
|
||||||
h.logger,
|
h.logger,
|
||||||
|
|||||||
@@ -352,26 +352,28 @@ func NewWebShellHandler(logger *zap.Logger, db *database.DB) *WebShellHandler {
|
|||||||
|
|
||||||
// CreateConnectionRequest 创建连接请求
|
// CreateConnectionRequest 创建连接请求
|
||||||
type CreateConnectionRequest struct {
|
type CreateConnectionRequest struct {
|
||||||
URL string `json:"url" binding:"required"`
|
ProjectID string `json:"project_id"`
|
||||||
Password string `json:"password"`
|
URL string `json:"url" binding:"required"`
|
||||||
Type string `json:"type"`
|
Password string `json:"password"`
|
||||||
Method string `json:"method"`
|
Type string `json:"type"`
|
||||||
CmdParam string `json:"cmd_param"`
|
Method string `json:"method"`
|
||||||
Remark string `json:"remark"`
|
CmdParam string `json:"cmd_param"`
|
||||||
Encoding string `json:"encoding"`
|
Remark string `json:"remark"`
|
||||||
OS string `json:"os"`
|
Encoding string `json:"encoding"`
|
||||||
|
OS string `json:"os"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateConnectionRequest 更新连接请求
|
// UpdateConnectionRequest 更新连接请求
|
||||||
type UpdateConnectionRequest struct {
|
type UpdateConnectionRequest struct {
|
||||||
URL string `json:"url" binding:"required"`
|
ProjectID string `json:"project_id"`
|
||||||
Password string `json:"password"`
|
URL string `json:"url" binding:"required"`
|
||||||
Type string `json:"type"`
|
Password string `json:"password"`
|
||||||
Method string `json:"method"`
|
Type string `json:"type"`
|
||||||
CmdParam string `json:"cmd_param"`
|
Method string `json:"method"`
|
||||||
Remark string `json:"remark"`
|
CmdParam string `json:"cmd_param"`
|
||||||
Encoding string `json:"encoding"`
|
Remark string `json:"remark"`
|
||||||
OS string `json:"os"`
|
Encoding string `json:"encoding"`
|
||||||
|
OS string `json:"os"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ListConnections 列出所有 WebShell 连接(GET /api/webshell/connections)
|
// ListConnections 列出所有 WebShell 连接(GET /api/webshell/connections)
|
||||||
@@ -381,7 +383,7 @@ func (h *WebShellHandler) ListConnections(c *gin.Context) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
session, _ := security.CurrentSession(c)
|
session, _ := security.CurrentSession(c)
|
||||||
list, err := h.db.ListWebshellConnectionsForAccess(session.UserID, session.Scope)
|
list, err := h.db.ListWebshellConnectionsForAccess(session.UserID, session.Scope, c.Query("project_id"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||||
return
|
return
|
||||||
@@ -412,6 +414,11 @@ func (h *WebShellHandler) CreateConnection(c *gin.Context) {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid url"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid url"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
projectID := strings.TrimSpace(req.ProjectID)
|
||||||
|
if !h.canAccessProject(c, projectID) {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{"error": "project access denied"})
|
||||||
|
return
|
||||||
|
}
|
||||||
method := strings.ToLower(strings.TrimSpace(req.Method))
|
method := strings.ToLower(strings.TrimSpace(req.Method))
|
||||||
if method != "get" && method != "post" {
|
if method != "get" && method != "post" {
|
||||||
method = "post"
|
method = "post"
|
||||||
@@ -422,6 +429,7 @@ func (h *WebShellHandler) CreateConnection(c *gin.Context) {
|
|||||||
}
|
}
|
||||||
conn := &database.WebShellConnection{
|
conn := &database.WebShellConnection{
|
||||||
ID: "ws_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:12],
|
ID: "ws_" + strings.ReplaceAll(uuid.New().String(), "-", "")[:12],
|
||||||
|
ProjectID: projectID,
|
||||||
URL: req.URL,
|
URL: req.URL,
|
||||||
Password: strings.TrimSpace(req.Password),
|
Password: strings.TrimSpace(req.Password),
|
||||||
Type: shellType,
|
Type: shellType,
|
||||||
@@ -477,6 +485,11 @@ func (h *WebShellHandler) UpdateConnection(c *gin.Context) {
|
|||||||
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid url"})
|
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid url"})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
projectID := strings.TrimSpace(req.ProjectID)
|
||||||
|
if !h.canAccessProject(c, projectID) {
|
||||||
|
c.JSON(http.StatusForbidden, gin.H{"error": "project access denied"})
|
||||||
|
return
|
||||||
|
}
|
||||||
method := strings.ToLower(strings.TrimSpace(req.Method))
|
method := strings.ToLower(strings.TrimSpace(req.Method))
|
||||||
if method != "get" && method != "post" {
|
if method != "get" && method != "post" {
|
||||||
method = "post"
|
method = "post"
|
||||||
@@ -486,15 +499,16 @@ func (h *WebShellHandler) UpdateConnection(c *gin.Context) {
|
|||||||
shellType = "php"
|
shellType = "php"
|
||||||
}
|
}
|
||||||
conn := &database.WebShellConnection{
|
conn := &database.WebShellConnection{
|
||||||
ID: id,
|
ID: id,
|
||||||
URL: req.URL,
|
ProjectID: projectID,
|
||||||
Password: strings.TrimSpace(req.Password),
|
URL: req.URL,
|
||||||
Type: shellType,
|
Password: strings.TrimSpace(req.Password),
|
||||||
Method: method,
|
Type: shellType,
|
||||||
CmdParam: strings.TrimSpace(req.CmdParam),
|
Method: method,
|
||||||
Remark: strings.TrimSpace(req.Remark),
|
CmdParam: strings.TrimSpace(req.CmdParam),
|
||||||
Encoding: normalizeWebshellEncoding(req.Encoding),
|
Remark: strings.TrimSpace(req.Remark),
|
||||||
OS: normalizeWebshellOS(req.OS),
|
Encoding: normalizeWebshellEncoding(req.Encoding),
|
||||||
|
OS: normalizeWebshellOS(req.OS),
|
||||||
}
|
}
|
||||||
if err := h.db.UpdateWebshellConnection(conn); err != nil {
|
if err := h.db.UpdateWebshellConnection(conn); err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
@@ -940,6 +954,21 @@ func (h *WebShellHandler) authorizedWebshellConnection(c *gin.Context, connectio
|
|||||||
return conn, true
|
return conn, true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *WebShellHandler) canAccessProject(c *gin.Context, projectID string) bool {
|
||||||
|
projectID = strings.TrimSpace(projectID)
|
||||||
|
if projectID == "" || h.db == nil {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
session, ok := security.CurrentSession(c)
|
||||||
|
if !ok {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if session.Scope == database.RBACScopeAll {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return h.db.UserCanAccessResource(session.UserID, session.Scope, "project", projectID)
|
||||||
|
}
|
||||||
|
|
||||||
// ExecWithConnection 在指定 WebShell 连接上执行命令(供 MCP/Agent 等非 HTTP 调用)
|
// ExecWithConnection 在指定 WebShell 连接上执行命令(供 MCP/Agent 等非 HTTP 调用)
|
||||||
func (h *WebShellHandler) ExecWithConnection(conn *database.WebShellConnection, command string) (output string, ok bool, errMsg string) {
|
func (h *WebShellHandler) ExecWithConnection(conn *database.WebShellConnection, command string) (output string, ok bool, errMsg string) {
|
||||||
if conn == nil {
|
if conn == nil {
|
||||||
|
|||||||
Reference in New Issue
Block a user