package main import ( "fmt" "os" "os/exec" "path/filepath" "strconv" "strings" "sync" "time" ) // --- 道 (Config & State) --- type Config struct { MemoryRoot string Port string } type TaoServer struct { config Config mu sync.Mutex // 确保并发写入安全 conns sync.Map // token -> chan string } // --- 一、二、三 (Time Logic) --- // GetTaoPath 根据道家哲学计算时间路径 func (s *TaoServer) GetTaoPath() (string, string) { return s.GetTaoPathFromTime(time.Now()) } // GetTaoPathFromTime 根据指定时间计算路径 func (s *TaoServer) GetTaoPathFromTime(t time.Time) (string, string) { year := t.Format("2006") month := int(t.Month()) _, isoWeek := t.ISOWeek() // 一生二:上半年(H1_Upper) / 下半年(H2_Lower) half := "H1_Upper" if month > 6 { half = "H2_Lower" } // 二生三:季度 quarter := fmt.Sprintf("Q%d", (month-1)/3+1) // 三生万物:月与周 monthDir := fmt.Sprintf("%02d_%s", month, t.Month().String()) weekDir := fmt.Sprintf("W%02d", isoWeek) dirPath := filepath.Join(s.config.MemoryRoot, year, half, quarter, monthDir, weekDir) fileName := t.Format("2006-01-02_Monday.md") return dirPath, fileName } func (s *TaoServer) getMonthDirFromTime(t time.Time) string { year := t.Format("2006") month := int(t.Month()) half := "H1_Upper" if month > 6 { half = "H2_Lower" } quarter := fmt.Sprintf("Q%d", (month-1)/3+1) monthDir := fmt.Sprintf("%02d_%s", month, t.Month().String()) return filepath.Join(s.config.MemoryRoot, year, half, quarter, monthDir) } func (s *TaoServer) getQuarterDirFromTime(t time.Time) string { year := t.Format("2006") month := int(t.Month()) half := "H1_Upper" if month > 6 { half = "H2_Lower" } quarter := fmt.Sprintf("Q%d", (month-1)/3+1) return filepath.Join(s.config.MemoryRoot, year, half, quarter) } func (s *TaoServer) getHalfDirFromTime(t time.Time) string { year := t.Format("2006") month := int(t.Month()) half := "H1_Upper" if month > 6 { half = "H2_Lower" } return filepath.Join(s.config.MemoryRoot, year, half) } func (s *TaoServer) getYearDirFromTime(t time.Time) string { return filepath.Join(s.config.MemoryRoot, t.Format("2006")) } func (s *TaoServer) ApplyWeeklyTemplateIfMissing(content string, weekOffset int) string { if strings.Contains(content, "### 📅 时空坐标") || strings.Contains(content, "## 📅 时空坐标") { return content } targetTime := time.Now().AddDate(0, 0, weekOffset*7) year, week := targetTime.ISOWeek() period := fmt.Sprintf("%d年第%d周", year, week) refinedAt := time.Now().Format("2006-01-02") template := fmt.Sprintf(`### 📅 时空坐标 * **周期**:%s * **炼化时间**:%s ### ⚡ 核心突破 (Major Breakthroughs) %s ### 🏮 避坑/因果记录 (Pitfalls & Karma) - ### 🧪 炼化所得 (Extracted Essence) - ### 🚀 下一轮循环 (Next Cycle) - [ ] ### 🧧 炼丹师评注 - `, period, refinedAt, strings.TrimSpace(content)) return template } func (s *TaoServer) CaptureIdea(content string, tags []string) (string, error) { if len(tags) == 0 { tags = []string{"Unsorted"} } now := time.Now() fileName := fmt.Sprintf("Idea_%d.md", now.Unix()) dirPath := filepath.Join(s.config.MemoryRoot, "Inspirations") if err := os.MkdirAll(dirPath, 0755); err != nil { return "", err } var buf strings.Builder buf.WriteString("---\n") buf.WriteString(fmt.Sprintf("date: %s\n", now.Format("2006-01-02 15:04:05"))) buf.WriteString("type: inspiration\n") if len(tags) > 0 { buf.WriteString("tags:\n") for _, tag := range tags { buf.WriteString(fmt.Sprintf(" - %s\n", tag)) } } buf.WriteString("---\n\n") buf.WriteString(content) filePath := filepath.Join(dirPath, fileName) if err := os.WriteFile(filePath, []byte(buf.String()), 0644); err != nil { return "", err } summary := content if len([]rune(summary)) > 40 { summary = string([]rune(summary)[:40]) + "..." } _ = s.Record("idea", fmt.Sprintf("💡 %s (归档: %s)", summary, fileName), 2) return fmt.Sprintf("灵感已归档: %s", filePath), nil } // GetMonthData 读取指定月份目录下所有 Week_Summary.md func (s *TaoServer) GetMonthData(monthOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, monthOffset, 0) monthDir := s.getMonthDirFromTime(targetTime) var monthContent strings.Builder monthContent.WriteString(fmt.Sprintf("# 待炼化月素材: %s\n\n", monthDir)) err := filepath.Walk(monthDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && info.Name() == "Week_Summary.md" { content, err := os.ReadFile(path) if err != nil { return nil } rel, _ := filepath.Rel(monthDir, path) monthContent.WriteString(fmt.Sprintf("## 来源: %s\n%s\n\n", rel, string(content))) } return nil }) return monthContent.String(), err } // RecordMonthSummary 将炼化后的内容写入 Month_Summary.md func (s *TaoServer) RecordMonthSummary(content string, monthOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, monthOffset, 0) monthDir := s.getMonthDirFromTime(targetTime) if err := os.MkdirAll(monthDir, 0755); err != nil { return "", err } summaryPath := filepath.Join(monthDir, "Month_Summary.md") header := fmt.Sprintf("---\ntype: summary\nlevel: month\nrefined_at: %s\n---\n\n", time.Now().Format(time.RFC3339)) err := os.WriteFile(summaryPath, []byte(header+content), 0644) if err != nil { return "", err } return summaryPath, nil } // GetQuarterData 读取指定季度目录下所有 Month_Summary.md func (s *TaoServer) GetQuarterData(quarterOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, quarterOffset*3, 0) quarterDir := s.getQuarterDirFromTime(targetTime) var quarterContent strings.Builder quarterContent.WriteString(fmt.Sprintf("# 待炼化季素材: %s\n\n", quarterDir)) err := filepath.Walk(quarterDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && info.Name() == "Month_Summary.md" { content, err := os.ReadFile(path) if err != nil { return nil } rel, _ := filepath.Rel(quarterDir, path) quarterContent.WriteString(fmt.Sprintf("## 来源: %s\n%s\n\n", rel, string(content))) } return nil }) return quarterContent.String(), err } // RecordQuarterSummary 将炼化后的内容写入 Quarter_Summary.md func (s *TaoServer) RecordQuarterSummary(content string, quarterOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, quarterOffset*3, 0) quarterDir := s.getQuarterDirFromTime(targetTime) if err := os.MkdirAll(quarterDir, 0755); err != nil { return "", err } summaryPath := filepath.Join(quarterDir, "Quarter_Summary.md") header := fmt.Sprintf("---\ntype: summary\nlevel: quarter\nrefined_at: %s\n---\n\n", time.Now().Format(time.RFC3339)) err := os.WriteFile(summaryPath, []byte(header+content), 0644) if err != nil { return "", err } return summaryPath, nil } // GetSemiannualData 读取指定半年目录下所有 Quarter_Summary.md func (s *TaoServer) GetSemiannualData(halfOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, halfOffset*6, 0) halfDir := s.getHalfDirFromTime(targetTime) var halfContent strings.Builder halfContent.WriteString(fmt.Sprintf("# 待炼化半年素材: %s\n\n", halfDir)) err := filepath.Walk(halfDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && info.Name() == "Quarter_Summary.md" { content, err := os.ReadFile(path) if err != nil { return nil } rel, _ := filepath.Rel(halfDir, path) halfContent.WriteString(fmt.Sprintf("## 来源: %s\n%s\n\n", rel, string(content))) } return nil }) return halfContent.String(), err } // RecordSemiannualSummary 将炼化后的内容写入 Semiannual_Summary.md func (s *TaoServer) RecordSemiannualSummary(content string, halfOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, halfOffset*6, 0) halfDir := s.getHalfDirFromTime(targetTime) if err := os.MkdirAll(halfDir, 0755); err != nil { return "", err } summaryPath := filepath.Join(halfDir, "Semiannual_Summary.md") header := fmt.Sprintf("---\ntype: summary\nlevel: semiannual\nrefined_at: %s\n---\n\n", time.Now().Format(time.RFC3339)) err := os.WriteFile(summaryPath, []byte(header+content), 0644) if err != nil { return "", err } return summaryPath, nil } // GetYearData 读取指定年度目录下所有 Semiannual_Summary.md func (s *TaoServer) GetYearData(yearOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(yearOffset, 0, 0) yearDir := s.getYearDirFromTime(targetTime) var yearContent strings.Builder yearContent.WriteString(fmt.Sprintf("# 待炼化年素材: %s\n\n", yearDir)) err := filepath.Walk(yearDir, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !info.IsDir() && info.Name() == "Semiannual_Summary.md" { content, err := os.ReadFile(path) if err != nil { return nil } rel, _ := filepath.Rel(yearDir, path) yearContent.WriteString(fmt.Sprintf("## 来源: %s\n%s\n\n", rel, string(content))) } return nil }) return yearContent.String(), err } // RecordYearSummary 将炼化后的内容写入 Year_Summary.md func (s *TaoServer) RecordYearSummary(content string, yearOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(yearOffset, 0, 0) yearDir := s.getYearDirFromTime(targetTime) if err := os.MkdirAll(yearDir, 0755); err != nil { return "", err } summaryPath := filepath.Join(yearDir, "Year_Summary.md") header := fmt.Sprintf("---\ntype: summary\nlevel: year\nrefined_at: %s\n---\n\n", time.Now().Format(time.RFC3339)) err := os.WriteFile(summaryPath, []byte(header+content), 0644) if err != nil { return "", err } return summaryPath, nil } func (s *TaoServer) HousekeepMemory(targetMonth string) (string, error) { s.mu.Lock() defer s.mu.Unlock() parts := strings.Split(targetMonth, "-") if len(parts) != 2 { return "", fmt.Errorf("invalid target_month format, expected YYYY-MM") } year, month := parts[0], parts[1] if len(year) != 4 || len(month) != 2 { return "", fmt.Errorf("invalid target_month format, expected YYYY-MM") } monthNum, err := strconv.Atoi(month) if err != nil || monthNum < 1 || monthNum > 12 { return "", fmt.Errorf("invalid month number") } t := time.Date(mustAtoi(year), time.Month(monthNum), 1, 0, 0, 0, 0, time.Local) monthDir := s.getMonthDirFromTime(t) summaryPath := filepath.Join(monthDir, "Month_Summary.md") info, err := os.Stat(summaryPath) if err != nil { return "", fmt.Errorf("Month_Summary.md not found: %s", summaryPath) } if info.Size() < 100 { return "", fmt.Errorf("Month_Summary.md too small, skip archive") } content, err := os.ReadFile(summaryPath) if err != nil { return "", err } if !strings.Contains(string(content), "###") { return "", fmt.Errorf("Month_Summary.md missing expected headings") } archiveRoot := filepath.Join(s.config.MemoryRoot, "_Archive", year, month) if err := os.MkdirAll(archiveRoot, 0755); err != nil { return "", err } entries, err := os.ReadDir(monthDir) if err != nil { return "", err } for _, entry := range entries { if entry.IsDir() && strings.HasPrefix(entry.Name(), "W") { src := filepath.Join(monthDir, entry.Name()) dst := filepath.Join(archiveRoot, entry.Name()) if err := os.Rename(src, dst); err != nil { return "", err } } if !entry.IsDir() && entry.Name() != "Month_Summary.md" { src := filepath.Join(monthDir, entry.Name()) dst := filepath.Join(archiveRoot, entry.Name()) if err := os.Rename(src, dst); err != nil { return "", err } } } marker := fmt.Sprintf("原始数据已归档至 %s 于 %s\n", archiveRoot, time.Now().Format("2006-01-02 15:04:05")) _ = os.WriteFile(filepath.Join(monthDir, "ARCHIVED.txt"), []byte(marker), 0644) return fmt.Sprintf("归档完成: %s", archiveRoot), nil } func mustAtoi(s string) int { n, _ := strconv.Atoi(s) return n } func (s *TaoServer) InspectAndPropose(repoPath string) (string, error) { s.mu.Lock() defer s.mu.Unlock() if repoPath == "" { repoPath = "/root/.openclaw/workspace/tao_mcp_go" } // 1) 拉取最新代码(若失败则继续) _ = exec.Command("git", "-C", repoPath, "pull").Run() // 2) 收集灵感(包含 #Todo/#Fix) inspDir := filepath.Join(s.config.MemoryRoot, "Inspirations") var ideas []string _ = filepath.Walk(inspDir, func(path string, info os.FileInfo, err error) error { if err != nil || info.IsDir() || filepath.Ext(path) != ".md" { return nil } b, err := os.ReadFile(path) if err != nil { return nil } text := string(b) if strings.Contains(text, "#Todo") || strings.Contains(text, "#Fix") { ideas = append(ideas, text) } return nil }) if len(ideas) == 0 { return "未发现 #Todo/#Fix 灵感记录,跳过。", nil } // 3) 写入待办清单文件(供 Agent 生成补丁时参考) patchDir := filepath.Join(s.config.MemoryRoot, "_Proposals") _ = os.MkdirAll(patchDir, 0755) proposalPath := filepath.Join(patchDir, fmt.Sprintf("proposal_%s.md", time.Now().Format("20060102_150405"))) body := "# 灵感-代码对照清单\n\n" + strings.Join(ideas, "\n\n---\n\n") _ = os.WriteFile(proposalPath, []byte(body), 0644) // 4) 记录到当日日志 summary := fmt.Sprintf("生成灵感对照清单:%s(共 %d 条)", proposalPath, len(ideas)) _ = s.Record("inspect", summary, 3) return summary, nil } func (s *TaoServer) RecordDaily(content string, karma int) (string, error) { if karma == 0 { karma = 1 } if err := s.Record("daily", content, karma); err != nil { return "", err } dir, file := s.GetTaoPath() return filepath.Join(dir, file), nil } // --- 以简御繁 (Core Logic) --- // Record 将提炼后的精华存入时间流 func (s *TaoServer) Record(category, content string, karma int) error { s.mu.Lock() defer s.mu.Unlock() dir, file := s.GetTaoPath() if err := os.MkdirAll(dir, 0755); err != nil { return err } fullPath := filepath.Join(dir, file) f, err := os.OpenFile(fullPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) if err != nil { return err } defer f.Close() entry := fmt.Sprintf("### [%s] [%s] (Karma:%d)\n%s\n\n", time.Now().Format("15:04:05"), category, karma, content) _, err = f.WriteString(entry) return err } // GetWeekData 读取指定周目录下所有的每日记录,为“炼化”准备素材 func (s *TaoServer) GetWeekData(weekOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, 0, weekOffset*7) targetDir, _ := s.GetTaoPathFromTime(targetTime) files, err := os.ReadDir(targetDir) if err != nil { return "", err } var weekContent strings.Builder weekContent.WriteString(fmt.Sprintf("# 待炼化周素材: %s\n\n", targetDir)) for _, file := range files { if !file.IsDir() && filepath.Ext(file.Name()) == ".md" && file.Name() != "Week_Summary.md" { content, _ := os.ReadFile(filepath.Join(targetDir, file.Name())) weekContent.WriteString(fmt.Sprintf("## 来源: %s\n%s\n\n", file.Name(), string(content))) } } return weekContent.String(), nil } // RecordSummary 将炼化后的内容写入 Week_Summary.md func (s *TaoServer) RecordSummary(content string, weekOffset int) (string, error) { s.mu.Lock() defer s.mu.Unlock() targetTime := time.Now().AddDate(0, 0, weekOffset*7) targetDir, _ := s.GetTaoPathFromTime(targetTime) // 周总结在周目录下 summaryPath := filepath.Join(targetDir, "Week_Summary.md") header := fmt.Sprintf("---\ntype: summary\nrefined_at: %s\n---\n\n", time.Now().Format(time.RFC3339)) err := os.WriteFile(summaryPath, []byte(header+content), 0644) if err != nil { return "", err } return summaryPath, nil } // SearchMemory 遍历所有 Markdown 文件,寻找包含关键词的内容 func (s *TaoServer) SearchMemoryAdvanced(keyword string, related []string, causal bool, includeArchive bool) ([]string, error) { s.mu.Lock() defer s.mu.Unlock() type hit struct { term string causal bool content string } hits := []hit{} terms := []string{keyword} if causal { for _, t := range related { if t != "" && t != keyword { terms = append(terms, t) } } } err := filepath.Walk(s.config.MemoryRoot, func(path string, info os.FileInfo, err error) error { if err != nil { return err } if !includeArchive && info.IsDir() && strings.Contains(path, string(filepath.Separator)+"_Archive"+string(filepath.Separator)) { return filepath.SkipDir } if !info.IsDir() && filepath.Ext(path) == ".md" { if !includeArchive && strings.Contains(path, string(filepath.Separator)+"_Archive"+string(filepath.Separator)) { return nil } content, err := os.ReadFile(path) if err != nil { return nil } text := string(content) for _, term := range terms { if term != "" && strings.Contains(text, term) { rel, _ := filepath.Rel(s.config.MemoryRoot, path) label := "命中" isCausal := term != keyword if isCausal { label = "关联" } hits = append(hits, hit{term: term, causal: isCausal, content: fmt.Sprintf("[%s: %s] %s\n%s", label, term, rel, text)}) break } } } return nil }) // 优先原词命中 var results []string for _, h := range hits { if !h.causal { results = append(results, h.content) } } for _, h := range hits { if h.causal { results = append(results, h.content) } } return results, err }