mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-08-23 11:22:47 +02:00
Add files via upload
This commit is contained in:
@@ -12,6 +12,7 @@
|
|||||||

|

|
||||||
|
|
||||||
## Changelog
|
## Changelog
|
||||||
|
- 2025.11.16 Added large result pagination feature: when tool execution results exceed the threshold (default 50KB), automatically save to file and return execution ID, support paginated queries, keyword search, conditional filtering, and regex matching through query_execution_result tool, effectively solving the problem of overly long single responses and improving large file processing capabilities
|
||||||
- 2025.11.15 Added external MCP integration feature: support for integrating external MCP servers to extend tool capabilities, supports both stdio and HTTP transport modes, tool-level enable/disable control, complete configuration guide and management APIs
|
- 2025.11.15 Added external MCP integration feature: support for integrating external MCP servers to extend tool capabilities, supports both stdio and HTTP transport modes, tool-level enable/disable control, complete configuration guide and management APIs
|
||||||
- 2025.11.14 Performance optimizations: optimized tool lookup from O(n) to O(1) using index map, added automatic cleanup mechanism for execution records to prevent memory leaks, and added pagination support for database queries
|
- 2025.11.14 Performance optimizations: optimized tool lookup from O(n) to O(1) using index map, added automatic cleanup mechanism for execution records to prevent memory leaks, and added pagination support for database queries
|
||||||
- 2025.11.13 Added authentication for the web mode, including automatic password generation and in-app password change
|
- 2025.11.13 Added authentication for the web mode, including automatic password generation and in-app password change
|
||||||
@@ -30,6 +31,7 @@
|
|||||||
- 💬 **Conversational Interface** - Natural language conversation interface with streaming output (SSE), real-time execution viewing
|
- 💬 **Conversational Interface** - Natural language conversation interface with streaming output (SSE), real-time execution viewing
|
||||||
- 📊 **Conversation History Management** - Complete conversation history records, supports viewing, deletion, and management
|
- 📊 **Conversation History Management** - Complete conversation history records, supports viewing, deletion, and management
|
||||||
- ⚙️ **Visual Configuration Management** - Web interface for system settings, supports real-time loading and saving configurations with required field validation
|
- ⚙️ **Visual Configuration Management** - Web interface for system settings, supports real-time loading and saving configurations with required field validation
|
||||||
|
- 📄 **Large Result Pagination** - When tool execution results exceed the threshold, automatically save to file, support paginated queries, keyword search, conditional filtering, and regex matching, effectively solving the problem of overly long single responses, with examples for various tools (head, tail, grep, sed, etc.) for segmented reading
|
||||||
|
|
||||||
### Tool Integration
|
### Tool Integration
|
||||||
- 🔌 **MCP Protocol Support** - Complete MCP protocol implementation, supports tool registration, invocation, and monitoring
|
- 🔌 **MCP Protocol Support** - Complete MCP protocol implementation, supports tool registration, invocation, and monitoring
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||

|

|
||||||
|
|
||||||
## 更新日志
|
## 更新日志
|
||||||
|
- 2025.11.16 新增大结果分段读取功能:当工具执行结果超过阈值(默认50KB)时,自动保存到文件并返回执行ID,支持通过 query_execution_result 工具进行分页查询、关键词搜索、条件过滤和正则表达式匹配,有效解决单次返回过长的问题,提升大文件处理能力
|
||||||
- 2025.11.15 新增外部 MCP 接入功能:支持接入外部 MCP 服务器扩展工具能力,支持 stdio 和 HTTP 两种传输模式,支持工具级别的启用/禁用控制,提供完整的配置指南和管理接口
|
- 2025.11.15 新增外部 MCP 接入功能:支持接入外部 MCP 服务器扩展工具能力,支持 stdio 和 HTTP 两种传输模式,支持工具级别的启用/禁用控制,提供完整的配置指南和管理接口
|
||||||
- 2025.11.14 性能优化:工具查找从 O(n) 优化为 O(1)(使用索引映射),添加执行记录自动清理机制防止内存泄漏,数据库查询支持分页加载
|
- 2025.11.14 性能优化:工具查找从 O(n) 优化为 O(1)(使用索引映射),添加执行记录自动清理机制防止内存泄漏,数据库查询支持分页加载
|
||||||
- 2025.11.13 Web 端新增统一鉴权,支持自动生成强密码与前端修改密码;
|
- 2025.11.13 Web 端新增统一鉴权,支持自动生成强密码与前端修改密码;
|
||||||
@@ -28,6 +29,7 @@
|
|||||||
- 💬 **对话式交互** - 自然语言对话界面,支持流式输出(SSE),实时查看执行过程
|
- 💬 **对话式交互** - 自然语言对话界面,支持流式输出(SSE),实时查看执行过程
|
||||||
- 📊 **对话历史管理** - 完整的对话历史记录,支持查看、删除和管理
|
- 📊 **对话历史管理** - 完整的对话历史记录,支持查看、删除和管理
|
||||||
- ⚙️ **可视化配置管理** - Web界面配置系统设置,支持实时加载和保存配置,必填项验证
|
- ⚙️ **可视化配置管理** - Web界面配置系统设置,支持实时加载和保存配置,必填项验证
|
||||||
|
- 📄 **大结果分段读取** - 当工具执行结果超过阈值时自动保存,支持分页查询、关键词搜索、条件过滤和正则表达式匹配,有效解决单次返回过长问题,提供多种工具(head、tail、grep、sed等)的分段读取示例
|
||||||
|
|
||||||
### 工具集成
|
### 工具集成
|
||||||
- 🔌 **MCP协议支持** - 完整实现MCP协议,支持工具注册、调用、监控
|
- 🔌 **MCP协议支持** - 完整实现MCP协议,支持工具注册、调用、监控
|
||||||
|
|||||||
+1
-1
@@ -27,7 +27,7 @@ mcp:
|
|||||||
# 必填项:api_key, base_url, model 必须填写才能正常运行
|
# 必填项:api_key, base_url, model 必须填写才能正常运行
|
||||||
openai:
|
openai:
|
||||||
base_url: https://api.deepseek.com/v1 # API 基础 URL(必填)
|
base_url: https://api.deepseek.com/v1 # API 基础 URL(必填)
|
||||||
api_key: sk-xxx # API 密钥(必填)
|
api_key: sk-xxxx # API 密钥(必填)
|
||||||
# 支持的 API 服务商:
|
# 支持的 API 服务商:
|
||||||
# - OpenAI: https://api.openai.com/v1
|
# - OpenAI: https://api.openai.com/v1
|
||||||
# - DeepSeek: https://api.deepseek.com/v1
|
# - DeepSeek: https://api.deepseek.com/v1
|
||||||
|
|||||||
+108
-70
@@ -15,22 +15,23 @@ import (
|
|||||||
"cyberstrike-ai/internal/config"
|
"cyberstrike-ai/internal/config"
|
||||||
"cyberstrike-ai/internal/mcp"
|
"cyberstrike-ai/internal/mcp"
|
||||||
"cyberstrike-ai/internal/storage"
|
"cyberstrike-ai/internal/storage"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Agent AI代理
|
// Agent AI代理
|
||||||
type Agent struct {
|
type Agent struct {
|
||||||
openAIClient *http.Client
|
openAIClient *http.Client
|
||||||
config *config.OpenAIConfig
|
config *config.OpenAIConfig
|
||||||
agentConfig *config.AgentConfig
|
agentConfig *config.AgentConfig
|
||||||
mcpServer *mcp.Server
|
mcpServer *mcp.Server
|
||||||
externalMCPMgr *mcp.ExternalMCPManager // 外部MCP管理器
|
externalMCPMgr *mcp.ExternalMCPManager // 外部MCP管理器
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
maxIterations int
|
maxIterations int
|
||||||
resultStorage ResultStorage // 结果存储
|
resultStorage ResultStorage // 结果存储
|
||||||
largeResultThreshold int // 大结果阈值(字节)
|
largeResultThreshold int // 大结果阈值(字节)
|
||||||
mu sync.RWMutex // 添加互斥锁以支持并发更新
|
mu sync.RWMutex // 添加互斥锁以支持并发更新
|
||||||
toolNameMapping map[string]string // 工具名称映射:OpenAI格式 -> 原始格式(用于外部MCP工具)
|
toolNameMapping map[string]string // 工具名称映射:OpenAI格式 -> 原始格式(用于外部MCP工具)
|
||||||
}
|
}
|
||||||
|
|
||||||
// ResultStorage 结果存储接口(直接使用 storage 包的类型)
|
// ResultStorage 结果存储接口(直接使用 storage 包的类型)
|
||||||
@@ -38,9 +39,10 @@ type ResultStorage interface {
|
|||||||
SaveResult(executionID string, toolName string, result string) error
|
SaveResult(executionID string, toolName string, result string) error
|
||||||
GetResult(executionID string) (string, error)
|
GetResult(executionID string) (string, error)
|
||||||
GetResultPage(executionID string, page int, limit int) (*storage.ResultPage, error)
|
GetResultPage(executionID string, page int, limit int) (*storage.ResultPage, error)
|
||||||
SearchResult(executionID string, keyword string) ([]string, error)
|
SearchResult(executionID string, keyword string, useRegex bool) ([]string, error)
|
||||||
FilterResult(executionID string, filter string) ([]string, error)
|
FilterResult(executionID string, filter string, useRegex bool) ([]string, error)
|
||||||
GetResultMetadata(executionID string) (*storage.ResultMetadata, error)
|
GetResultMetadata(executionID string) (*storage.ResultMetadata, error)
|
||||||
|
GetResultPath(executionID string) string
|
||||||
DeleteResult(executionID string) error
|
DeleteResult(executionID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,12 +79,12 @@ func NewAgent(cfg *config.OpenAIConfig, agentCfg *config.AgentConfig, mcpServer
|
|||||||
Timeout: 300 * time.Second,
|
Timeout: 300 * time.Second,
|
||||||
KeepAlive: 300 * time.Second,
|
KeepAlive: 300 * time.Second,
|
||||||
}).DialContext,
|
}).DialContext,
|
||||||
MaxIdleConns: 100,
|
MaxIdleConns: 100,
|
||||||
MaxIdleConnsPerHost: 10,
|
MaxIdleConnsPerHost: 10,
|
||||||
IdleConnTimeout: 90 * time.Second,
|
IdleConnTimeout: 90 * time.Second,
|
||||||
TLSHandshakeTimeout: 30 * time.Second,
|
TLSHandshakeTimeout: 30 * time.Second,
|
||||||
ResponseHeaderTimeout: 60 * time.Minute, // 响应头超时:增加到15分钟,应对大响应
|
ResponseHeaderTimeout: 60 * time.Minute, // 响应头超时:增加到15分钟,应对大响应
|
||||||
DisableKeepAlives: false, // 启用连接复用
|
DisableKeepAlives: false, // 启用连接复用
|
||||||
}
|
}
|
||||||
|
|
||||||
// 增加超时时间到30分钟,以支持长时间运行的AI推理
|
// 增加超时时间到30分钟,以支持长时间运行的AI推理
|
||||||
@@ -92,15 +94,15 @@ func NewAgent(cfg *config.OpenAIConfig, agentCfg *config.AgentConfig, mcpServer
|
|||||||
Timeout: 30 * time.Minute, // 从5分钟增加到30分钟
|
Timeout: 30 * time.Minute, // 从5分钟增加到30分钟
|
||||||
Transport: transport,
|
Transport: transport,
|
||||||
},
|
},
|
||||||
config: cfg,
|
config: cfg,
|
||||||
agentConfig: agentCfg,
|
agentConfig: agentCfg,
|
||||||
mcpServer: mcpServer,
|
mcpServer: mcpServer,
|
||||||
externalMCPMgr: externalMCPMgr,
|
externalMCPMgr: externalMCPMgr,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
maxIterations: maxIterations,
|
maxIterations: maxIterations,
|
||||||
resultStorage: resultStorage,
|
resultStorage: resultStorage,
|
||||||
largeResultThreshold: largeResultThreshold,
|
largeResultThreshold: largeResultThreshold,
|
||||||
toolNameMapping: make(map[string]string), // 初始化工具名称映射
|
toolNameMapping: make(map[string]string), // 初始化工具名称映射
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,10 +115,10 @@ func (a *Agent) SetResultStorage(storage ResultStorage) {
|
|||||||
|
|
||||||
// ChatMessage 聊天消息
|
// ChatMessage 聊天消息
|
||||||
type ChatMessage struct {
|
type ChatMessage struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content,omitempty"`
|
Content string `json:"content,omitempty"`
|
||||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||||
ToolCallID string `json:"tool_call_id,omitempty"`
|
ToolCallID string `json:"tool_call_id,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// MarshalJSON 自定义JSON序列化,将tool_calls中的arguments转换为JSON字符串
|
// MarshalJSON 自定义JSON序列化,将tool_calls中的arguments转换为JSON字符串
|
||||||
@@ -187,15 +189,15 @@ type Choice struct {
|
|||||||
|
|
||||||
// MessageWithTools 带工具调用的消息
|
// MessageWithTools 带工具调用的消息
|
||||||
type MessageWithTools struct {
|
type MessageWithTools struct {
|
||||||
Role string `json:"role"`
|
Role string `json:"role"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
ToolCalls []ToolCall `json:"tool_calls,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tool OpenAI工具定义
|
// Tool OpenAI工具定义
|
||||||
type Tool struct {
|
type Tool struct {
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Function FunctionDefinition `json:"function"`
|
Function FunctionDefinition `json:"function"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionDefinition 函数定义
|
// FunctionDefinition 函数定义
|
||||||
@@ -213,9 +215,9 @@ type Error struct {
|
|||||||
|
|
||||||
// ToolCall 工具调用
|
// ToolCall 工具调用
|
||||||
type ToolCall struct {
|
type ToolCall struct {
|
||||||
ID string `json:"id"`
|
ID string `json:"id"`
|
||||||
Type string `json:"type"`
|
Type string `json:"type"`
|
||||||
Function FunctionCall `json:"function"`
|
Function FunctionCall `json:"function"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// FunctionCall 函数调用
|
// FunctionCall 函数调用
|
||||||
@@ -267,7 +269,7 @@ func (fc *FunctionCall) UnmarshalJSON(data []byte) error {
|
|||||||
|
|
||||||
// AgentLoopResult Agent Loop执行结果
|
// AgentLoopResult Agent Loop执行结果
|
||||||
type AgentLoopResult struct {
|
type AgentLoopResult struct {
|
||||||
Response string
|
Response string
|
||||||
MCPExecutionIDs []string
|
MCPExecutionIDs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -451,13 +453,13 @@ func (a *Agent) AgentLoopWithProgress(ctx context.Context, userInput string, his
|
|||||||
// 发送工具调用开始事件
|
// 发送工具调用开始事件
|
||||||
toolArgsJSON, _ := json.Marshal(toolCall.Function.Arguments)
|
toolArgsJSON, _ := json.Marshal(toolCall.Function.Arguments)
|
||||||
sendProgress("tool_call", fmt.Sprintf("正在调用工具: %s", toolCall.Function.Name), map[string]interface{}{
|
sendProgress("tool_call", fmt.Sprintf("正在调用工具: %s", toolCall.Function.Name), map[string]interface{}{
|
||||||
"toolName": toolCall.Function.Name,
|
"toolName": toolCall.Function.Name,
|
||||||
"arguments": string(toolArgsJSON),
|
"arguments": string(toolArgsJSON),
|
||||||
"argumentsObj": toolCall.Function.Arguments,
|
"argumentsObj": toolCall.Function.Arguments,
|
||||||
"toolCallId": toolCall.ID,
|
"toolCallId": toolCall.ID,
|
||||||
"index": idx + 1,
|
"index": idx + 1,
|
||||||
"total": len(choice.Message.ToolCalls),
|
"total": len(choice.Message.ToolCalls),
|
||||||
"iteration": i + 1,
|
"iteration": i + 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 执行工具
|
// 执行工具
|
||||||
@@ -466,21 +468,21 @@ func (a *Agent) AgentLoopWithProgress(ctx context.Context, userInput string, his
|
|||||||
// 构建详细的错误信息,帮助AI理解问题并做出决策
|
// 构建详细的错误信息,帮助AI理解问题并做出决策
|
||||||
errorMsg := a.formatToolError(toolCall.Function.Name, toolCall.Function.Arguments, err)
|
errorMsg := a.formatToolError(toolCall.Function.Name, toolCall.Function.Arguments, err)
|
||||||
messages = append(messages, ChatMessage{
|
messages = append(messages, ChatMessage{
|
||||||
Role: "tool",
|
Role: "tool",
|
||||||
ToolCallID: toolCall.ID,
|
ToolCallID: toolCall.ID,
|
||||||
Content: errorMsg,
|
Content: errorMsg,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 发送工具执行失败事件
|
// 发送工具执行失败事件
|
||||||
sendProgress("tool_result", fmt.Sprintf("工具 %s 执行失败", toolCall.Function.Name), map[string]interface{}{
|
sendProgress("tool_result", fmt.Sprintf("工具 %s 执行失败", toolCall.Function.Name), map[string]interface{}{
|
||||||
"toolName": toolCall.Function.Name,
|
"toolName": toolCall.Function.Name,
|
||||||
"success": false,
|
"success": false,
|
||||||
"isError": true,
|
"isError": true,
|
||||||
"error": err.Error(),
|
"error": err.Error(),
|
||||||
"toolCallId": toolCall.ID,
|
"toolCallId": toolCall.ID,
|
||||||
"index": idx + 1,
|
"index": idx + 1,
|
||||||
"total": len(choice.Message.ToolCalls),
|
"total": len(choice.Message.ToolCalls),
|
||||||
"iteration": i + 1,
|
"iteration": i + 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
a.logger.Warn("工具执行失败,已返回详细错误信息",
|
a.logger.Warn("工具执行失败,已返回详细错误信息",
|
||||||
@@ -490,9 +492,9 @@ func (a *Agent) AgentLoopWithProgress(ctx context.Context, userInput string, his
|
|||||||
} else {
|
} else {
|
||||||
// 即使工具返回了错误结果(IsError=true),也继续处理,让AI决定下一步
|
// 即使工具返回了错误结果(IsError=true),也继续处理,让AI决定下一步
|
||||||
messages = append(messages, ChatMessage{
|
messages = append(messages, ChatMessage{
|
||||||
Role: "tool",
|
Role: "tool",
|
||||||
ToolCallID: toolCall.ID,
|
ToolCallID: toolCall.ID,
|
||||||
Content: execResult.Result,
|
Content: execResult.Result,
|
||||||
})
|
})
|
||||||
// 收集执行ID
|
// 收集执行ID
|
||||||
if execResult.ExecutionID != "" {
|
if execResult.ExecutionID != "" {
|
||||||
@@ -505,16 +507,16 @@ func (a *Agent) AgentLoopWithProgress(ctx context.Context, userInput string, his
|
|||||||
resultPreview = resultPreview[:200] + "..."
|
resultPreview = resultPreview[:200] + "..."
|
||||||
}
|
}
|
||||||
sendProgress("tool_result", fmt.Sprintf("工具 %s 执行完成", toolCall.Function.Name), map[string]interface{}{
|
sendProgress("tool_result", fmt.Sprintf("工具 %s 执行完成", toolCall.Function.Name), map[string]interface{}{
|
||||||
"toolName": toolCall.Function.Name,
|
"toolName": toolCall.Function.Name,
|
||||||
"success": !execResult.IsError,
|
"success": !execResult.IsError,
|
||||||
"isError": execResult.IsError,
|
"isError": execResult.IsError,
|
||||||
"result": execResult.Result, // 完整结果
|
"result": execResult.Result, // 完整结果
|
||||||
"resultPreview": resultPreview, // 预览结果
|
"resultPreview": resultPreview, // 预览结果
|
||||||
"executionId": execResult.ExecutionID,
|
"executionId": execResult.ExecutionID,
|
||||||
"toolCallId": toolCall.ID,
|
"toolCallId": toolCall.ID,
|
||||||
"index": idx + 1,
|
"index": idx + 1,
|
||||||
"total": len(choice.Message.ToolCalls),
|
"total": len(choice.Message.ToolCalls),
|
||||||
"iteration": i + 1,
|
"iteration": i + 1,
|
||||||
})
|
})
|
||||||
|
|
||||||
// 如果工具返回了错误,记录日志但不中断流程
|
// 如果工具返回了错误,记录日志但不中断流程
|
||||||
@@ -1112,7 +1114,11 @@ func (a *Agent) executeToolViaMCP(ctx context.Context, toolName string, args map
|
|||||||
|
|
||||||
// 返回最小化通知
|
// 返回最小化通知
|
||||||
lines := strings.Split(resultStr, "\n")
|
lines := strings.Split(resultStr, "\n")
|
||||||
notification := a.formatMinimalNotification(executionID, toolName, resultSize, len(lines))
|
filePath := ""
|
||||||
|
if storage != nil {
|
||||||
|
filePath = storage.GetResultPath(executionID)
|
||||||
|
}
|
||||||
|
notification := a.formatMinimalNotification(executionID, toolName, resultSize, len(lines), filePath)
|
||||||
|
|
||||||
return &ToolExecutionResult{
|
return &ToolExecutionResult{
|
||||||
Result: notification,
|
Result: notification,
|
||||||
@@ -1129,7 +1135,7 @@ func (a *Agent) executeToolViaMCP(ctx context.Context, toolName string, args map
|
|||||||
}
|
}
|
||||||
|
|
||||||
// formatMinimalNotification 格式化最小化通知
|
// formatMinimalNotification 格式化最小化通知
|
||||||
func (a *Agent) formatMinimalNotification(executionID string, toolName string, size int, lineCount int) string {
|
func (a *Agent) formatMinimalNotification(executionID string, toolName string, size int, lineCount int, filePath string) string {
|
||||||
var sb strings.Builder
|
var sb strings.Builder
|
||||||
|
|
||||||
sb.WriteString(fmt.Sprintf("工具执行完成。结果已保存(ID: %s)。\n\n", executionID))
|
sb.WriteString(fmt.Sprintf("工具执行完成。结果已保存(ID: %s)。\n\n", executionID))
|
||||||
@@ -1137,11 +1143,44 @@ func (a *Agent) formatMinimalNotification(executionID string, toolName string, s
|
|||||||
sb.WriteString(fmt.Sprintf(" - 工具: %s\n", toolName))
|
sb.WriteString(fmt.Sprintf(" - 工具: %s\n", toolName))
|
||||||
sb.WriteString(fmt.Sprintf(" - 大小: %d 字节 (%.2f KB)\n", size, float64(size)/1024))
|
sb.WriteString(fmt.Sprintf(" - 大小: %d 字节 (%.2f KB)\n", size, float64(size)/1024))
|
||||||
sb.WriteString(fmt.Sprintf(" - 行数: %d 行\n", lineCount))
|
sb.WriteString(fmt.Sprintf(" - 行数: %d 行\n", lineCount))
|
||||||
|
if filePath != "" {
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 文件路径: %s\n", filePath))
|
||||||
|
}
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
sb.WriteString("使用以下工具查询完整结果:\n")
|
sb.WriteString("推荐使用 query_execution_result 工具查询完整结果:\n")
|
||||||
sb.WriteString(fmt.Sprintf(" - 查询第一页: query_execution_result(execution_id=\"%s\", page=1, limit=100)\n", executionID))
|
sb.WriteString(fmt.Sprintf(" - 查询第一页: query_execution_result(execution_id=\"%s\", page=1, limit=100)\n", executionID))
|
||||||
sb.WriteString(fmt.Sprintf(" - 搜索关键词: query_execution_result(execution_id=\"%s\", search=\"关键词\")\n", executionID))
|
sb.WriteString(fmt.Sprintf(" - 搜索关键词: query_execution_result(execution_id=\"%s\", search=\"关键词\")\n", executionID))
|
||||||
sb.WriteString(fmt.Sprintf(" - 过滤条件: query_execution_result(execution_id=\"%s\", filter=\"error\")\n", executionID))
|
sb.WriteString(fmt.Sprintf(" - 过滤条件: query_execution_result(execution_id=\"%s\", filter=\"error\")\n", executionID))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 正则匹配: query_execution_result(execution_id=\"%s\", search=\"\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+\", use_regex=true)\n", executionID))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
if filePath != "" {
|
||||||
|
sb.WriteString("如果 query_execution_result 工具不满足需求,也可以使用其他工具处理文件:\n")
|
||||||
|
sb.WriteString("\n")
|
||||||
|
sb.WriteString("**分段读取示例:**\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 查看前100行: exec(command=\"head\", args=[\"-n\", \"100\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 查看后100行: exec(command=\"tail\", args=[\"-n\", \"100\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 查看第50-150行: exec(command=\"sed\", args=[\"-n\", \"50,150p\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
sb.WriteString("**搜索和正则匹配示例:**\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 搜索关键词: exec(command=\"grep\", args=[\"关键词\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 正则匹配IP地址: exec(command=\"grep\", args=[\"-E\", \"\\\\d+\\\\.\\\\d+\\\\.\\\\d+\\\\.\\\\d+\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 不区分大小写搜索: exec(command=\"grep\", args=[\"-i\", \"关键词\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 显示匹配行号: exec(command=\"grep\", args=[\"-n\", \"关键词\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
sb.WriteString("**过滤和统计示例:**\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 统计总行数: exec(command=\"wc\", args=[\"-l\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 过滤包含error的行: exec(command=\"grep\", args=[\"error\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 排除空行: exec(command=\"grep\", args=[\"-v\", \"^$\", \"%s\"])\n", filePath))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
sb.WriteString("**完整读取(不推荐大文件):**\n")
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 使用 cat 工具: cat(file=\"%s\")\n", filePath))
|
||||||
|
sb.WriteString(fmt.Sprintf(" - 使用 exec 工具: exec(command=\"cat\", args=[\"%s\"])\n", filePath))
|
||||||
|
sb.WriteString("\n")
|
||||||
|
sb.WriteString("**注意:**\n")
|
||||||
|
sb.WriteString(" - 直接读取大文件可能会再次触发大结果保存机制\n")
|
||||||
|
sb.WriteString(" - 建议优先使用分段读取和搜索功能,避免一次性加载整个文件\n")
|
||||||
|
sb.WriteString(" - 正则表达式语法遵循标准 POSIX 正则表达式规范\n")
|
||||||
|
}
|
||||||
|
|
||||||
return sb.String()
|
return sb.String()
|
||||||
}
|
}
|
||||||
@@ -1183,4 +1222,3 @@ func (a *Agent) formatToolError(toolName string, args map[string]interface{}, er
|
|||||||
|
|
||||||
return errorMsg
|
return errorMsg
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -53,8 +53,9 @@ func TestAgent_FormatMinimalNotification(t *testing.T) {
|
|||||||
toolName := "nmap_scan"
|
toolName := "nmap_scan"
|
||||||
size := 50000
|
size := 50000
|
||||||
lineCount := 1000
|
lineCount := 1000
|
||||||
|
filePath := "tmp/test_exec_001.txt"
|
||||||
|
|
||||||
notification := agent.formatMinimalNotification(executionID, toolName, size, lineCount)
|
notification := agent.formatMinimalNotification(executionID, toolName, size, lineCount, filePath)
|
||||||
|
|
||||||
// 验证通知包含必要信息
|
// 验证通知包含必要信息
|
||||||
if !strings.Contains(notification, executionID) {
|
if !strings.Contains(notification, executionID) {
|
||||||
@@ -130,7 +131,8 @@ func TestAgent_ExecuteToolViaMCP_LargeResult(t *testing.T) {
|
|||||||
|
|
||||||
// 生成通知
|
// 生成通知
|
||||||
lines := strings.Split(resultStr, "\n")
|
lines := strings.Split(resultStr, "\n")
|
||||||
notification := agent.formatMinimalNotification(executionID, toolName, resultSize, len(lines))
|
filePath := storage.GetResultPath(executionID)
|
||||||
|
notification := agent.formatMinimalNotification(executionID, toolName, resultSize, len(lines), filePath)
|
||||||
|
|
||||||
// 验证通知格式
|
// 验证通知格式
|
||||||
if !strings.Contains(notification, executionID) {
|
if !strings.Contains(notification, executionID) {
|
||||||
|
|||||||
@@ -28,9 +28,10 @@ type ResultStorage interface {
|
|||||||
SaveResult(executionID string, toolName string, result string) error
|
SaveResult(executionID string, toolName string, result string) error
|
||||||
GetResult(executionID string) (string, error)
|
GetResult(executionID string) (string, error)
|
||||||
GetResultPage(executionID string, page int, limit int) (*storage.ResultPage, error)
|
GetResultPage(executionID string, page int, limit int) (*storage.ResultPage, error)
|
||||||
SearchResult(executionID string, keyword string) ([]string, error)
|
SearchResult(executionID string, keyword string, useRegex bool) ([]string, error)
|
||||||
FilterResult(executionID string, filter string) ([]string, error)
|
FilterResult(executionID string, filter string, useRegex bool) ([]string, error)
|
||||||
GetResultMetadata(executionID string) (*storage.ResultMetadata, error)
|
GetResultMetadata(executionID string) (*storage.ResultMetadata, error)
|
||||||
|
GetResultPath(executionID string) string
|
||||||
DeleteResult(executionID string) error
|
DeleteResult(executionID string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -755,6 +756,11 @@ func (e *Executor) executeQueryExecutionResult(ctx context.Context, args map[str
|
|||||||
filter = f
|
filter = f
|
||||||
}
|
}
|
||||||
|
|
||||||
|
useRegex := false
|
||||||
|
if r, ok := args["use_regex"].(bool); ok {
|
||||||
|
useRegex = r
|
||||||
|
}
|
||||||
|
|
||||||
// 检查结果存储是否可用
|
// 检查结果存储是否可用
|
||||||
if e.resultStorage == nil {
|
if e.resultStorage == nil {
|
||||||
return &mcp.ToolResult{
|
return &mcp.ToolResult{
|
||||||
@@ -774,7 +780,7 @@ func (e *Executor) executeQueryExecutionResult(ctx context.Context, args map[str
|
|||||||
|
|
||||||
if search != "" {
|
if search != "" {
|
||||||
// 搜索模式
|
// 搜索模式
|
||||||
matchedLines, err := e.resultStorage.SearchResult(executionID, search)
|
matchedLines, err := e.resultStorage.SearchResult(executionID, search, useRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &mcp.ToolResult{
|
return &mcp.ToolResult{
|
||||||
Content: []mcp.Content{
|
Content: []mcp.Content{
|
||||||
@@ -790,7 +796,7 @@ func (e *Executor) executeQueryExecutionResult(ctx context.Context, args map[str
|
|||||||
resultPage = paginateLines(matchedLines, page, limit)
|
resultPage = paginateLines(matchedLines, page, limit)
|
||||||
} else if filter != "" {
|
} else if filter != "" {
|
||||||
// 过滤模式
|
// 过滤模式
|
||||||
filteredLines, err := e.resultStorage.FilterResult(executionID, filter)
|
filteredLines, err := e.resultStorage.FilterResult(executionID, filter, useRegex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &mcp.ToolResult{
|
return &mcp.ToolResult{
|
||||||
Content: []mcp.Content{
|
Content: []mcp.Content{
|
||||||
@@ -853,9 +859,15 @@ func (e *Executor) executeQueryExecutionResult(ctx context.Context, args map[str
|
|||||||
sb.WriteString(fmt.Sprintf("提示: 使用 page=%d 查看下一页", resultPage.Page+1))
|
sb.WriteString(fmt.Sprintf("提示: 使用 page=%d 查看下一页", resultPage.Page+1))
|
||||||
if search != "" {
|
if search != "" {
|
||||||
sb.WriteString(fmt.Sprintf(",或使用 search=\"%s\" 继续搜索", search))
|
sb.WriteString(fmt.Sprintf(",或使用 search=\"%s\" 继续搜索", search))
|
||||||
|
if useRegex {
|
||||||
|
sb.WriteString(" (正则模式)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if filter != "" {
|
if filter != "" {
|
||||||
sb.WriteString(fmt.Sprintf(",或使用 filter=\"%s\" 继续过滤", filter))
|
sb.WriteString(fmt.Sprintf(",或使用 filter=\"%s\" 继续过滤", filter))
|
||||||
|
if useRegex {
|
||||||
|
sb.WriteString(" (正则模式)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
sb.WriteString("\n")
|
sb.WriteString("\n")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -24,14 +25,19 @@ type ResultStorage interface {
|
|||||||
GetResultPage(executionID string, page int, limit int) (*ResultPage, error)
|
GetResultPage(executionID string, page int, limit int) (*ResultPage, error)
|
||||||
|
|
||||||
// SearchResult 搜索结果
|
// SearchResult 搜索结果
|
||||||
SearchResult(executionID string, keyword string) ([]string, error)
|
// useRegex: 如果为 true,将 keyword 作为正则表达式使用;如果为 false,使用简单的字符串包含匹配
|
||||||
|
SearchResult(executionID string, keyword string, useRegex bool) ([]string, error)
|
||||||
|
|
||||||
// FilterResult 过滤结果
|
// FilterResult 过滤结果
|
||||||
FilterResult(executionID string, filter string) ([]string, error)
|
// useRegex: 如果为 true,将 filter 作为正则表达式使用;如果为 false,使用简单的字符串包含匹配
|
||||||
|
FilterResult(executionID string, filter string, useRegex bool) ([]string, error)
|
||||||
|
|
||||||
// GetResultMetadata 获取结果元信息
|
// GetResultMetadata 获取结果元信息
|
||||||
GetResultMetadata(executionID string) (*ResultMetadata, error)
|
GetResultMetadata(executionID string) (*ResultMetadata, error)
|
||||||
|
|
||||||
|
// GetResultPath 获取结果文件路径
|
||||||
|
GetResultPath(executionID string) string
|
||||||
|
|
||||||
// DeleteResult 删除结果
|
// DeleteResult 删除结果
|
||||||
DeleteResult(executionID string) error
|
DeleteResult(executionID string) error
|
||||||
}
|
}
|
||||||
@@ -214,7 +220,7 @@ func (s *FileResultStorage) GetResultPage(executionID string, page int, limit in
|
|||||||
}
|
}
|
||||||
|
|
||||||
// SearchResult 搜索结果
|
// SearchResult 搜索结果
|
||||||
func (s *FileResultStorage) SearchResult(executionID string, keyword string) ([]string, error) {
|
func (s *FileResultStorage) SearchResult(executionID string, keyword string, useRegex bool) ([]string, error) {
|
||||||
s.mu.RLock()
|
s.mu.RLock()
|
||||||
defer s.mu.RUnlock()
|
defer s.mu.RUnlock()
|
||||||
|
|
||||||
@@ -224,12 +230,29 @@ func (s *FileResultStorage) SearchResult(executionID string, keyword string) ([]
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 如果使用正则表达式,先编译正则
|
||||||
|
var regex *regexp.Regexp
|
||||||
|
if useRegex {
|
||||||
|
compiledRegex, err := regexp.Compile(keyword)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("无效的正则表达式: %w", err)
|
||||||
|
}
|
||||||
|
regex = compiledRegex
|
||||||
|
}
|
||||||
|
|
||||||
// 分割为行并搜索
|
// 分割为行并搜索
|
||||||
lines := strings.Split(result, "\n")
|
lines := strings.Split(result, "\n")
|
||||||
var matchedLines []string
|
var matchedLines []string
|
||||||
|
|
||||||
for _, line := range lines {
|
for _, line := range lines {
|
||||||
if strings.Contains(line, keyword) {
|
var matched bool
|
||||||
|
if useRegex {
|
||||||
|
matched = regex.MatchString(line)
|
||||||
|
} else {
|
||||||
|
matched = strings.Contains(line, keyword)
|
||||||
|
}
|
||||||
|
|
||||||
|
if matched {
|
||||||
matchedLines = append(matchedLines, line)
|
matchedLines = append(matchedLines, line)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -238,9 +261,14 @@ func (s *FileResultStorage) SearchResult(executionID string, keyword string) ([]
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FilterResult 过滤结果
|
// FilterResult 过滤结果
|
||||||
func (s *FileResultStorage) FilterResult(executionID string, filter string) ([]string, error) {
|
func (s *FileResultStorage) FilterResult(executionID string, filter string, useRegex bool) ([]string, error) {
|
||||||
// 过滤和搜索逻辑相同,都是查找包含关键词的行
|
// 过滤和搜索逻辑相同,都是查找包含关键词的行
|
||||||
return s.SearchResult(executionID, filter)
|
return s.SearchResult(executionID, filter, useRegex)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetResultPath 获取结果文件路径
|
||||||
|
func (s *FileResultStorage) GetResultPath(executionID string) string {
|
||||||
|
return s.getResultPath(executionID)
|
||||||
}
|
}
|
||||||
|
|
||||||
// DeleteResult 删除结果
|
// DeleteResult 删除结果
|
||||||
@@ -267,4 +295,3 @@ func (s *FileResultStorage) DeleteResult(executionID string) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -256,8 +256,8 @@ func TestFileResultStorage_SearchResult(t *testing.T) {
|
|||||||
t.Fatalf("保存结果失败: %v", err)
|
t.Fatalf("保存结果失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 搜索包含"error"的行
|
// 搜索包含"error"的行(简单字符串匹配)
|
||||||
matchedLines, err := storage.SearchResult(executionID, "error")
|
matchedLines, err := storage.SearchResult(executionID, "error", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("搜索失败: %v", err)
|
t.Fatalf("搜索失败: %v", err)
|
||||||
}
|
}
|
||||||
@@ -274,7 +274,7 @@ func TestFileResultStorage_SearchResult(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 测试搜索不存在的关键词
|
// 测试搜索不存在的关键词
|
||||||
noMatch, err := storage.SearchResult(executionID, "nonexistent")
|
noMatch, err := storage.SearchResult(executionID, "nonexistent", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("搜索失败: %v", err)
|
t.Fatalf("搜索失败: %v", err)
|
||||||
}
|
}
|
||||||
@@ -282,6 +282,16 @@ func TestFileResultStorage_SearchResult(t *testing.T) {
|
|||||||
if len(noMatch) != 0 {
|
if len(noMatch) != 0 {
|
||||||
t.Errorf("搜索不存在的关键词应该返回空结果。实际: %d行", len(noMatch))
|
t.Errorf("搜索不存在的关键词应该返回空结果。实际: %d行", len(noMatch))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 测试正则表达式搜索
|
||||||
|
regexMatched, err := storage.SearchResult(executionID, "error.*again", true)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("正则搜索失败: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(regexMatched) != 1 {
|
||||||
|
t.Errorf("正则搜索结果数量不匹配。期望: 1, 实际: %d", len(regexMatched))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFileResultStorage_FilterResult(t *testing.T) {
|
func TestFileResultStorage_FilterResult(t *testing.T) {
|
||||||
@@ -298,8 +308,8 @@ func TestFileResultStorage_FilterResult(t *testing.T) {
|
|||||||
t.Fatalf("保存结果失败: %v", err)
|
t.Fatalf("保存结果失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 过滤包含"warning"的行
|
// 过滤包含"warning"的行(简单字符串匹配)
|
||||||
filteredLines, err := storage.FilterResult(executionID, "warning")
|
filteredLines, err := storage.FilterResult(executionID, "warning", false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("过滤失败: %v", err)
|
t.Fatalf("过滤失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user