mirror of
https://github.com/dtzp555-max/memory-continuity.git
synced 2026-07-21 21:15:07 +00:00
release: v5.0.0 — conservative subagent support (parent seed + child recovery)
- Add workspace resolution helpers (isSubagentSession, parseParentAgentId, resolveAgentWorkspace) - Parent→Child seeding: inject parent CURRENT_STATE.md into subagent context at startup - Child→Parent recovery: new subagent_ended hook reads child unsurfaced results, merges into parent - Add subagentSeed and subagentRecovery config keys (both default: true) - Add /mc subagents command for cross-workspace state overview - Bump lifecycle plugin to v5.0.0, mc-plugin to v2.1.0 Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,674 @@
|
||||
# v3.2 Mid-Term Memory Layer Implementation Plan
|
||||
|
||||
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add layered markdown summaries (daily/weekly), enhanced cross-source search, and automatic tag extraction to the Memory Continuity plugin.
|
||||
|
||||
**Architecture:** Three independent features layered on v3.1's session logging. Daily summaries are generated from session logs at `agent_end` when the day changes. Weekly summaries roll up dailies every Monday. Enhanced search extends the existing `/mc search` to cover sessions + summaries with context lines. Auto-tagging scans session entries for `#tag` patterns and maintains a `memory/tags.md` index. All pure markdown, zero dependencies.
|
||||
|
||||
**Tech Stack:** Node.js (ES modules), pure filesystem, zero dependencies
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
| File | Action | Responsibility |
|
||||
|------|--------|---------------|
|
||||
| `index.js` | Modify | Add `generateDailySummary`, `generateWeeklySummary`, call from `agent_end` |
|
||||
| `openclaw.plugin.json` | Modify | Add `summaryEnabled` config |
|
||||
| `mc-plugin/index.js` | Modify | Enhance `cmdSearch` to cover sessions+summaries, add `/mc tags` and `/mc summary` commands |
|
||||
|
||||
---
|
||||
|
||||
### Task 1: Daily Summary Generation
|
||||
|
||||
**Files:**
|
||||
- Modify: `index.js` (add helper + call in agent_end)
|
||||
- Modify: `openclaw.plugin.json` (add `summaryEnabled` config)
|
||||
|
||||
- [ ] **Step 1: Add `summaryEnabled` config to `openclaw.plugin.json`**
|
||||
|
||||
Read `openclaw.plugin.json`. Add after the `tailProtectCount` property in configSchema:
|
||||
|
||||
```json
|
||||
"summaryEnabled": {
|
||||
"type": "boolean",
|
||||
"default": true,
|
||||
"description": "Generate daily and weekly summaries from session logs"
|
||||
}
|
||||
```
|
||||
|
||||
Add corresponding uiHints after the `tailProtectCount` hint:
|
||||
|
||||
```json
|
||||
"summaryEnabled": {
|
||||
"label": "Summary generation",
|
||||
"help": "Auto-generate daily/weekly summaries from session logs"
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add `generateDailySummary` helper to `index.js`**
|
||||
|
||||
Read `index.js`. Add after the `extractTailMessages` function (ends around line 302), before `extractStateFromMessages`:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Generate a daily summary from the previous day's session log.
|
||||
* Only runs when today differs from the last summary date.
|
||||
* Output: memory/summaries/daily/YYYY-MM-DD.md
|
||||
*/
|
||||
function generateDailySummary(workspaceDir, config = {}) {
|
||||
if (config.summaryEnabled === false) return;
|
||||
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
const now = new Date();
|
||||
const today = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
|
||||
|
||||
// Check yesterday's date
|
||||
const yesterday = new Date(now);
|
||||
yesterday.setDate(yesterday.getDate() - 1);
|
||||
const yStr = `${yesterday.getFullYear()}-${pad(yesterday.getMonth() + 1)}-${pad(yesterday.getDate())}`;
|
||||
|
||||
const summaryDir = path.join(workspaceDir, "memory", "summaries", "daily");
|
||||
const summaryFile = path.join(summaryDir, `${yStr}.md`);
|
||||
|
||||
// Skip if summary already exists for yesterday
|
||||
if (fs.existsSync(summaryFile)) return;
|
||||
|
||||
// Read yesterday's session log
|
||||
const sessionFile = path.join(workspaceDir, "memory", "sessions", `${yStr}.md`);
|
||||
const sessionContent = readFile(sessionFile);
|
||||
if (!sessionContent) return; // No sessions yesterday
|
||||
|
||||
// Parse session entries
|
||||
const entries = sessionContent.split(/^### /gm).filter(e => e.trim());
|
||||
if (entries.length === 0) return;
|
||||
|
||||
const topics = [];
|
||||
let totalUser = 0;
|
||||
let totalAssistant = 0;
|
||||
let totalTokens = 0;
|
||||
|
||||
for (const entry of entries) {
|
||||
const topicMatch = entry.match(/\*\*Topic:\*\*\s*(.+)/);
|
||||
const msgMatch = entry.match(/\*\*Messages:\*\*\s*(\d+)\s*user\s*\/\s*(\d+)\s*assistant/);
|
||||
const tokenMatch = entry.match(/\*\*Est\. tokens:\*\*\s*~(\d+)/);
|
||||
|
||||
if (topicMatch) topics.push(topicMatch[1].trim());
|
||||
if (msgMatch) {
|
||||
totalUser += parseInt(msgMatch[1]);
|
||||
totalAssistant += parseInt(msgMatch[2]);
|
||||
}
|
||||
if (tokenMatch) totalTokens += parseInt(tokenMatch[1]);
|
||||
}
|
||||
|
||||
// Build daily summary
|
||||
const summary = [
|
||||
`# Daily Summary — ${yStr}`,
|
||||
"",
|
||||
`**Sessions:** ${entries.length}`,
|
||||
`**Messages:** ${totalUser} user / ${totalAssistant} assistant`,
|
||||
`**Est. tokens:** ~${totalTokens}`,
|
||||
"",
|
||||
"## Topics",
|
||||
...topics.map((t, i) => `${i + 1}. ${t}`),
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
fs.mkdirSync(summaryDir, { recursive: true });
|
||||
fs.writeFileSync(summaryFile, summary, "utf8");
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Call `generateDailySummary` from `agent_end` hook**
|
||||
|
||||
In the `agent_end` hook, right after the `writeSessionLog(ws, messages, config);` line (around line 515), add:
|
||||
|
||||
```javascript
|
||||
// Generate daily summary for previous day if needed
|
||||
generateDailySummary(ws, config);
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Verify no syntax errors**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity && node -c index.js
|
||||
```
|
||||
Expected: no output (syntax OK)
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add index.js openclaw.plugin.json
|
||||
git commit -m "feat: auto-generate daily summaries from session logs (v3.2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2: Weekly Summary Generation
|
||||
|
||||
**Files:**
|
||||
- Modify: `index.js` (add `generateWeeklySummary` + call in agent_end)
|
||||
|
||||
- [ ] **Step 1: Add `generateWeeklySummary` helper to `index.js`**
|
||||
|
||||
Read `index.js`. Add after the `generateDailySummary` function:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Generate a weekly summary by rolling up daily summaries.
|
||||
* Runs on Monday, summarizing the previous week (Mon-Sun).
|
||||
* Output: memory/summaries/weekly/YYYY-Www.md (ISO week number)
|
||||
*/
|
||||
function generateWeeklySummary(workspaceDir, config = {}) {
|
||||
if (config.summaryEnabled === false) return;
|
||||
|
||||
const now = new Date();
|
||||
// Only run on Mondays
|
||||
if (now.getDay() !== 1) return;
|
||||
|
||||
const pad = (n) => String(n).padStart(2, "0");
|
||||
|
||||
// Calculate previous week's Monday
|
||||
const prevMonday = new Date(now);
|
||||
prevMonday.setDate(prevMonday.getDate() - 7);
|
||||
|
||||
// ISO week number
|
||||
const jan1 = new Date(prevMonday.getFullYear(), 0, 1);
|
||||
const weekNum = Math.ceil(((prevMonday - jan1) / 86400000 + jan1.getDay() + 1) / 7);
|
||||
const weekLabel = `${prevMonday.getFullYear()}-W${pad(weekNum)}`;
|
||||
|
||||
const weeklyDir = path.join(workspaceDir, "memory", "summaries", "weekly");
|
||||
const weeklyFile = path.join(weeklyDir, `${weekLabel}.md`);
|
||||
|
||||
// Skip if already generated
|
||||
if (fs.existsSync(weeklyFile)) return;
|
||||
|
||||
// Collect daily summaries for the 7 days of previous week
|
||||
const dailyDir = path.join(workspaceDir, "memory", "summaries", "daily");
|
||||
const dailies = [];
|
||||
let weekSessions = 0;
|
||||
let weekUser = 0;
|
||||
let weekAssistant = 0;
|
||||
let weekTokens = 0;
|
||||
const allTopics = [];
|
||||
|
||||
for (let d = 0; d < 7; d++) {
|
||||
const day = new Date(prevMonday);
|
||||
day.setDate(day.getDate() + d);
|
||||
const dayStr = `${day.getFullYear()}-${pad(day.getMonth() + 1)}-${pad(day.getDate())}`;
|
||||
const dailyFile = path.join(dailyDir, `${dayStr}.md`);
|
||||
const content = readFile(dailyFile);
|
||||
if (!content) continue;
|
||||
|
||||
dailies.push(dayStr);
|
||||
|
||||
const sessMatch = content.match(/\*\*Sessions:\*\*\s*(\d+)/);
|
||||
const msgMatch = content.match(/\*\*Messages:\*\*\s*(\d+)\s*user\s*\/\s*(\d+)\s*assistant/);
|
||||
const tokenMatch = content.match(/\*\*Est\. tokens:\*\*\s*~(\d+)/);
|
||||
|
||||
if (sessMatch) weekSessions += parseInt(sessMatch[1]);
|
||||
if (msgMatch) {
|
||||
weekUser += parseInt(msgMatch[1]);
|
||||
weekAssistant += parseInt(msgMatch[2]);
|
||||
}
|
||||
if (tokenMatch) weekTokens += parseInt(tokenMatch[1]);
|
||||
|
||||
// Extract topics
|
||||
const topicSection = content.split("## Topics")[1];
|
||||
if (topicSection) {
|
||||
const topics = topicSection.match(/^\d+\.\s+(.+)$/gm);
|
||||
if (topics) allTopics.push(...topics.map(t => t.replace(/^\d+\.\s+/, "").trim()));
|
||||
}
|
||||
}
|
||||
|
||||
if (dailies.length === 0) return;
|
||||
|
||||
// Deduplicate topics (keep first occurrence)
|
||||
const seen = new Set();
|
||||
const uniqueTopics = allTopics.filter(t => {
|
||||
const key = t.toLowerCase().slice(0, 50);
|
||||
if (seen.has(key)) return false;
|
||||
seen.add(key);
|
||||
return true;
|
||||
});
|
||||
|
||||
const summary = [
|
||||
`# Weekly Summary — ${weekLabel}`,
|
||||
`> ${dailies[0]} to ${dailies[dailies.length - 1]}`,
|
||||
"",
|
||||
`**Active days:** ${dailies.length}/7`,
|
||||
`**Total sessions:** ${weekSessions}`,
|
||||
`**Total messages:** ${weekUser} user / ${weekAssistant} assistant`,
|
||||
`**Est. total tokens:** ~${weekTokens}`,
|
||||
"",
|
||||
"## Key Topics",
|
||||
...uniqueTopics.slice(0, 20).map((t, i) => `${i + 1}. ${t}`),
|
||||
"",
|
||||
].join("\n");
|
||||
|
||||
fs.mkdirSync(weeklyDir, { recursive: true });
|
||||
fs.writeFileSync(weeklyFile, summary, "utf8");
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Call `generateWeeklySummary` from `agent_end` hook**
|
||||
|
||||
Right after the `generateDailySummary(ws, config);` line, add:
|
||||
|
||||
```javascript
|
||||
generateWeeklySummary(ws, config);
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify no syntax errors**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity && node -c index.js
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add index.js
|
||||
git commit -m "feat: auto-generate weekly summaries from daily rollups (v3.2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3: Auto-Tagging — Extract `#tag` from Session Logs
|
||||
|
||||
**Files:**
|
||||
- Modify: `index.js` (add `updateTagIndex` helper + call in `writeSessionLog`)
|
||||
|
||||
- [ ] **Step 1: Add `updateTagIndex` helper to `index.js`**
|
||||
|
||||
Read `index.js`. Add after the `generateWeeklySummary` function:
|
||||
|
||||
```javascript
|
||||
/**
|
||||
* Extract #tags from a session topic and update the tag index.
|
||||
* Tags file: memory/tags.md — simple markdown index mapping tags to dates.
|
||||
*/
|
||||
function updateTagIndex(workspaceDir, topic, dateStr) {
|
||||
if (!topic) return;
|
||||
|
||||
// Extract #tags (word chars + hyphens after #)
|
||||
const tags = topic.match(/#[\w-]+/g);
|
||||
if (!tags || tags.length === 0) return;
|
||||
|
||||
const tagsFile = path.join(workspaceDir, "memory", "tags.md");
|
||||
let existing = readFile(tagsFile) || "# Tag Index\n\n";
|
||||
|
||||
for (const tag of tags) {
|
||||
const normalizedTag = tag.toLowerCase();
|
||||
// Check if this tag+date combo already exists
|
||||
if (existing.includes(`${normalizedTag}`) && existing.includes(dateStr)) continue;
|
||||
|
||||
// Find or create tag section
|
||||
const tagHeader = `## ${normalizedTag}`;
|
||||
if (existing.includes(tagHeader)) {
|
||||
// Append date to existing tag section
|
||||
existing = existing.replace(
|
||||
tagHeader,
|
||||
`${tagHeader}\n- ${dateStr}`
|
||||
);
|
||||
} else {
|
||||
// Add new tag section
|
||||
existing += `${tagHeader}\n- ${dateStr}\n\n`;
|
||||
}
|
||||
}
|
||||
|
||||
writeFile(tagsFile, existing);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Call `updateTagIndex` from `writeSessionLog`**
|
||||
|
||||
In the `writeSessionLog` function, right after the `fs.appendFileSync(logFile, prefix + entry, "utf8");` line (the last line of the function), add:
|
||||
|
||||
```javascript
|
||||
// Extract and index #tags from the topic
|
||||
updateTagIndex(workspaceDir, topic, dateStr);
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Verify no syntax errors**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity && node -c index.js
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add index.js
|
||||
git commit -m "feat: auto-extract #tags from sessions to memory/tags.md (v3.2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4: Enhanced Search — Cross Sessions + Summaries
|
||||
|
||||
**Files:**
|
||||
- Modify: `mc-plugin/index.js` (enhance `cmdSearch`)
|
||||
|
||||
- [ ] **Step 1: Enhance `cmdSearch` to search sessions and summaries**
|
||||
|
||||
Read `mc-plugin/index.js`. Replace the entire `cmdSearch` function (lines 220-279) with:
|
||||
|
||||
```javascript
|
||||
function cmdSearch(args) {
|
||||
if (!args) return "Usage: /mc search <keyword> [agent]\nSearches state, archives, sessions, and summaries.";
|
||||
|
||||
const parts = args.trim().split(/\s+/);
|
||||
let keyword, agent;
|
||||
const agents = discoverAgents();
|
||||
const agentNames = new Set(agents.map(a => a.name));
|
||||
|
||||
if (parts.length > 1 && agentNames.has(parts[parts.length - 1])) {
|
||||
agent = parts.pop();
|
||||
keyword = parts.join(" ");
|
||||
} else {
|
||||
keyword = parts.join(" ");
|
||||
agent = null;
|
||||
}
|
||||
|
||||
const re = new RegExp(keyword, "gi");
|
||||
const results = [];
|
||||
|
||||
const searchAgents = agent ? [{ name: agent, memDir: resolveMemDir(agent) }] : agents;
|
||||
|
||||
for (const { name, memDir } of searchAgents) {
|
||||
if (!memDir) continue;
|
||||
|
||||
// 1. Search current state
|
||||
const state = readFile(path.join(memDir, "CURRENT_STATE.md"));
|
||||
if (state && re.test(state)) {
|
||||
re.lastIndex = 0;
|
||||
const lines = state.split("\n").filter(l => re.test(l));
|
||||
results.push({ agent: name, source: "CURRENT_STATE", type: "state", matches: lines.slice(0, 3) });
|
||||
}
|
||||
|
||||
// 2. Search archives (most recent 30)
|
||||
const archiveDir = path.join(memDir, "session_archive");
|
||||
try {
|
||||
const files = fs.readdirSync(archiveDir).filter(f => f.endsWith(".md")).sort().reverse();
|
||||
for (const f of files.slice(0, 30)) {
|
||||
re.lastIndex = 0;
|
||||
const content = readFile(path.join(archiveDir, f));
|
||||
if (content && re.test(content)) {
|
||||
re.lastIndex = 0;
|
||||
const lines = content.split("\n").filter(l => re.test(l));
|
||||
results.push({ agent: name, source: f.replace(".md", ""), type: "archive", matches: lines.slice(0, 2) });
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// 3. Search session logs (most recent 14 days)
|
||||
const sessionsDir = path.join(memDir, "sessions");
|
||||
try {
|
||||
const files = fs.readdirSync(sessionsDir).filter(f => f.endsWith(".md")).sort().reverse();
|
||||
for (const f of files.slice(0, 14)) {
|
||||
re.lastIndex = 0;
|
||||
const content = readFile(path.join(sessionsDir, f));
|
||||
if (content && re.test(content)) {
|
||||
re.lastIndex = 0;
|
||||
const lines = content.split("\n").filter(l => re.test(l));
|
||||
results.push({ agent: name, source: f.replace(".md", ""), type: "session", matches: lines.slice(0, 2) });
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
|
||||
// 4. Search summaries (daily + weekly)
|
||||
for (const sub of ["daily", "weekly"]) {
|
||||
const sumDir = path.join(memDir, "summaries", sub);
|
||||
try {
|
||||
const files = fs.readdirSync(sumDir).filter(f => f.endsWith(".md")).sort().reverse();
|
||||
for (const f of files.slice(0, 10)) {
|
||||
re.lastIndex = 0;
|
||||
const content = readFile(path.join(sumDir, f));
|
||||
if (content && re.test(content)) {
|
||||
re.lastIndex = 0;
|
||||
const lines = content.split("\n").filter(l => re.test(l));
|
||||
results.push({ agent: name, source: f.replace(".md", ""), type: sub, matches: lines.slice(0, 2) });
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
if (!results.length) return `No matches for "${keyword}".`;
|
||||
|
||||
// Group by type for clearer output
|
||||
let out = `Search: "${keyword}" (${results.length} hits)\n`;
|
||||
out += "─────────────────────────────\n";
|
||||
|
||||
const typeOrder = ["state", "archive", "session", "daily", "weekly"];
|
||||
const typeLabels = { state: "State", archive: "Archive", session: "Session", daily: "Daily", weekly: "Weekly" };
|
||||
|
||||
for (const type of typeOrder) {
|
||||
const group = results.filter(r => r.type === type);
|
||||
if (group.length === 0) continue;
|
||||
|
||||
out += `\n[${typeLabels[type]}]\n`;
|
||||
for (const r of group.slice(0, 5)) {
|
||||
out += ` ${r.agent}/${r.source}\n`;
|
||||
for (const line of r.matches) {
|
||||
out += ` ${truncate(line.trim(), 75)}\n`;
|
||||
}
|
||||
}
|
||||
if (group.length > 5) out += ` ... +${group.length - 5} more\n`;
|
||||
}
|
||||
|
||||
return out.trimEnd();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Verify no syntax errors**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity && node -c mc-plugin/index.js
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add mc-plugin/index.js
|
||||
git commit -m "feat: enhanced search across sessions and summaries (v3.2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5: `/mc tags` and `/mc summary` Commands
|
||||
|
||||
**Files:**
|
||||
- Modify: `mc-plugin/index.js` (add two command handlers + wire switch + update help)
|
||||
|
||||
- [ ] **Step 1: Add `cmdTags` handler to `mc-plugin/index.js`**
|
||||
|
||||
Read `mc-plugin/index.js`. Add before the `cmdHelp` function:
|
||||
|
||||
```javascript
|
||||
function cmdTags(args) {
|
||||
const agent = args || "main";
|
||||
const memDir = resolveMemDir(agent);
|
||||
if (!memDir) return `Agent "${agent}" not found.`;
|
||||
|
||||
const tagsFile = path.join(memDir, "tags.md");
|
||||
const content = readFile(tagsFile);
|
||||
if (!content) return `No tags for "${agent}".`;
|
||||
|
||||
// Parse tags and their date counts
|
||||
const tagSections = content.split(/^## /gm).filter(s => s.trim());
|
||||
if (tagSections.length === 0) return `No tags for "${agent}".`;
|
||||
|
||||
let out = `Tags: ${agent} (${tagSections.length} tags)\n`;
|
||||
out += "─────────────────────────────\n";
|
||||
|
||||
const tags = [];
|
||||
for (const section of tagSections) {
|
||||
const lines = section.trim().split("\n");
|
||||
const tag = lines[0].trim();
|
||||
if (!tag.startsWith("#")) continue;
|
||||
const dates = lines.filter(l => l.startsWith("- "));
|
||||
tags.push({ tag, count: dates.length, latest: dates[0]?.replace("- ", "") || "?" });
|
||||
}
|
||||
|
||||
// Sort by count descending
|
||||
tags.sort((a, b) => b.count - a.count);
|
||||
|
||||
for (const { tag, count, latest } of tags.slice(0, 30)) {
|
||||
out += `${tag.padEnd(25)} ${String(count).padStart(3)} days latest: ${latest}\n`;
|
||||
}
|
||||
|
||||
if (tags.length > 30) out += `\n ... and ${tags.length - 30} more tags`;
|
||||
return out.trimEnd();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add `cmdSummary` handler**
|
||||
|
||||
Add after `cmdTags`:
|
||||
|
||||
```javascript
|
||||
function cmdSummary(args) {
|
||||
const parts = (args || "").trim().split(/\s+/);
|
||||
const agent = parts.find(p => !p.startsWith("-") && !/^\d{4}/.test(p) && p !== "daily" && p !== "weekly") || "main";
|
||||
const typeArg = parts.find(p => p === "daily" || p === "weekly");
|
||||
const dateArg = parts.find(p => /^\d{4}/.test(p));
|
||||
|
||||
const memDir = resolveMemDir(agent);
|
||||
if (!memDir) return `Agent "${agent}" not found.`;
|
||||
|
||||
// If specific date given, show that summary
|
||||
if (dateArg) {
|
||||
const type = typeArg || (dateArg.includes("W") ? "weekly" : "daily");
|
||||
const sumFile = path.join(memDir, "summaries", type, `${dateArg}.md`);
|
||||
const content = readFile(sumFile);
|
||||
if (!content) return `No ${type} summary for ${dateArg}.`;
|
||||
return content;
|
||||
}
|
||||
|
||||
// List available summaries
|
||||
const types = typeArg ? [typeArg] : ["daily", "weekly"];
|
||||
let out = `Summaries: ${agent}\n`;
|
||||
out += "─────────────────────────────\n";
|
||||
|
||||
for (const type of types) {
|
||||
const sumDir = path.join(memDir, "summaries", type);
|
||||
let files;
|
||||
try { files = fs.readdirSync(sumDir).filter(f => f.endsWith(".md")).sort().reverse(); }
|
||||
catch { continue; }
|
||||
|
||||
if (files.length === 0) continue;
|
||||
|
||||
out += `\n[${type.charAt(0).toUpperCase() + type.slice(1)}] (${files.length})\n`;
|
||||
for (const f of files.slice(0, 10)) {
|
||||
const name = f.replace(".md", "");
|
||||
const content = readFile(path.join(sumDir, f));
|
||||
const sessMatch = content?.match(/\*\*(?:Total s|S)essions:\*\*\s*(\d+)/);
|
||||
const sessions = sessMatch ? sessMatch[1] : "?";
|
||||
out += ` ${name} ${sessions} session(s)\n`;
|
||||
}
|
||||
if (files.length > 10) out += ` ... +${files.length - 10} more\n`;
|
||||
}
|
||||
|
||||
out += `\nUse /mc summary <YYYY-MM-DD> or /mc summary <YYYY-Www> to view details.`;
|
||||
return out.trimEnd();
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Wire `tags` and `summary` into the command switch**
|
||||
|
||||
In the switch statement, add before the `help` case:
|
||||
|
||||
```javascript
|
||||
case "tags": text = cmdTags(subargs || null); break;
|
||||
case "summary": text = cmdSummary(subargs || null); break;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Update `cmdHelp` to include new commands**
|
||||
|
||||
Add these lines after `/mc sessions [date]`:
|
||||
|
||||
```
|
||||
/mc summary [daily|weekly] List or view summaries
|
||||
/mc tags [agent] View tag index
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Verify no syntax errors**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity && node -c mc-plugin/index.js
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add mc-plugin/index.js
|
||||
git commit -m "feat: add /mc tags and /mc summary commands (v3.2)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 6: Version Bump and Final Verification
|
||||
|
||||
**Files:**
|
||||
- Modify: `openclaw.plugin.json` (version 3.1.0 → 3.2.0)
|
||||
- Modify: `package.json` (version 3.1.0 → 3.2.0)
|
||||
|
||||
- [ ] **Step 1: Bump version in both files to 3.2.0**
|
||||
|
||||
Change `"version": "3.1.0"` to `"version": "3.2.0"` in both `openclaw.plugin.json` and `package.json`.
|
||||
|
||||
- [ ] **Step 2: Verify all files parse correctly**
|
||||
|
||||
Run:
|
||||
```bash
|
||||
cd /Users/taodeng/.openclaw/projects/memory-continuity && node -c index.js && node -c mc-plugin/index.js && node -e "JSON.parse(require('fs').readFileSync('openclaw.plugin.json','utf8'))" && echo "All OK"
|
||||
```
|
||||
Expected: `All OK`
|
||||
|
||||
- [ ] **Step 3: Commit**
|
||||
|
||||
```bash
|
||||
git add openclaw.plugin.json package.json
|
||||
git commit -m "release: v3.2.0 — mid-term memory layer"
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Push to GitHub**
|
||||
|
||||
```bash
|
||||
git push
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Summary of New Features
|
||||
|
||||
| Feature | Files | Description |
|
||||
|---------|-------|-------------|
|
||||
| Daily summaries | `memory/summaries/daily/YYYY-MM-DD.md` | Auto-generated from previous day's session log |
|
||||
| Weekly summaries | `memory/summaries/weekly/YYYY-Www.md` | Rolls up daily summaries every Monday |
|
||||
| Auto-tagging | `memory/tags.md` | Extracts `#tag` from session topics, maintains index |
|
||||
| Enhanced search | `/mc search` | Now covers state + archives + sessions + summaries, grouped by type |
|
||||
| `/mc summary` | mc-plugin | List/view daily and weekly summaries |
|
||||
| `/mc tags` | mc-plugin | View tag index with counts and latest dates |
|
||||
|
||||
## New Config Keys
|
||||
|
||||
| Key | Type | Default | Purpose |
|
||||
|-----|------|---------|---------|
|
||||
| `summaryEnabled` | `boolean` | `true` | Enable/disable summary generation |
|
||||
|
||||
## New Commands
|
||||
|
||||
| Command | Purpose |
|
||||
|---------|---------|
|
||||
| `/mc summary [daily\|weekly]` | List summaries or view specific one |
|
||||
| `/mc summary <date>` | View specific daily/weekly summary |
|
||||
| `/mc tags [agent]` | View tag index |
|
||||
Reference in New Issue
Block a user