mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-06-20 13:00:10 +02:00
Add files via upload
This commit is contained in:
@@ -640,7 +640,7 @@ func (h *AgentHandler) runRobotEinoSingleWithRetry(
|
||||
var emptyResponseAttempts int
|
||||
for {
|
||||
resultMA, errMA = multiagent.RunEinoSingleChatModelAgent(
|
||||
taskCtx, h.config, &h.config.MultiAgent, h.agent, h.logger,
|
||||
taskCtx, h.config, &h.config.MultiAgent, h.agent, h.db, h.logger,
|
||||
conversationID, h.conversationProjectID(conversationID), curMsg, curHist, roleTools, progressCallback, nil, h.projectBlackboardBlock(conversationID),
|
||||
)
|
||||
handledEmpty, exhaustedEmpty := h.handleEinoEmptyResponseContinue(
|
||||
@@ -689,7 +689,7 @@ func (h *AgentHandler) runRobotMultiAgentWithRetry(
|
||||
var emptyResponseAttempts int
|
||||
for {
|
||||
resultMA, errMA = multiagent.RunDeepAgent(
|
||||
taskCtx, h.config, &h.config.MultiAgent, h.agent, h.logger,
|
||||
taskCtx, h.config, &h.config.MultiAgent, h.agent, h.db, h.logger,
|
||||
conversationID, h.conversationProjectID(conversationID), curMsg, curHist, roleTools, progressCallback,
|
||||
h.agentsMarkdownDir, orchestration, nil, h.projectBlackboardBlock(conversationID),
|
||||
)
|
||||
@@ -2290,12 +2290,12 @@ func (h *AgentHandler) executeBatchQueue(queueID string) {
|
||||
var runErr error
|
||||
switch {
|
||||
case useBatchMulti:
|
||||
resultMA, runErr = multiagent.RunDeepAgent(taskCtx, h.config, &h.config.MultiAgent, h.agent, h.logger, conversationID, h.conversationProjectID(conversationID), finalMessage, []agent.ChatMessage{}, roleTools, progressCallback, h.agentsMarkdownDir, batchOrch, nil, h.projectBlackboardBlock(conversationID))
|
||||
resultMA, runErr = multiagent.RunDeepAgent(taskCtx, h.config, &h.config.MultiAgent, h.agent, h.db, h.logger, conversationID, h.conversationProjectID(conversationID), finalMessage, []agent.ChatMessage{}, roleTools, progressCallback, h.agentsMarkdownDir, batchOrch, nil, h.projectBlackboardBlock(conversationID))
|
||||
default:
|
||||
if h.config == nil {
|
||||
runErr = fmt.Errorf("服务器配置未加载")
|
||||
} else {
|
||||
resultMA, runErr = multiagent.RunEinoSingleChatModelAgent(taskCtx, h.config, &h.config.MultiAgent, h.agent, h.logger, conversationID, h.conversationProjectID(conversationID), finalMessage, []agent.ChatMessage{}, roleTools, progressCallback, nil, h.projectBlackboardBlock(conversationID))
|
||||
resultMA, runErr = multiagent.RunEinoSingleChatModelAgent(taskCtx, h.config, &h.config.MultiAgent, h.agent, h.db, h.logger, conversationID, h.conversationProjectID(conversationID), finalMessage, []agent.ChatMessage{}, roleTools, progressCallback, nil, h.projectBlackboardBlock(conversationID))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -224,6 +224,7 @@ func (h *AgentHandler) EinoSingleAgentLoopStream(c *gin.Context) {
|
||||
h.config,
|
||||
&h.config.MultiAgent,
|
||||
h.agent,
|
||||
h.db,
|
||||
h.logger,
|
||||
conversationID,
|
||||
h.conversationProjectID(conversationID),
|
||||
@@ -455,6 +456,7 @@ func (h *AgentHandler) EinoSingleAgentLoop(c *gin.Context) {
|
||||
h.config,
|
||||
&h.config.MultiAgent,
|
||||
h.agent,
|
||||
h.db,
|
||||
h.logger,
|
||||
prep.ConversationID,
|
||||
h.conversationProjectID(prep.ConversationID),
|
||||
|
||||
@@ -234,6 +234,7 @@ func (h *AgentHandler) MultiAgentLoopStream(c *gin.Context) {
|
||||
h.config,
|
||||
&h.config.MultiAgent,
|
||||
h.agent,
|
||||
h.db,
|
||||
h.logger,
|
||||
conversationID,
|
||||
h.conversationProjectID(conversationID),
|
||||
@@ -467,6 +468,7 @@ func (h *AgentHandler) MultiAgentLoop(c *gin.Context) {
|
||||
h.config,
|
||||
&h.config.MultiAgent,
|
||||
h.agent,
|
||||
h.db,
|
||||
h.logger,
|
||||
prep.ConversationID,
|
||||
h.conversationProjectID(prep.ConversationID),
|
||||
|
||||
@@ -22,6 +22,12 @@ func AppendSystemPromptBlock(base, block string) string {
|
||||
return base + "\n\n" + block
|
||||
}
|
||||
|
||||
const (
|
||||
factIndexFooterGetDetail = "需要完整内容(攻击链、POC、请求响应等)时必须调用 get_project_fact(fact_key),禁止凭摘要臆造细节。"
|
||||
factIndexFooterWriteHint = "写入事实时:summary 写「什么+在哪+如何验证」;body 写可复现全流程(发现/利用类 fact_key 建议 finding|chain|exploit|poc/ 前缀)。"
|
||||
factIndexFooterEmpty = "需要写入请使用 upsert_project_fact;需要详情请调用 get_project_fact(fact_key)。"
|
||||
)
|
||||
|
||||
// BuildFactIndexBlock 为 Agent 系统提示生成项目黑板索引(仅 key + summary,不含 body)。
|
||||
func BuildFactIndexBlock(db *database.DB, projectID string, cfg config.ProjectConfig) (string, error) {
|
||||
if db == nil || !cfg.Enabled {
|
||||
@@ -42,7 +48,7 @@ func BuildFactIndexBlock(db *database.DB, projectID string, cfg config.ProjectCo
|
||||
return "", err
|
||||
}
|
||||
if len(facts) == 0 {
|
||||
return fmt.Sprintf("## 项目黑板索引(project: %s, id: %s)\n(暂无事实)\n需要写入请使用 upsert_project_fact;需要详情请调用 get_project_fact(fact_key)。", proj.Name, proj.ID), nil
|
||||
return wrapFactIndexBlock(fmt.Sprintf("## 项目黑板索引(project: %s, id: %s)\n(暂无事实)\n%s", proj.Name, proj.ID, factIndexFooterEmpty)), nil
|
||||
}
|
||||
|
||||
sort.SliceStable(facts, func(i, j int) bool {
|
||||
@@ -72,7 +78,8 @@ func BuildFactIndexBlock(db *database.DB, projectID string, cfg config.ProjectCo
|
||||
if omitted > 0 {
|
||||
b.WriteString(fmt.Sprintf("\n(另有 %d 条未列入索引,请使用 list_project_facts 或 search_project_facts 查询。)\n", omitted))
|
||||
}
|
||||
b.WriteString("需要完整内容(攻击链、POC、请求响应等)时必须调用 get_project_fact(fact_key),禁止凭摘要臆造细节。\n")
|
||||
b.WriteString("写入事实时:summary 写「什么+在哪+如何验证」;body 写可复现全流程(发现/利用类 fact_key 建议 finding|chain|exploit|poc/ 前缀)。\n")
|
||||
return b.String(), nil
|
||||
b.WriteString(factIndexFooterGetDetail)
|
||||
b.WriteByte('\n')
|
||||
b.WriteString(factIndexFooterWriteHint)
|
||||
return wrapFactIndexBlock(b.String()), nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
package project
|
||||
|
||||
import "strings"
|
||||
|
||||
// FactIndexSectionHeading 黑板索引可读标题行前缀(块内保留,供 Agent 阅读)。
|
||||
const FactIndexSectionHeading = "## 项目黑板索引"
|
||||
|
||||
// FactIndexSectionStartMarker / EndMarker:HTML 注释边界,供程序化替换;对模型无指令语义。
|
||||
const (
|
||||
FactIndexSectionStartMarker = "<!-- fact-index-start -->"
|
||||
FactIndexSectionEndMarker = "<!-- fact-index-end -->"
|
||||
)
|
||||
|
||||
// ReplaceFactIndexSection 用 freshIndex 替换 content 中已有的项目黑板索引段。
|
||||
// freshIndex 须为 BuildFactIndexBlock 的完整输出。起止 HTML 注释缺失时返回 (_, false)。
|
||||
func ReplaceFactIndexSection(content, freshIndex string) (string, bool) {
|
||||
freshIndex = strings.TrimSpace(freshIndex)
|
||||
if freshIndex == "" {
|
||||
return content, false
|
||||
}
|
||||
start, ok := factIndexSectionStart(content)
|
||||
if !ok {
|
||||
return content, false
|
||||
}
|
||||
end, ok := factIndexSectionEnd(content, start)
|
||||
if !ok || end <= start {
|
||||
return content, false
|
||||
}
|
||||
return content[:start] + freshIndex + content[end:], true
|
||||
}
|
||||
|
||||
// wrapFactIndexBlock 为 BuildFactIndexBlock 正文加上统一起止 HTML 注释边界。
|
||||
func wrapFactIndexBlock(content string) string {
|
||||
content = strings.TrimSpace(content)
|
||||
return FactIndexSectionStartMarker + "\n" + content + "\n" + FactIndexSectionEndMarker + "\n"
|
||||
}
|
||||
|
||||
func factIndexSectionStart(content string) (int, bool) {
|
||||
idx := strings.Index(content, FactIndexSectionStartMarker)
|
||||
if idx < 0 {
|
||||
return 0, false
|
||||
}
|
||||
return idx, true
|
||||
}
|
||||
|
||||
func factIndexSectionEnd(content string, start int) (int, bool) {
|
||||
if start < 0 || start >= len(content) {
|
||||
return 0, false
|
||||
}
|
||||
tail := content[start:]
|
||||
idx := strings.LastIndex(tail, FactIndexSectionEndMarker)
|
||||
if idx < 0 {
|
||||
return 0, false
|
||||
}
|
||||
return start + idx + len(FactIndexSectionEndMarker), true
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
package project
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"cyberstrike-ai/internal/config"
|
||||
"cyberstrike-ai/internal/database"
|
||||
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func sampleFactIndexWithFacts(projectLabel, summary string) string {
|
||||
return wrapFactIndexBlock("## 项目黑板索引(project: " + projectLabel + ", id: x)\n" +
|
||||
"- [target/a] target — " + summary + " (tentative)\n" +
|
||||
factIndexFooterGetDetail + "\n" +
|
||||
factIndexFooterWriteHint)
|
||||
}
|
||||
|
||||
func TestReplaceFactIndexSection(t *testing.T) {
|
||||
t.Parallel()
|
||||
oldIndex := sampleFactIndexWithFacts("p1", "old summary")
|
||||
newIndex := sampleFactIndexWithFacts("p1", "new summary")
|
||||
|
||||
t.Run("replaces index before next section", func(t *testing.T) {
|
||||
content := "你是助手\n\n" + oldIndex + "\n\n## 图片分析\n看截图"
|
||||
out, ok := ReplaceFactIndexSection(content, newIndex)
|
||||
if !ok {
|
||||
t.Fatal("expected replacement")
|
||||
}
|
||||
if strings.Contains(out, "old summary") {
|
||||
t.Fatalf("old index should be gone: %q", out)
|
||||
}
|
||||
if !strings.Contains(out, "new summary") || !strings.Contains(out, "## 图片分析") {
|
||||
t.Fatalf("expected new index and preserved vision section: %q", out)
|
||||
}
|
||||
if strings.Count(out, FactIndexSectionStartMarker) != 1 || strings.Count(out, FactIndexSectionEndMarker) != 1 {
|
||||
t.Fatalf("expected exactly one start/end marker pair: %q", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("replaces index at end", func(t *testing.T) {
|
||||
content := "## 项目测试范围\nscope\n\n" + oldIndex
|
||||
out, ok := ReplaceFactIndexSection(content, newIndex)
|
||||
if !ok {
|
||||
t.Fatal("expected replacement")
|
||||
}
|
||||
if !strings.Contains(out, "## 项目测试范围") || !strings.Contains(out, "new summary") {
|
||||
t.Fatalf("scope preserved, index updated: %q", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("summary with false markdown header does not truncate early", func(t *testing.T) {
|
||||
summaryWithFakeHeader := "see\n\n## fake header in summary"
|
||||
old := sampleFactIndexWithFacts("p1", summaryWithFakeHeader)
|
||||
newIdx := sampleFactIndexWithFacts("p1", "new summary")
|
||||
content := old + "\n\n## 图片分析\nvision"
|
||||
out, ok := ReplaceFactIndexSection(content, newIdx)
|
||||
if !ok {
|
||||
t.Fatal("expected replacement")
|
||||
}
|
||||
if strings.Contains(out, "fake header in summary") {
|
||||
t.Fatalf("old index tail should be fully removed: %q", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("summary containing end marker text does not truncate early", func(t *testing.T) {
|
||||
summary := "note " + FactIndexSectionEndMarker + " in summary"
|
||||
old := sampleFactIndexWithFacts("p1", summary)
|
||||
newIdx := sampleFactIndexWithFacts("p1", "clean")
|
||||
content := old + "\n\n## 图片分析\nvision"
|
||||
out, ok := ReplaceFactIndexSection(content, newIdx)
|
||||
if !ok {
|
||||
t.Fatal("expected replacement")
|
||||
}
|
||||
if strings.Contains(out, "in summary") {
|
||||
t.Fatalf("old block should be fully removed: %q", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("missing html markers does not replace", func(t *testing.T) {
|
||||
legacy := "## 项目黑板索引(project: p1, id: x)\n- [a] note — old (tentative)\n"
|
||||
newIdx := sampleFactIndexWithFacts("p1", "new")
|
||||
out, ok := ReplaceFactIndexSection("prefix\n\n"+legacy, newIdx)
|
||||
if ok {
|
||||
t.Fatalf("expected no replacement without markers: %q", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty facts block", func(t *testing.T) {
|
||||
oldEmpty := wrapFactIndexBlock("## 项目黑板索引(project: p1, id: x)\n(暂无事实)\n" + factIndexFooterEmpty)
|
||||
newEmpty := sampleFactIndexWithFacts("p1", "first fact")
|
||||
out, ok := ReplaceFactIndexSection(oldEmpty, newEmpty)
|
||||
if !ok {
|
||||
t.Fatal("expected replacement")
|
||||
}
|
||||
if strings.Contains(out, "(暂无事实)") {
|
||||
t.Fatalf("old empty block should be gone: %q", out)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("no marker", func(t *testing.T) {
|
||||
_, ok := ReplaceFactIndexSection("no blackboard here", newIndex)
|
||||
if ok {
|
||||
t.Fatal("expected false when marker missing")
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("empty fresh index", func(t *testing.T) {
|
||||
_, ok := ReplaceFactIndexSection(oldIndex, " ")
|
||||
if ok {
|
||||
t.Fatal("expected false for empty fresh index")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestFactIndexSectionBounds_useHTMLMarkers(t *testing.T) {
|
||||
t.Parallel()
|
||||
body := sampleFactIndexWithFacts("p", "line with\n\n## not a real section") + "TAIL_SHOULD_DROP"
|
||||
start, ok := factIndexSectionStart(body)
|
||||
if !ok || !strings.HasPrefix(body[start:], FactIndexSectionStartMarker) {
|
||||
t.Fatalf("start should be at html start marker, got %d", start)
|
||||
}
|
||||
end, ok := factIndexSectionEnd(body, start)
|
||||
if !ok || body[end:] != "\nTAIL_SHOULD_DROP" {
|
||||
t.Fatalf("end should be after end marker, got remainder %q", body[end:])
|
||||
}
|
||||
}
|
||||
|
||||
func TestBuildFactIndexBlock_includesHTMLMarkers(t *testing.T) {
|
||||
t.Parallel()
|
||||
dbPath := filepath.Join(t.TempDir(), "facts.db")
|
||||
db, err := database.NewDB(dbPath, zap.NewNop())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer db.Close()
|
||||
|
||||
proj, err := db.CreateProject(&database.Project{Name: "marker-proj"})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
block, err := BuildFactIndexBlock(db, proj.ID, config.ProjectConfig{Enabled: true})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !strings.HasPrefix(strings.TrimSpace(block), FactIndexSectionStartMarker) {
|
||||
t.Fatalf("block should start with start marker: %q", block)
|
||||
}
|
||||
if !strings.Contains(block, FactIndexSectionEndMarker) {
|
||||
t.Fatalf("block should include end marker: %q", block)
|
||||
}
|
||||
}
|
||||
@@ -2,10 +2,14 @@ package project
|
||||
|
||||
import "strings"
|
||||
|
||||
// VisionImageSectionMarker 图片分析 section 标题(与 AppendVisionImageAnalysisIfReady 注入一致)。
|
||||
const VisionImageSectionMarker = "## 图片分析"
|
||||
|
||||
// VisionImageAnalysisSection 单/多代理共用的图片分析提示(analyze_image;上下文仅保留文字摘要)。
|
||||
func VisionImageAnalysisSection() string {
|
||||
var b strings.Builder
|
||||
b.WriteString("## 图片分析\n\n")
|
||||
b.WriteString(VisionImageSectionMarker)
|
||||
b.WriteString("\n\n")
|
||||
b.WriteString("- 遇到图片文件(截图、验证码、登录页、报告配图)时,若存在工具 analyze_image,请传入服务器上的文件路径进行分析。\n")
|
||||
b.WriteString("- 不要对二进制图片使用 read_file 指望理解内容;用户消息中「📎 xxx.png: /path」即为可传给 analyze_image 的路径。\n")
|
||||
b.WriteString("- 验证码类:若已从页面或接口保存为本地图片(如 captcha.png),用 analyze_image,question 写明「只输出验证码字符」;识别失败则刷新验证码后重新保存再识;复杂滑块/行为验证码勿指望单次识图成功。\n")
|
||||
|
||||
Reference in New Issue
Block a user