scorpio e6e8bd8ce1 feat: phase强制校验、重复分配防护、章节file call、成员对话模式
- 系统级phase强制校验:阻止跨阶段分配任务,前置阶段未完成时自动拦截
- 循环内去重:同phase同成员不会被重复执行,消除master重复分配问题
- 消除重复提示:feedbackMsg和continueMsg不再同时注入
- 动态章节file call:所有静态文件完成后自动触发masterChapterFileCall
- 章节内容安全网拦截:master在聊天中输出文档内容时自动保存到workspace
- 用户@成员对话:区分对话和任务分配,短消息/问候走对话路由而非任务流水线
- handleMemberConversation改进:初始化系统提示、流式输出、DB存储
- 策划编辑AGENT.md:新增前置依赖检查、评分必须引用文档数据来源
- TodoList更新提醒:任务完成后提醒master用编辑指令更新TodoList
- buildWorkflowStep强化:显示阶段依赖关系和后续阶段解锁提示

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 22:16:08 +08:00

129 lines
4.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package room
import (
"context"
"fmt"
"log"
"os"
"path/filepath"
"strings"
"time"
"github.com/sdaduanbilei/agent-team/internal/agent"
"github.com/sdaduanbilei/agent-team/internal/llm"
)
func (r *Room) updateMasterMemory(ctx context.Context, task string, msgs []llm.Message) {
if len([]rune(strings.TrimSpace(task))) < 10 {
log.Printf("[memory] 跳过短任务记忆: %q", task)
return
}
summaryPrompt := fmt.Sprintf(`基于这个任务: %q
总结核心经验最多3条 bullet points
如果这个任务没有值得记忆的经验(如简单问候、闲聊),只输出 SKIP。`, task)
memMsgs := append(msgs, llm.NewMsg("user", summaryPrompt))
summary, err := r.master.Chat(ctx, memMsgs, nil)
if err != nil || summary == "" {
return
}
if strings.TrimSpace(summary) == "SKIP" {
log.Printf("[memory] LLM 判断跳过记忆: %q", task)
return
}
filename := time.Now().Format("2006-01") + ".md"
existing, _ := os.ReadFile(filepath.Join(r.master.Dir, "memory", filename))
taskTitle := task
if len([]rune(taskTitle)) > 50 {
taskTitle = string([]rune(taskTitle)[:50])
}
content := string(existing) + fmt.Sprintf("\n## %s — %s\n\n%s\n", time.Now().Format("2006-01-02"), taskTitle, summary)
r.master.SaveMemory(filename, content)
memFile := filepath.Join(r.master.Dir, "memory", filename)
if info, err := os.Stat(memFile); err == nil && info.Size() > 20*1024 {
log.Printf("[memory] 当月文件 %s 超过 20KB触发压缩", filename)
go r.master.CompressMemory(context.Background())
}
}
// CompressAllMemory 压缩 room 中所有 agentmaster + members的记忆。
func (r *Room) CompressAllMemory(ctx context.Context) {
if r.master != nil {
if err := r.master.CompressMemory(ctx); err != nil {
log.Printf("[memory] master 压缩失败: %v", err)
}
}
for name, member := range r.members {
if err := member.CompressMemory(ctx); err != nil {
log.Printf("[memory] 成员 %s 压缩失败: %v", name, err)
}
}
log.Printf("[memory] 全部 agent 记忆压缩完成")
}
// updateMemberMemory 为成员 agent 保存任务经验。
func (r *Room) updateMemberMemory(ctx context.Context, member *agent.Agent, task, result string) {
if len([]rune(strings.TrimSpace(task))) < 10 {
return
}
summaryPrompt := fmt.Sprintf(`基于这个任务和结果:
任务: %q
结果: %.500s
总结1-2条关键经验。如果没有值得记忆的只输出 SKIP。`, task, result)
msgs := []llm.Message{
llm.NewMsg("system", "你是一个团队成员,总结工作经验。"),
llm.NewMsg("user", summaryPrompt),
}
summary, err := member.Chat(ctx, msgs, nil)
if err != nil || summary == "" || strings.TrimSpace(summary) == "SKIP" {
return
}
filename := time.Now().Format("2006-01") + ".md"
existing, _ := os.ReadFile(filepath.Join(member.Dir, "memory", filename))
taskTitle := task
if len([]rune(taskTitle)) > 50 {
taskTitle = string([]rune(taskTitle)[:50])
}
content := string(existing) + fmt.Sprintf("\n## %s — %s\n\n%s\n", time.Now().Format("2006-01-02"), taskTitle, summary)
member.SaveMemory(filename, content)
log.Printf("[memory] 成员 %s 记忆已更新", member.Config.Name)
}
// appendPlanLog 将沟通记录追加到任务计划文档
func (r *Room) appendPlanLog(from, to, content string) {
if r.planFilename == "" {
return
}
path := filepath.Join(r.Dir, "workspace", r.planFilename)
f, err := os.OpenFile(path, os.O_APPEND|os.O_WRONLY, 0644)
if err != nil {
return
}
defer f.Close()
entry := fmt.Sprintf("\n---\n**[%s] %s → %s**\n\n%s\n",
time.Now().Format("15:04:05"), from, to, content)
f.WriteString(entry)
}
func (r *Room) updateTasks(msgs []llm.Message) {
var tasks strings.Builder
tasks.WriteString("# Tasks\n\n")
for _, m := range msgs {
if m.Role == "assistant" {
assignments := parseAssignments(m.Content)
for name, task := range assignments {
tasks.WriteString(fmt.Sprintf("- [ ] [%s] %s\n", name, task))
}
}
}
content := tasks.String()
os.WriteFile(filepath.Join(r.Dir, "tasks.md"), []byte(content), 0644)
r.emit(Event{Type: EvtTasksUpdate, Content: content})
}