feat: add session logging to memory/sessions/ daily files (v3.1)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-31 14:29:02 +10:00
co-authored by Claude Sonnet 4.6
parent e4ac7acaed
commit 9d4968c44b
2 changed files with 75 additions and 1 deletions
+65
View File
@@ -170,6 +170,68 @@ function cleanupMemoryDir(workspaceDir) {
} catch {} } catch {}
} }
/**
* Append a session summary to the daily session log.
* File: memory/sessions/YYYY-MM-DD.md (one per day, append-only)
*/
function writeSessionLog(workspaceDir, messages, config = {}) {
if (config.sessionLogging === false) return;
if (!messages || messages.length === 0) return;
const now = new Date();
const pad = (n) => String(n).padStart(2, "0");
const dateStr = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())}`;
const timeStr = `${pad(now.getHours())}:${pad(now.getMinutes())}`;
const sessionsDir = path.join(workspaceDir, "memory", "sessions");
fs.mkdirSync(sessionsDir, { recursive: true });
const logFile = path.join(sessionsDir, `${dateStr}.md`);
// Extract first meaningful user message as topic
let topic = "(no topic)";
for (const msg of messages) {
if (msg?.role !== "user") continue;
const text = typeof msg.content === "string"
? msg.content
: Array.isArray(msg.content)
? msg.content.filter(b => b?.type === "text").map(b => b.text).join("\n")
: "";
const cleaned = text
.replace(/^Conversation info \(untrusted metadata\):[\s\S]*?\n\n/m, "")
.replace(/^Sender \(untrusted metadata\):[\s\S]*?\n\n/m, "")
.trim();
if (cleaned.length > 10) {
topic = cleaned.split("\n")[0].slice(0, 120);
break;
}
}
// Count messages by role
const userCount = messages.filter(m => m?.role === "user").length;
const assistantCount = messages.filter(m => m?.role === "assistant").length;
const totalTokens = estimateTokens(
messages.map(m => typeof m?.content === "string" ? m.content : "").join("")
);
// Build log entry
const entry = [
`### ${timeStr}`,
`- **Topic:** ${topic}`,
`- **Messages:** ${userCount} user / ${assistantCount} assistant`,
`- **Est. tokens:** ~${totalTokens}`,
"",
].join("\n");
// Append to daily log (create with header if new)
if (!fs.existsSync(logFile)) {
const header = `# Session Log — ${dateStr}\n\n`;
fs.writeFileSync(logFile, header + entry, "utf8");
} else {
fs.appendFileSync(logFile, entry, "utf8");
}
}
function extractStateFromMessages(messages) { function extractStateFromMessages(messages) {
if (!messages || messages.length === 0) return null; if (!messages || messages.length === 0) return null;
@@ -368,6 +430,9 @@ const plugin = {
} }
} }
// Write session log entry
writeSessionLog(ws, messages, config);
const existing = readFile(statePath); const existing = readFile(statePath);
const newState = extractStateFromMessages(messages); const newState = extractStateFromMessages(messages);
if (!newState) { if (!newState) {
+10 -1
View File
@@ -2,7 +2,7 @@
"id": "memory-continuity", "id": "memory-continuity",
"name": "Memory Continuity", "name": "Memory Continuity",
"description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.", "description": "Preserves working state across /new, reset, compaction, and gateway restarts via a simple markdown checkpoint file.",
"version": "3.0.0", "version": "3.1.0",
"source": "https://github.com/dtzp555-max/memory-continuity", "source": "https://github.com/dtzp555-max/memory-continuity",
"configSchema": { "configSchema": {
"type": "object", "type": "object",
@@ -32,6 +32,11 @@
"type": "array", "type": "array",
"default": [], "default": [],
"description": "Regex patterns to ignore sessions (e.g. cron jobs, subagent noise). Matched against first user message." "description": "Regex patterns to ignore sessions (e.g. cron jobs, subagent noise). Matched against first user message."
},
"sessionLogging": {
"type": "boolean",
"default": true,
"description": "Write session summaries to memory/sessions/ daily logs"
} }
} }
}, },
@@ -55,6 +60,10 @@
"ignorePatterns": { "ignorePatterns": {
"label": "Ignore patterns", "label": "Ignore patterns",
"help": "Skip state extraction for sessions matching these patterns (regex)" "help": "Skip state extraction for sessions matching these patterns (regex)"
},
"sessionLogging": {
"label": "Session logging",
"help": "Append session summaries to daily markdown logs"
} }
} }
} }