- 系统级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>
129 lines
4.0 KiB
Go
129 lines
4.0 KiB
Go
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 中所有 agent(master + 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})
|
||
}
|