mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-05-17 21:44:43 +02:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2e482a3baf | |||
| 67d5e7f11e | |||
| 7e0198a64c | |||
| 1e50272229 | |||
| 39b47a86fb | |||
| 74738ee555 | |||
| 90bc3f4b61 | |||
| ad96be3c64 | |||
| 8866ff4cdd | |||
| 3534a956b2 |
+1
-1
@@ -10,7 +10,7 @@
|
|||||||
# ============================================
|
# ============================================
|
||||||
|
|
||||||
# 前端显示的版本号(可选,不填则显示默认版本)
|
# 前端显示的版本号(可选,不填则显示默认版本)
|
||||||
version: "v1.3.10"
|
version: "v1.3.13"
|
||||||
|
|
||||||
# 服务器配置
|
# 服务器配置
|
||||||
server:
|
server:
|
||||||
|
|||||||
@@ -113,6 +113,11 @@
|
|||||||
| **新对话** | 开启一个新对话,后续消息在新对话中 |
|
| **新对话** | 开启一个新对话,后续消息在新对话中 |
|
||||||
| **清空** | 清空当前对话上下文(效果等同「新对话」) |
|
| **清空** | 清空当前对话上下文(效果等同「新对话」) |
|
||||||
| **当前** | 显示当前对话 ID 与标题 |
|
| **当前** | 显示当前对话 ID 与标题 |
|
||||||
|
| **停止** | 中断当前正在执行的任务 |
|
||||||
|
| **角色** 或 **角色列表** | 列出所有可用角色(渗透测试、CTF、Web 应用扫描等) |
|
||||||
|
| **角色 \<角色名\>** 或 **切换角色 \<角色名\>** | 切换当前使用的角色 |
|
||||||
|
| **删除 \<对话ID\>** | 删除指定对话 |
|
||||||
|
| **版本** | 显示当前 CyberStrikeAI 版本号 |
|
||||||
|
|
||||||
除以上命令外,**直接输入任意文字**会作为用户消息发给 AI,与 Web 端对话逻辑一致(渗透测试/安全分析等)。
|
除以上命令外,**直接输入任意文字**会作为用户消息发给 AI,与 Web 端对话逻辑一致(渗透测试/安全分析等)。
|
||||||
|
|
||||||
@@ -184,6 +189,9 @@ curl -X POST "http://localhost:8080/api/robot/test" \
|
|||||||
|
|
||||||
按顺序检查:
|
按顺序检查:
|
||||||
|
|
||||||
|
0. **笔记本合盖睡眠 / 断网后**
|
||||||
|
钉钉、飞书均使用长连接收消息,睡眠或断网后连接会断开。程序会**自动重连**(约 5 秒~60 秒内重试)。唤醒或恢复网络后稍等一会儿再发消息;若仍无反应,可重启 CyberStrikeAI 进程。
|
||||||
|
|
||||||
1. **Client ID / Client Secret 是否与开放平台完全一致**
|
1. **Client ID / Client Secret 是否与开放平台完全一致**
|
||||||
从「凭证与基础信息」里**复制粘贴**,不要手打。注意数字 **0** 与字母 **o**、数字 **1** 与字母 **l**(例如 `ding9gf9tiozuc504aer` 中间是 **504** 不是 5o4)。
|
从「凭证与基础信息」里**复制粘贴**,不要手打。注意数字 **0** 与字母 **o**、数字 **1** 与字母 **l**(例如 `ding9gf9tiozuc504aer` 中间是 **504** 不是 5o4)。
|
||||||
|
|
||||||
|
|||||||
@@ -112,6 +112,11 @@ Send these **text commands** to the bot in DingTalk or Lark (text only):
|
|||||||
| **新对话** (new) | Start a new conversation |
|
| **新对话** (new) | Start a new conversation |
|
||||||
| **清空** (clear) | Clear current context (same effect as new conversation) |
|
| **清空** (clear) | Clear current context (same effect as new conversation) |
|
||||||
| **当前** (current) | Show current conversation ID and title |
|
| **当前** (current) | Show current conversation ID and title |
|
||||||
|
| **停止** (stop) | Abort the currently running task |
|
||||||
|
| **角色** or **角色列表** (roles) | List all available roles (penetration testing, CTF, Web scan, etc.) |
|
||||||
|
| **角色 \<roleName\>** or **切换角色 \<roleName\>** | Switch to the specified role |
|
||||||
|
| **删除 \<conversationID\>** | Delete the specified conversation |
|
||||||
|
| **版本** (version) | Show current CyberStrikeAI version |
|
||||||
|
|
||||||
Any other text is sent to the AI as a user message, same as in the web UI (e.g. penetration testing, security analysis).
|
Any other text is sent to the AI as a user message, same as in the web UI (e.g. penetration testing, security analysis).
|
||||||
|
|
||||||
@@ -183,6 +188,9 @@ API: `POST /api/robot/test` (requires login). Body: `{"platform":"optional","use
|
|||||||
|
|
||||||
Check in this order:
|
Check in this order:
|
||||||
|
|
||||||
|
0. **After laptop sleep or network drop**
|
||||||
|
DingTalk and Lark both use long-lived connections; they break when the machine sleeps or the network drops. The app **auto-reconnects** (retries within about 5–60 seconds). After wake or network recovery, wait a moment before sending; if there is still no response, restart the CyberStrikeAI process.
|
||||||
|
|
||||||
1. **Client ID / Client Secret match the open platform exactly**
|
1. **Client ID / Client Secret match the open platform exactly**
|
||||||
Copy from “Credentials and basic info”; avoid typing. Watch **0** vs **o** and **1** vs **l** (e.g. `ding9gf9tiozuc504aer` has **504**, not 5o4).
|
Copy from “Credentials and basic info”; avoid typing. Watch **0** vs **o** and **1** vs **l** (e.g. `ding9gf9tiozuc504aer` has **504**, not 5o4).
|
||||||
|
|
||||||
|
|||||||
@@ -48,3 +48,7 @@ require (
|
|||||||
golang.org/x/text v0.13.0 // indirect
|
golang.org/x/text v0.13.0 // indirect
|
||||||
google.golang.org/protobuf v1.30.0 // indirect
|
google.golang.org/protobuf v1.30.0 // indirect
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 修复钉钉 Stream SDK 在长连接断开(熄屏/网络中断)后 "panic: send on closed channel" 问题
|
||||||
|
// 详见: https://github.com/open-dingtalk/dingtalk-stream-sdk-go/issues/28
|
||||||
|
replace github.com/open-dingtalk/dingtalk-stream-sdk-go => github.com/uouuou/dingtalk-stream-sdk-go v0.0.0-20250626025113-079132acc406
|
||||||
|
|||||||
@@ -62,8 +62,6 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
|
|||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
|
||||||
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
|
||||||
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
|
||||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1 h1:Lb/Uzkiw2Ugt2Xf03J5wmv81PdkYOiWbI8CNBi1boC8=
|
|
||||||
github.com/open-dingtalk/dingtalk-stream-sdk-go v0.9.1/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU=
|
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
|
||||||
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
|
||||||
github.com/pkoukk/tiktoken-go v0.1.8 h1:85ENo+3FpWgAACBaEUVp+lctuTcYUO7BtmfhlN/QTRo=
|
github.com/pkoukk/tiktoken-go v0.1.8 h1:85ENo+3FpWgAACBaEUVp+lctuTcYUO7BtmfhlN/QTRo=
|
||||||
@@ -85,6 +83,8 @@ github.com/twitchyliquid64/golang-asm v0.15.1 h1:SU5vSMR7hnwNxj24w34ZyCi/FmDZTkS
|
|||||||
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2+aY1QWCk3Cedj/Gdt08=
|
||||||
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
github.com/ugorji/go/codec v1.2.11 h1:BMaWp1Bb6fHwEtbplGBGJ498wD+LKlNSl25MjdZY4dU=
|
||||||
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
github.com/ugorji/go/codec v1.2.11/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg=
|
||||||
|
github.com/uouuou/dingtalk-stream-sdk-go v0.0.0-20250626025113-079132acc406 h1:b72HNsEnmTRn7vhWGOfbWHAkA5RbRCk0Pbc56V2WAuY=
|
||||||
|
github.com/uouuou/dingtalk-stream-sdk-go v0.0.0-20250626025113-079132acc406/go.mod h1:ln3IqPYYocZbYvl9TAOrG/cxGR9xcn4pnZRLdCTEGEU=
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
|
||||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
|
||||||
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
|
||||||
|
|||||||
+238
-46
@@ -7,9 +7,11 @@ import (
|
|||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -22,34 +24,45 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
robotCmdHelp = "帮助"
|
robotCmdHelp = "帮助"
|
||||||
robotCmdList = "列表"
|
robotCmdList = "列表"
|
||||||
robotCmdListAlt = "对话列表"
|
robotCmdListAlt = "对话列表"
|
||||||
robotCmdSwitch = "切换"
|
robotCmdSwitch = "切换"
|
||||||
robotCmdContinue = "继续"
|
robotCmdContinue = "继续"
|
||||||
robotCmdNew = "新对话"
|
robotCmdNew = "新对话"
|
||||||
robotCmdClear = "清空"
|
robotCmdClear = "清空"
|
||||||
robotCmdCurrent = "当前"
|
robotCmdCurrent = "当前"
|
||||||
|
robotCmdStop = "停止"
|
||||||
|
robotCmdRoles = "角色"
|
||||||
|
robotCmdRolesList = "角色列表"
|
||||||
|
robotCmdSwitchRole = "切换角色"
|
||||||
|
robotCmdDelete = "删除"
|
||||||
|
robotCmdVersion = "版本"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RobotHandler 企业微信/钉钉/飞书等机器人回调处理
|
// RobotHandler 企业微信/钉钉/飞书等机器人回调处理
|
||||||
type RobotHandler struct {
|
type RobotHandler struct {
|
||||||
config *config.Config
|
config *config.Config
|
||||||
db *database.DB
|
db *database.DB
|
||||||
agentHandler *AgentHandler
|
agentHandler *AgentHandler
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
sessions map[string]string // key: "platform_userID", value: conversationID
|
sessions map[string]string // key: "platform_userID", value: conversationID
|
||||||
|
sessionRoles map[string]string // key: "platform_userID", value: roleName(默认"默认")
|
||||||
|
cancelMu sync.Mutex // 保护 runningCancels
|
||||||
|
runningCancels map[string]context.CancelFunc // key: "platform_userID", 用于停止命令中断任务
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewRobotHandler 创建机器人处理器
|
// NewRobotHandler 创建机器人处理器
|
||||||
func NewRobotHandler(cfg *config.Config, db *database.DB, agentHandler *AgentHandler, logger *zap.Logger) *RobotHandler {
|
func NewRobotHandler(cfg *config.Config, db *database.DB, agentHandler *AgentHandler, logger *zap.Logger) *RobotHandler {
|
||||||
return &RobotHandler{
|
return &RobotHandler{
|
||||||
config: cfg,
|
config: cfg,
|
||||||
db: db,
|
db: db,
|
||||||
agentHandler: agentHandler,
|
agentHandler: agentHandler,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
sessions: make(map[string]string),
|
sessions: make(map[string]string),
|
||||||
|
sessionRoles: make(map[string]string),
|
||||||
|
runningCancels: make(map[string]context.CancelFunc),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -58,15 +71,21 @@ func (h *RobotHandler) sessionKey(platform, userID string) string {
|
|||||||
return platform + "_" + userID
|
return platform + "_" + userID
|
||||||
}
|
}
|
||||||
|
|
||||||
// getOrCreateConversation 获取或创建当前会话
|
// getOrCreateConversation 获取或创建当前会话,title 用于新对话的标题(取用户首条消息前50字)
|
||||||
func (h *RobotHandler) getOrCreateConversation(platform, userID string) (convID string, isNew bool) {
|
func (h *RobotHandler) getOrCreateConversation(platform, userID, title string) (convID string, isNew bool) {
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
convID = h.sessions[h.sessionKey(platform, userID)]
|
convID = h.sessions[h.sessionKey(platform, userID)]
|
||||||
h.mu.RUnlock()
|
h.mu.RUnlock()
|
||||||
if convID != "" {
|
if convID != "" {
|
||||||
return convID, false
|
return convID, false
|
||||||
}
|
}
|
||||||
conv, err := h.db.CreateConversation("机器人对话")
|
t := strings.TrimSpace(title)
|
||||||
|
if t == "" {
|
||||||
|
t = "新对话 " + time.Now().Format("01-02 15:04")
|
||||||
|
} else {
|
||||||
|
t = safeTruncateString(t, 50)
|
||||||
|
}
|
||||||
|
conv, err := h.db.CreateConversation(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Warn("创建机器人会话失败", zap.Error(err))
|
h.logger.Warn("创建机器人会话失败", zap.Error(err))
|
||||||
return "", false
|
return "", false
|
||||||
@@ -85,9 +104,28 @@ func (h *RobotHandler) setConversation(platform, userID, convID string) {
|
|||||||
h.mu.Unlock()
|
h.mu.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// getRole 获取当前用户使用的角色,未设置时返回"默认"
|
||||||
|
func (h *RobotHandler) getRole(platform, userID string) string {
|
||||||
|
h.mu.RLock()
|
||||||
|
role := h.sessionRoles[h.sessionKey(platform, userID)]
|
||||||
|
h.mu.RUnlock()
|
||||||
|
if role == "" {
|
||||||
|
return "默认"
|
||||||
|
}
|
||||||
|
return role
|
||||||
|
}
|
||||||
|
|
||||||
|
// setRole 设置当前用户使用的角色
|
||||||
|
func (h *RobotHandler) setRole(platform, userID, roleName string) {
|
||||||
|
h.mu.Lock()
|
||||||
|
h.sessionRoles[h.sessionKey(platform, userID)] = roleName
|
||||||
|
h.mu.Unlock()
|
||||||
|
}
|
||||||
|
|
||||||
// clearConversation 清空当前会话(切换到新对话)
|
// clearConversation 清空当前会话(切换到新对话)
|
||||||
func (h *RobotHandler) clearConversation(platform, userID string) (newConvID string) {
|
func (h *RobotHandler) clearConversation(platform, userID string) (newConvID string) {
|
||||||
conv, err := h.db.CreateConversation("新对话")
|
title := "新对话 " + time.Now().Format("01-02 15:04")
|
||||||
|
conv, err := h.db.CreateConversation(title)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Warn("创建新对话失败", zap.Error(err))
|
h.logger.Warn("创建新对话失败", zap.Error(err))
|
||||||
return ""
|
return ""
|
||||||
@@ -100,41 +138,91 @@ func (h *RobotHandler) clearConversation(platform, userID string) (newConvID str
|
|||||||
func (h *RobotHandler) HandleMessage(platform, userID, text string) (reply string) {
|
func (h *RobotHandler) HandleMessage(platform, userID, text string) (reply string) {
|
||||||
text = strings.TrimSpace(text)
|
text = strings.TrimSpace(text)
|
||||||
if text == "" {
|
if text == "" {
|
||||||
return "请输入内容或发送「帮助」查看命令。"
|
return "请输入内容或发送「帮助」/ help 查看命令。"
|
||||||
}
|
}
|
||||||
|
|
||||||
// 命令分发
|
// 命令分发(支持中英文)
|
||||||
switch {
|
switch {
|
||||||
case text == robotCmdHelp || text == "help" || text == "?" || text == "?":
|
case text == robotCmdHelp || text == "help" || text == "?" || text == "?":
|
||||||
return h.cmdHelp()
|
return h.cmdHelp()
|
||||||
case text == robotCmdList || text == robotCmdListAlt:
|
case text == robotCmdList || text == robotCmdListAlt || text == "list":
|
||||||
return h.cmdList(userID)
|
return h.cmdList()
|
||||||
case strings.HasPrefix(text, robotCmdSwitch+" ") || strings.HasPrefix(text, robotCmdContinue+" "):
|
case strings.HasPrefix(text, robotCmdSwitch+" ") || strings.HasPrefix(text, robotCmdContinue+" ") || strings.HasPrefix(text, "switch ") || strings.HasPrefix(text, "continue "):
|
||||||
var id string
|
var id string
|
||||||
if strings.HasPrefix(text, robotCmdSwitch+" ") {
|
switch {
|
||||||
|
case strings.HasPrefix(text, robotCmdSwitch+" "):
|
||||||
id = strings.TrimSpace(text[len(robotCmdSwitch)+1:])
|
id = strings.TrimSpace(text[len(robotCmdSwitch)+1:])
|
||||||
} else {
|
case strings.HasPrefix(text, robotCmdContinue+" "):
|
||||||
id = strings.TrimSpace(text[len(robotCmdContinue)+1:])
|
id = strings.TrimSpace(text[len(robotCmdContinue)+1:])
|
||||||
|
case strings.HasPrefix(text, "switch "):
|
||||||
|
id = strings.TrimSpace(text[7:])
|
||||||
|
default:
|
||||||
|
id = strings.TrimSpace(text[9:])
|
||||||
}
|
}
|
||||||
return h.cmdSwitch(platform, userID, id)
|
return h.cmdSwitch(platform, userID, id)
|
||||||
case text == robotCmdNew:
|
case text == robotCmdNew || text == "new":
|
||||||
return h.cmdNew(platform, userID)
|
return h.cmdNew(platform, userID)
|
||||||
case text == robotCmdClear:
|
case text == robotCmdClear || text == "clear":
|
||||||
return h.cmdClear(platform, userID)
|
return h.cmdClear(platform, userID)
|
||||||
case text == robotCmdCurrent:
|
case text == robotCmdCurrent || text == "current":
|
||||||
return h.cmdCurrent(platform, userID)
|
return h.cmdCurrent(platform, userID)
|
||||||
|
case text == robotCmdStop || text == "stop":
|
||||||
|
return h.cmdStop(platform, userID)
|
||||||
|
case text == robotCmdRoles || text == robotCmdRolesList || text == "roles":
|
||||||
|
return h.cmdRoles()
|
||||||
|
case strings.HasPrefix(text, robotCmdRoles+" ") || strings.HasPrefix(text, robotCmdSwitchRole+" ") || strings.HasPrefix(text, "role "):
|
||||||
|
var roleName string
|
||||||
|
switch {
|
||||||
|
case strings.HasPrefix(text, robotCmdRoles+" "):
|
||||||
|
roleName = strings.TrimSpace(text[len(robotCmdRoles)+1:])
|
||||||
|
case strings.HasPrefix(text, robotCmdSwitchRole+" "):
|
||||||
|
roleName = strings.TrimSpace(text[len(robotCmdSwitchRole)+1:])
|
||||||
|
default:
|
||||||
|
roleName = strings.TrimSpace(text[5:])
|
||||||
|
}
|
||||||
|
return h.cmdSwitchRole(platform, userID, roleName)
|
||||||
|
case strings.HasPrefix(text, robotCmdDelete+" ") || strings.HasPrefix(text, "delete "):
|
||||||
|
var convID string
|
||||||
|
if strings.HasPrefix(text, robotCmdDelete+" ") {
|
||||||
|
convID = strings.TrimSpace(text[len(robotCmdDelete)+1:])
|
||||||
|
} else {
|
||||||
|
convID = strings.TrimSpace(text[7:])
|
||||||
|
}
|
||||||
|
return h.cmdDelete(platform, userID, convID)
|
||||||
|
case text == robotCmdVersion || text == "version":
|
||||||
|
return h.cmdVersion()
|
||||||
}
|
}
|
||||||
|
|
||||||
// 普通消息:走 Agent
|
// 普通消息:走 Agent
|
||||||
convID, _ := h.getOrCreateConversation(platform, userID)
|
convID, _ := h.getOrCreateConversation(platform, userID, text)
|
||||||
if convID == "" {
|
if convID == "" {
|
||||||
return "无法创建或获取对话,请稍后再试。"
|
return "无法创建或获取对话,请稍后再试。"
|
||||||
}
|
}
|
||||||
|
// 若对话标题为「新对话 xx:xx」格式(由「新对话」命令创建),将标题更新为首条消息内容,与 Web 端体验一致
|
||||||
|
if conv, err := h.db.GetConversation(convID); err == nil && strings.HasPrefix(conv.Title, "新对话 ") {
|
||||||
|
newTitle := safeTruncateString(text, 50)
|
||||||
|
if newTitle != "" {
|
||||||
|
_ = h.db.UpdateConversationTitle(convID, newTitle)
|
||||||
|
}
|
||||||
|
}
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Minute)
|
||||||
defer cancel()
|
sk := h.sessionKey(platform, userID)
|
||||||
resp, newConvID, err := h.agentHandler.ProcessMessageForRobot(ctx, convID, text, "默认")
|
h.cancelMu.Lock()
|
||||||
|
h.runningCancels[sk] = cancel
|
||||||
|
h.cancelMu.Unlock()
|
||||||
|
defer func() {
|
||||||
|
cancel()
|
||||||
|
h.cancelMu.Lock()
|
||||||
|
delete(h.runningCancels, sk)
|
||||||
|
h.cancelMu.Unlock()
|
||||||
|
}()
|
||||||
|
role := h.getRole(platform, userID)
|
||||||
|
resp, newConvID, err := h.agentHandler.ProcessMessageForRobot(ctx, convID, text, role)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
h.logger.Warn("机器人 Agent 执行失败", zap.String("platform", platform), zap.String("userID", userID), zap.Error(err))
|
h.logger.Warn("机器人 Agent 执行失败", zap.String("platform", platform), zap.String("userID", userID), zap.Error(err))
|
||||||
|
if errors.Is(err, context.Canceled) {
|
||||||
|
return "任务已取消。"
|
||||||
|
}
|
||||||
return "处理失败: " + err.Error()
|
return "处理失败: " + err.Error()
|
||||||
}
|
}
|
||||||
if newConvID != convID {
|
if newConvID != convID {
|
||||||
@@ -144,17 +232,24 @@ func (h *RobotHandler) HandleMessage(platform, userID, text string) (reply strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (h *RobotHandler) cmdHelp() string {
|
func (h *RobotHandler) cmdHelp() string {
|
||||||
return `【CyberStrikeAI 机器人命令】
|
return "**【CyberStrikeAI 机器人命令】**\n\n" +
|
||||||
· 帮助 — 显示本帮助
|
"- `帮助` `help` — 显示本帮助 | Show this help\n" +
|
||||||
· 列表 / 对话列表 — 列出所有对话标题与 ID
|
"- `列表` `list` — 列出所有对话标题与 ID | List conversations\n" +
|
||||||
· 切换 <对话ID> / 继续 <对话ID> — 指定对话继续
|
"- `切换 <ID>` `switch <ID>` — 指定对话继续 | Switch to conversation\n" +
|
||||||
· 新对话 — 开启新对话
|
"- `新对话` `new` — 开启新对话 | Start new conversation\n" +
|
||||||
· 清空 — 清空当前上下文(等同于新对话)
|
"- `清空` `clear` — 清空当前上下文 | Clear context\n" +
|
||||||
· 当前 — 显示当前对话 ID 与标题
|
"- `当前` `current` — 显示当前对话 ID 与标题 | Show current conversation\n" +
|
||||||
除以上命令外,直接输入内容将发送给 AI 进行渗透测试/安全分析。`
|
"- `停止` `stop` — 中断当前任务 | Stop running task\n" +
|
||||||
|
"- `角色` `roles` — 列出所有可用角色 | List roles\n" +
|
||||||
|
"- `角色 <名>` `role <name>` — 切换当前角色 | Switch role\n" +
|
||||||
|
"- `删除 <ID>` `delete <ID>` — 删除指定对话 | Delete conversation\n" +
|
||||||
|
"- `版本` `version` — 显示当前版本号 | Show version\n\n" +
|
||||||
|
"---\n" +
|
||||||
|
"除以上命令外,直接输入内容将发送给 AI 进行渗透测试/安全分析。\n" +
|
||||||
|
"Otherwise, send any text for AI penetration testing / security analysis."
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *RobotHandler) cmdList(userID string) string {
|
func (h *RobotHandler) cmdList() string {
|
||||||
convs, err := h.db.ListConversations(50, 0, "")
|
convs, err := h.db.ListConversations(50, 0, "")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "获取对话列表失败: " + err.Error()
|
return "获取对话列表失败: " + err.Error()
|
||||||
@@ -198,6 +293,21 @@ func (h *RobotHandler) cmdClear(platform, userID string) string {
|
|||||||
return h.cmdNew(platform, userID)
|
return h.cmdNew(platform, userID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (h *RobotHandler) cmdStop(platform, userID string) string {
|
||||||
|
sk := h.sessionKey(platform, userID)
|
||||||
|
h.cancelMu.Lock()
|
||||||
|
cancel, ok := h.runningCancels[sk]
|
||||||
|
if ok {
|
||||||
|
delete(h.runningCancels, sk)
|
||||||
|
cancel()
|
||||||
|
}
|
||||||
|
h.cancelMu.Unlock()
|
||||||
|
if !ok {
|
||||||
|
return "当前没有正在执行的任务。"
|
||||||
|
}
|
||||||
|
return "已停止当前任务。"
|
||||||
|
}
|
||||||
|
|
||||||
func (h *RobotHandler) cmdCurrent(platform, userID string) string {
|
func (h *RobotHandler) cmdCurrent(platform, userID string) string {
|
||||||
h.mu.RLock()
|
h.mu.RLock()
|
||||||
convID := h.sessions[h.sessionKey(platform, userID)]
|
convID := h.sessions[h.sessionKey(platform, userID)]
|
||||||
@@ -209,7 +319,89 @@ func (h *RobotHandler) cmdCurrent(platform, userID string) string {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "当前对话 ID: " + convID + "(获取标题失败)"
|
return "当前对话 ID: " + convID + "(获取标题失败)"
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("当前对话:「%s」\nID: %s", conv.Title, conv.ID)
|
role := h.getRole(platform, userID)
|
||||||
|
return fmt.Sprintf("当前对话:「%s」\nID: %s\n当前角色: %s", conv.Title, conv.ID, role)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RobotHandler) cmdRoles() string {
|
||||||
|
if h.config.Roles == nil || len(h.config.Roles) == 0 {
|
||||||
|
return "暂无可用角色。"
|
||||||
|
}
|
||||||
|
names := make([]string, 0, len(h.config.Roles))
|
||||||
|
for name, role := range h.config.Roles {
|
||||||
|
if role.Enabled {
|
||||||
|
names = append(names, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(names) == 0 {
|
||||||
|
return "暂无可用角色。"
|
||||||
|
}
|
||||||
|
sort.Slice(names, func(i, j int) bool {
|
||||||
|
if names[i] == "默认" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if names[j] == "默认" {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return names[i] < names[j]
|
||||||
|
})
|
||||||
|
var b strings.Builder
|
||||||
|
b.WriteString("【角色列表】\n")
|
||||||
|
for _, name := range names {
|
||||||
|
role := h.config.Roles[name]
|
||||||
|
desc := role.Description
|
||||||
|
if desc == "" {
|
||||||
|
desc = "无描述"
|
||||||
|
}
|
||||||
|
b.WriteString(fmt.Sprintf("· %s — %s\n", name, desc))
|
||||||
|
}
|
||||||
|
return strings.TrimSuffix(b.String(), "\n")
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RobotHandler) cmdSwitchRole(platform, userID, roleName string) string {
|
||||||
|
if roleName == "" {
|
||||||
|
return "请指定角色名称,例如:角色 渗透测试"
|
||||||
|
}
|
||||||
|
if h.config.Roles == nil {
|
||||||
|
return "暂无可用角色。"
|
||||||
|
}
|
||||||
|
role, exists := h.config.Roles[roleName]
|
||||||
|
if !exists {
|
||||||
|
return fmt.Sprintf("角色「%s」不存在。发送「角色」查看可用角色。", roleName)
|
||||||
|
}
|
||||||
|
if !role.Enabled {
|
||||||
|
return fmt.Sprintf("角色「%s」已禁用。", roleName)
|
||||||
|
}
|
||||||
|
h.setRole(platform, userID, roleName)
|
||||||
|
return fmt.Sprintf("已切换到角色:「%s」\n%s", roleName, role.Description)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RobotHandler) cmdDelete(platform, userID, convID string) string {
|
||||||
|
if convID == "" {
|
||||||
|
return "请指定对话 ID,例如:删除 xxx-xxx-xxx"
|
||||||
|
}
|
||||||
|
sk := h.sessionKey(platform, userID)
|
||||||
|
h.mu.RLock()
|
||||||
|
currentConvID := h.sessions[sk]
|
||||||
|
h.mu.RUnlock()
|
||||||
|
if convID == currentConvID {
|
||||||
|
// 删除当前对话时,先清空会话绑定
|
||||||
|
h.mu.Lock()
|
||||||
|
delete(h.sessions, sk)
|
||||||
|
h.mu.Unlock()
|
||||||
|
}
|
||||||
|
if err := h.db.DeleteConversation(convID); err != nil {
|
||||||
|
return "删除失败: " + err.Error()
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("已删除对话 ID: %s", convID)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *RobotHandler) cmdVersion() string {
|
||||||
|
v := h.config.Version
|
||||||
|
if v == "" {
|
||||||
|
v = "未知"
|
||||||
|
}
|
||||||
|
return "CyberStrikeAI " + v
|
||||||
}
|
}
|
||||||
|
|
||||||
// —————— 企业微信 ——————
|
// —————— 企业微信 ——————
|
||||||
|
|||||||
+57
-18
@@ -6,6 +6,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"net/http"
|
"net/http"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"cyberstrike-ai/internal/config"
|
"cyberstrike-ai/internal/config"
|
||||||
|
|
||||||
@@ -15,30 +16,54 @@ import (
|
|||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
dingReconnectInitial = 5 * time.Second // 首次重连间隔
|
||||||
|
dingReconnectMax = 60 * time.Second // 最大重连间隔
|
||||||
|
)
|
||||||
|
|
||||||
// StartDing 启动钉钉 Stream 长连接(无需公网),收到消息后调用 handler 并通过 SessionWebhook 回复。
|
// StartDing 启动钉钉 Stream 长连接(无需公网),收到消息后调用 handler 并通过 SessionWebhook 回复。
|
||||||
// ctx 被取消时长连接会退出,便于配置变更时重启。
|
// 断线(如笔记本睡眠、网络中断)后会自动重连;ctx 被取消时退出,便于配置变更时重启。
|
||||||
func StartDing(ctx context.Context, cfg config.RobotDingtalkConfig, h MessageHandler, logger *zap.Logger) {
|
func StartDing(ctx context.Context, cfg config.RobotDingtalkConfig, h MessageHandler, logger *zap.Logger) {
|
||||||
if !cfg.Enabled || cfg.ClientID == "" || cfg.ClientSecret == "" {
|
if !cfg.Enabled || cfg.ClientID == "" || cfg.ClientSecret == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
streamClient := client.NewStreamClient(
|
go runDingLoop(ctx, cfg, h, logger)
|
||||||
client.WithAppCredential(client.NewAppCredentialConfig(cfg.ClientID, cfg.ClientSecret)),
|
}
|
||||||
client.WithSubscription(dingutils.SubscriptionTypeKCallback, "/v1.0/im/bot/messages/get",
|
|
||||||
chatbot.NewDefaultChatBotFrameHandler(func(ctx context.Context, msg *chatbot.BotCallbackDataModel) ([]byte, error) {
|
// runDingLoop 循环维持钉钉长连接:断开且 ctx 未取消时按退避间隔重连。
|
||||||
go handleDingMessage(ctx, msg, h, logger)
|
func runDingLoop(ctx context.Context, cfg config.RobotDingtalkConfig, h MessageHandler, logger *zap.Logger) {
|
||||||
return nil, nil
|
backoff := dingReconnectInitial
|
||||||
}).OnEventReceived),
|
for {
|
||||||
)
|
streamClient := client.NewStreamClient(
|
||||||
logger.Info("钉钉 Stream 正在连接…", zap.String("client_id", cfg.ClientID))
|
client.WithAppCredential(client.NewAppCredentialConfig(cfg.ClientID, cfg.ClientSecret)),
|
||||||
go func() {
|
client.WithSubscription(dingutils.SubscriptionTypeKCallback, "/v1.0/im/bot/messages/get",
|
||||||
|
chatbot.NewDefaultChatBotFrameHandler(func(ctx context.Context, msg *chatbot.BotCallbackDataModel) ([]byte, error) {
|
||||||
|
go handleDingMessage(ctx, msg, h, logger)
|
||||||
|
return nil, nil
|
||||||
|
}).OnEventReceived),
|
||||||
|
)
|
||||||
|
logger.Info("钉钉 Stream 正在连接…", zap.String("client_id", cfg.ClientID))
|
||||||
err := streamClient.Start(ctx)
|
err := streamClient.Start(ctx)
|
||||||
if err != nil && ctx.Err() == nil {
|
if ctx.Err() != nil {
|
||||||
logger.Error("钉钉 Stream 长连接退出", zap.Error(err))
|
|
||||||
} else if ctx.Err() != nil {
|
|
||||||
logger.Info("钉钉 Stream 已按配置重启关闭")
|
logger.Info("钉钉 Stream 已按配置重启关闭")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}()
|
if err != nil {
|
||||||
logger.Info("钉钉 Stream 已启动(无需公网),等待收消息", zap.String("client_id", cfg.ClientID))
|
logger.Warn("钉钉 Stream 长连接断开(如睡眠/断网),将自动重连", zap.Error(err), zap.Duration("retry_after", backoff))
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(backoff):
|
||||||
|
// 下次重连间隔递增,上限 60 秒,避免频繁重试
|
||||||
|
if backoff < dingReconnectMax {
|
||||||
|
backoff *= 2
|
||||||
|
if backoff > dingReconnectMax {
|
||||||
|
backoff = dingReconnectMax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleDingMessage(ctx context.Context, msg *chatbot.BotCallbackDataModel, h MessageHandler, logger *zap.Logger) {
|
func handleDingMessage(ctx context.Context, msg *chatbot.BotCallbackDataModel, h MessageHandler, logger *zap.Logger) {
|
||||||
@@ -73,9 +98,23 @@ func handleDingMessage(ctx context.Context, msg *chatbot.BotCallbackDataModel, h
|
|||||||
userID = msg.ConversationId
|
userID = msg.ConversationId
|
||||||
}
|
}
|
||||||
reply := h.HandleMessage("dingtalk", userID, content)
|
reply := h.HandleMessage("dingtalk", userID, content)
|
||||||
|
// 使用 markdown 类型以便正确展示标题、列表、代码块等格式
|
||||||
|
title := reply
|
||||||
|
if idx := strings.IndexAny(reply, "\n"); idx > 0 {
|
||||||
|
title = strings.TrimSpace(reply[:idx])
|
||||||
|
}
|
||||||
|
if len(title) > 50 {
|
||||||
|
title = title[:50] + "…"
|
||||||
|
}
|
||||||
|
if title == "" {
|
||||||
|
title = "回复"
|
||||||
|
}
|
||||||
body := map[string]interface{}{
|
body := map[string]interface{}{
|
||||||
"msgtype": "text",
|
"msgtype": "markdown",
|
||||||
"text": map[string]string{"content": reply},
|
"markdown": map[string]string{
|
||||||
|
"title": title,
|
||||||
|
"text": reply,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
bodyBytes, _ := json.Marshal(body)
|
bodyBytes, _ := json.Marshal(body)
|
||||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, msg.SessionWebhook, bytes.NewReader(bodyBytes))
|
req, err := http.NewRequestWithContext(ctx, http.MethodPost, msg.SessionWebhook, bytes.NewReader(bodyBytes))
|
||||||
|
|||||||
+42
-17
@@ -4,45 +4,70 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"cyberstrike-ai/internal/config"
|
"cyberstrike-ai/internal/config"
|
||||||
|
|
||||||
|
lark "github.com/larksuite/oapi-sdk-go/v3"
|
||||||
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
|
larkcore "github.com/larksuite/oapi-sdk-go/v3/core"
|
||||||
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
|
"github.com/larksuite/oapi-sdk-go/v3/event/dispatcher"
|
||||||
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
larkim "github.com/larksuite/oapi-sdk-go/v3/service/im/v1"
|
||||||
lark "github.com/larksuite/oapi-sdk-go/v3"
|
|
||||||
larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
|
larkws "github.com/larksuite/oapi-sdk-go/v3/ws"
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
larkReconnectInitial = 5 * time.Second // 首次重连间隔
|
||||||
|
larkReconnectMax = 60 * time.Second // 最大重连间隔
|
||||||
|
)
|
||||||
|
|
||||||
type larkTextContent struct {
|
type larkTextContent struct {
|
||||||
Text string `json:"text"`
|
Text string `json:"text"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// StartLark 启动飞书长连接(无需公网),收到消息后调用 handler 并回复。
|
// StartLark 启动飞书长连接(无需公网),收到消息后调用 handler 并回复。
|
||||||
// ctx 被取消时长连接会退出,便于配置变更时重启。
|
// 断线(如笔记本睡眠、网络中断)后会自动重连;ctx 被取消时退出,便于配置变更时重启。
|
||||||
func StartLark(ctx context.Context, cfg config.RobotLarkConfig, h MessageHandler, logger *zap.Logger) {
|
func StartLark(ctx context.Context, cfg config.RobotLarkConfig, h MessageHandler, logger *zap.Logger) {
|
||||||
if !cfg.Enabled || cfg.AppID == "" || cfg.AppSecret == "" {
|
if !cfg.Enabled || cfg.AppID == "" || cfg.AppSecret == "" {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
larkClient := lark.NewClient(cfg.AppID, cfg.AppSecret)
|
go runLarkLoop(ctx, cfg, h, logger)
|
||||||
eventHandler := dispatcher.NewEventDispatcher("", "").OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
|
}
|
||||||
go handleLarkMessage(ctx, event, h, larkClient, logger)
|
|
||||||
return nil
|
// runLarkLoop 循环维持飞书长连接:断开且 ctx 未取消时按退避间隔重连。
|
||||||
})
|
func runLarkLoop(ctx context.Context, cfg config.RobotLarkConfig, h MessageHandler, logger *zap.Logger) {
|
||||||
wsClient := larkws.NewClient(cfg.AppID, cfg.AppSecret,
|
backoff := larkReconnectInitial
|
||||||
larkws.WithEventHandler(eventHandler),
|
for {
|
||||||
larkws.WithLogLevel(larkcore.LogLevelInfo),
|
larkClient := lark.NewClient(cfg.AppID, cfg.AppSecret)
|
||||||
)
|
eventHandler := dispatcher.NewEventDispatcher("", "").OnP2MessageReceiveV1(func(ctx context.Context, event *larkim.P2MessageReceiveV1) error {
|
||||||
go func() {
|
go handleLarkMessage(ctx, event, h, larkClient, logger)
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
wsClient := larkws.NewClient(cfg.AppID, cfg.AppSecret,
|
||||||
|
larkws.WithEventHandler(eventHandler),
|
||||||
|
larkws.WithLogLevel(larkcore.LogLevelInfo),
|
||||||
|
)
|
||||||
|
logger.Info("飞书长连接正在连接…", zap.String("app_id", cfg.AppID))
|
||||||
err := wsClient.Start(ctx)
|
err := wsClient.Start(ctx)
|
||||||
if err != nil && ctx.Err() == nil {
|
if ctx.Err() != nil {
|
||||||
logger.Error("飞书长连接退出", zap.Error(err))
|
|
||||||
} else if ctx.Err() != nil {
|
|
||||||
logger.Info("飞书长连接已按配置重启关闭")
|
logger.Info("飞书长连接已按配置重启关闭")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
}()
|
if err != nil {
|
||||||
logger.Info("飞书长连接已启动(无需公网)", zap.String("app_id", cfg.AppID))
|
logger.Warn("飞书长连接断开(如睡眠/断网),将自动重连", zap.Error(err), zap.Duration("retry_after", backoff))
|
||||||
|
}
|
||||||
|
select {
|
||||||
|
case <-ctx.Done():
|
||||||
|
return
|
||||||
|
case <-time.After(backoff):
|
||||||
|
if backoff < larkReconnectMax {
|
||||||
|
backoff *= 2
|
||||||
|
if backoff > larkReconnectMax {
|
||||||
|
backoff = larkReconnectMax
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleLarkMessage(ctx context.Context, event *larkim.P2MessageReceiveV1, h MessageHandler, client *lark.Client, logger *zap.Logger) {
|
func handleLarkMessage(ctx context.Context, event *larkim.P2MessageReceiveV1, h MessageHandler, client *lark.Client, logger *zap.Logger) {
|
||||||
|
|||||||
+2
-1
@@ -46,8 +46,9 @@ parameters:
|
|||||||
**注意事项:**
|
**注意事项:**
|
||||||
- 必需参数,不能为空
|
- 必需参数,不能为空
|
||||||
- 如果指定进程ID,需要配合 -d 参数使用
|
- 如果指定进程ID,需要配合 -d 参数使用
|
||||||
|
- 注意:radare2 要求文件路径必须是最后一个参数,因此 target 使用 position 1
|
||||||
required: true
|
required: true
|
||||||
position: 0
|
position: 1
|
||||||
format: "positional"
|
format: "positional"
|
||||||
- name: "commands"
|
- name: "commands"
|
||||||
type: "string"
|
type: "string"
|
||||||
|
|||||||
@@ -1289,15 +1289,21 @@
|
|||||||
|
|
||||||
<div class="settings-subsection">
|
<div class="settings-subsection">
|
||||||
<h4>机器人命令说明</h4>
|
<h4>机器人命令说明</h4>
|
||||||
<p class="settings-description">在对话中可发送以下命令:</p>
|
<p class="settings-description">在对话中可发送以下命令(支持中英文):</p>
|
||||||
<ul style="color: var(--text-muted); font-size: 13px; line-height: 1.8; margin: 8px 0 0 16px;">
|
<ul style="color: var(--text-muted); font-size: 13px; line-height: 1.8; margin: 8px 0 0 16px;">
|
||||||
<li><strong>帮助</strong> — 显示命令帮助</li>
|
<li><code>帮助</code> <code>help</code> — 显示本帮助 | Show this help</li>
|
||||||
<li><strong>列表</strong> 或 <strong>对话列表</strong> — 列出所有对话标题与 ID</li>
|
<li><code>列表</code> <code>list</code> — 列出所有对话标题与 ID | List conversations</li>
|
||||||
<li><strong>切换 <对话ID></strong> 或 <strong>继续 <对话ID></strong> — 指定对话 ID 继续对话</li>
|
<li><code>切换 <ID></code> <code>switch <ID></code> — 指定对话继续 | Switch to conversation</li>
|
||||||
<li><strong>新对话</strong> — 开启新对话</li>
|
<li><code>新对话</code> <code>new</code> — 开启新对话 | Start new conversation</li>
|
||||||
<li><strong>清空</strong> — 清空当前对话上下文(不删除历史)</li>
|
<li><code>清空</code> <code>clear</code> — 清空当前上下文 | Clear context</li>
|
||||||
<li><strong>当前</strong> — 显示当前对话 ID 与标题</li>
|
<li><code>当前</code> <code>current</code> — 显示当前对话 ID 与标题 | Show current conversation</li>
|
||||||
|
<li><code>停止</code> <code>stop</code> — 中断当前任务 | Stop running task</li>
|
||||||
|
<li><code>角色</code> <code>roles</code> — 列出所有可用角色 | List roles</li>
|
||||||
|
<li><code>角色 <名></code> <code>role <name></code> — 切换当前角色 | Switch role</li>
|
||||||
|
<li><code>删除 <ID></code> <code>delete <ID></code> — 删除指定对话 | Delete conversation</li>
|
||||||
|
<li><code>版本</code> <code>version</code> — 显示当前版本号 | Show version</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
<p class="settings-description" style="margin-top: 8px;">除以上命令外,直接输入内容将发送给 AI 进行渗透测试/安全分析。Otherwise, send any text for AI penetration testing / security analysis.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-actions">
|
<div class="settings-actions">
|
||||||
|
|||||||
Reference in New Issue
Block a user