@oyasmi/pipiclaw 0.3.5 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/CHANGELOG.md +3 -0
  2. package/LICENSE +184 -0
  3. package/README.md +267 -230
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +158 -76
  6. package/dist/agent.js.map +1 -1
  7. package/dist/command-extension.d.ts.map +1 -1
  8. package/dist/command-extension.js.map +1 -1
  9. package/dist/commands.d.ts.map +1 -1
  10. package/dist/commands.js.map +1 -1
  11. package/dist/config-loader.d.ts.map +1 -1
  12. package/dist/config-loader.js.map +1 -1
  13. package/dist/context.d.ts +18 -0
  14. package/dist/context.d.ts.map +1 -1
  15. package/dist/context.js +26 -2
  16. package/dist/context.js.map +1 -1
  17. package/dist/delivery.d.ts.map +1 -1
  18. package/dist/delivery.js +11 -14
  19. package/dist/delivery.js.map +1 -1
  20. package/dist/dingtalk.d.ts.map +1 -1
  21. package/dist/dingtalk.js +26 -26
  22. package/dist/dingtalk.js.map +1 -1
  23. package/dist/events.d.ts.map +1 -1
  24. package/dist/events.js +5 -8
  25. package/dist/events.js.map +1 -1
  26. package/dist/index.d.ts +24 -0
  27. package/dist/index.d.ts.map +1 -0
  28. package/dist/index.js +24 -0
  29. package/dist/index.js.map +1 -0
  30. package/dist/llm-json.d.ts +7 -0
  31. package/dist/llm-json.d.ts.map +1 -0
  32. package/dist/llm-json.js +77 -0
  33. package/dist/llm-json.js.map +1 -0
  34. package/dist/log.d.ts.map +1 -1
  35. package/dist/log.js.map +1 -1
  36. package/dist/main.d.ts.map +1 -1
  37. package/dist/main.js.map +1 -1
  38. package/dist/markdown-sections.d.ts +6 -0
  39. package/dist/markdown-sections.d.ts.map +1 -0
  40. package/dist/markdown-sections.js +34 -0
  41. package/dist/markdown-sections.js.map +1 -0
  42. package/dist/memory-candidates.d.ts +21 -0
  43. package/dist/memory-candidates.d.ts.map +1 -0
  44. package/dist/memory-candidates.js +126 -0
  45. package/dist/memory-candidates.js.map +1 -0
  46. package/dist/memory-consolidation.d.ts.map +1 -1
  47. package/dist/memory-consolidation.js +28 -49
  48. package/dist/memory-consolidation.js.map +1 -1
  49. package/dist/memory-files.d.ts +3 -0
  50. package/dist/memory-files.d.ts.map +1 -1
  51. package/dist/memory-files.js +51 -0
  52. package/dist/memory-files.js.map +1 -1
  53. package/dist/memory-lifecycle.d.ts +9 -0
  54. package/dist/memory-lifecycle.d.ts.map +1 -1
  55. package/dist/memory-lifecycle.js +67 -2
  56. package/dist/memory-lifecycle.js.map +1 -1
  57. package/dist/memory-recall.d.ts +29 -0
  58. package/dist/memory-recall.d.ts.map +1 -0
  59. package/dist/memory-recall.js +218 -0
  60. package/dist/memory-recall.js.map +1 -0
  61. package/dist/model-utils.d.ts.map +1 -1
  62. package/dist/model-utils.js.map +1 -1
  63. package/dist/paths.d.ts.map +1 -1
  64. package/dist/prompt-builder.d.ts.map +1 -1
  65. package/dist/prompt-builder.js +7 -2
  66. package/dist/prompt-builder.js.map +1 -1
  67. package/dist/sandbox.d.ts.map +1 -1
  68. package/dist/sandbox.js +0 -1
  69. package/dist/sandbox.js.map +1 -1
  70. package/dist/session-memory-files.d.ts +2 -0
  71. package/dist/session-memory-files.d.ts.map +1 -0
  72. package/dist/session-memory-files.js +2 -0
  73. package/dist/session-memory-files.js.map +1 -0
  74. package/dist/session-memory.d.ts +22 -0
  75. package/dist/session-memory.d.ts.map +1 -0
  76. package/dist/session-memory.js +274 -0
  77. package/dist/session-memory.js.map +1 -0
  78. package/dist/shell-escape.d.ts.map +1 -1
  79. package/dist/shell-escape.js.map +1 -1
  80. package/dist/sidecar-worker.d.ts +27 -0
  81. package/dist/sidecar-worker.d.ts.map +1 -0
  82. package/dist/sidecar-worker.js +105 -0
  83. package/dist/sidecar-worker.js.map +1 -0
  84. package/dist/store.d.ts.map +1 -1
  85. package/dist/store.js +2 -3
  86. package/dist/store.js.map +1 -1
  87. package/dist/sub-agents.d.ts +10 -0
  88. package/dist/sub-agents.d.ts.map +1 -1
  89. package/dist/sub-agents.js +132 -10
  90. package/dist/sub-agents.js.map +1 -1
  91. package/dist/tools/attach.d.ts.map +1 -1
  92. package/dist/tools/attach.js.map +1 -1
  93. package/dist/tools/bash.d.ts.map +1 -1
  94. package/dist/tools/bash.js.map +1 -1
  95. package/dist/tools/edit.d.ts.map +1 -1
  96. package/dist/tools/edit.js.map +1 -1
  97. package/dist/tools/index.d.ts +3 -0
  98. package/dist/tools/index.d.ts.map +1 -1
  99. package/dist/tools/index.js +2 -0
  100. package/dist/tools/index.js.map +1 -1
  101. package/dist/tools/read.d.ts.map +1 -1
  102. package/dist/tools/read.js.map +1 -1
  103. package/dist/tools/subagent.d.ts +6 -0
  104. package/dist/tools/subagent.d.ts.map +1 -1
  105. package/dist/tools/subagent.js +127 -12
  106. package/dist/tools/subagent.js.map +1 -1
  107. package/dist/tools/truncate.d.ts.map +1 -1
  108. package/dist/tools/truncate.js.map +1 -1
  109. package/dist/tools/write-content.d.ts.map +1 -1
  110. package/dist/tools/write-content.js.map +1 -1
  111. package/dist/tools/write.d.ts.map +1 -1
  112. package/dist/tools/write.js.map +1 -1
  113. package/docs/improve-memory/design.md +537 -0
  114. package/docs/improve-memory/interfaces-and-tests.md +473 -0
  115. package/docs/improve-memory/spec.md +357 -0
  116. package/docs/memory-rfc.md +297 -0
  117. package/docs/proj-review.md +188 -0
  118. package/docs/subagent/pi-subagent-analyse.txt +190 -0
  119. package/docs/subagent/pi-subagent-design.txt +266 -0
  120. package/docs/subagent/pi-subagent-phase1-plan.txt +529 -0
  121. package/docs/test-supplementation-plan.md +553 -0
  122. package/package.json +71 -53
@@ -1,12 +1,16 @@
1
- import { Agent } from "@mariozechner/pi-agent-core";
2
- import { convertToLlm, getLatestCompactionEntry, serializeConversation, } from "@mariozechner/pi-coding-agent";
3
- import { appendChannelHistoryBlock, appendChannelMemoryUpdate, readChannelHistory, readChannelMemory, rewriteChannelHistory, rewriteChannelMemory, splitMarkdownSections, } from "./memory-files.js";
1
+ import { getLatestCompactionEntry, serializeConversation, } from "@mariozechner/pi-coding-agent";
2
+ import { parseJsonObject } from "./llm-json.js";
3
+ import { appendChannelHistoryBlock, appendChannelMemoryUpdate, readChannelHistory, readChannelMemory, readChannelSession, rewriteChannelHistory, rewriteChannelMemory, splitMarkdownSections, } from "./memory-files.js";
4
+ import { runSidecarTask } from "./sidecar-worker.js";
4
5
  const INLINE_TRANSCRIPT_MAX_CHARS = 28_000;
5
6
  const MEMORY_CLEANUP_LENGTH_THRESHOLD = 8_000;
6
7
  const MEMORY_UPDATE_BLOCK_THRESHOLD = 6;
7
8
  const HISTORY_LENGTH_THRESHOLD = 16_000;
8
9
  const HISTORY_BLOCK_THRESHOLD = 8;
9
10
  const HISTORY_RECENT_BLOCKS_TO_KEEP = 4;
11
+ const INLINE_CONSOLIDATION_TIMEOUT_MS = 20_000;
12
+ const MEMORY_CLEANUP_TIMEOUT_MS = 30_000;
13
+ const HISTORY_FOLDING_TIMEOUT_MS = 30_000;
10
14
  const INLINE_CONSOLIDATION_SYSTEM_PROMPT = `You are a runtime memory consolidation worker for Pipiclaw.
11
15
 
12
16
  Return strict JSON only. Do not wrap in Markdown fences.
@@ -22,6 +26,7 @@ Rules:
22
26
  - Each memoryEntries item must be a standalone sentence fragment suitable for a Markdown bullet without the bullet prefix.
23
27
  - Do not include raw transcript quotes unless essential.
24
28
  - Do not include ephemeral chatter, obvious one-shot acknowledgements, or formatting instructions.
29
+ - Prefer leaving highly volatile step-by-step execution state in SESSION.md rather than promoting it into durable memory.
25
30
  - historyBlock: concise Markdown summarizing the conversation chunk for later recovery.
26
31
  - Prefer short bullets and short paragraphs.
27
32
  - If there is nothing worth storing, return empty values.`;
@@ -34,6 +39,7 @@ Goals:
34
39
  - Remove outdated entries, duplicates, and verbose phrasing.
35
40
  - Organize the result with stable sections where relevant.
36
41
  - Prefer concise bullets over prose.
42
+ - Remove content that is clearly transient session-state and belongs in SESSION.md instead.
37
43
 
38
44
  Suggested sections:
39
45
  - ## Identity / Participants
@@ -65,24 +71,8 @@ function clipTranscript(text, maxChars) {
65
71
  const tailChars = maxChars - headChars;
66
72
  return `${normalized.slice(0, headChars)}\n\n[... omitted middle section ...]\n\n${normalized.slice(-tailChars)}`;
67
73
  }
68
- function extractJsonObject(text) {
69
- const trimmed = text.trim();
70
- if (trimmed.startsWith("{") && trimmed.endsWith("}")) {
71
- return trimmed;
72
- }
73
- const fenceMatch = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i);
74
- if (fenceMatch?.[1]) {
75
- return fenceMatch[1].trim();
76
- }
77
- const firstBrace = trimmed.indexOf("{");
78
- const lastBrace = trimmed.lastIndexOf("}");
79
- if (firstBrace >= 0 && lastBrace > firstBrace) {
80
- return trimmed.slice(firstBrace, lastBrace + 1);
81
- }
82
- return trimmed;
83
- }
84
74
  function parseConsolidationResponse(text) {
85
- const parsed = JSON.parse(extractJsonObject(text));
75
+ const parsed = parseJsonObject(text);
86
76
  return {
87
77
  memoryEntries: Array.isArray(parsed.memoryEntries)
88
78
  ? parsed.memoryEntries
@@ -142,38 +132,27 @@ function hasMeaningfulMessages(messages) {
142
132
  function countMatchingSectionHeadings(content, prefix) {
143
133
  return splitMarkdownSections(content).filter((section) => section.heading.startsWith(prefix)).length;
144
134
  }
145
- async function runWorkerPrompt(model, resolveApiKey, systemPrompt, prompt) {
146
- const apiKey = await resolveApiKey(model);
147
- const worker = new Agent({
148
- initialState: {
149
- systemPrompt,
150
- model,
151
- thinkingLevel: "off",
152
- tools: [],
153
- },
154
- convertToLlm,
155
- getApiKey: async () => apiKey,
135
+ async function runWorkerPrompt(name, model, resolveApiKey, systemPrompt, prompt, timeoutMs) {
136
+ const result = await runSidecarTask({
137
+ name,
138
+ model,
139
+ resolveApiKey,
140
+ systemPrompt,
141
+ prompt,
142
+ timeoutMs,
143
+ parse: (text) => text.trim(),
156
144
  });
157
- await worker.prompt(prompt);
158
- await worker.waitForIdle();
159
- const lastMessage = worker.state.messages[worker.state.messages.length - 1];
160
- if (!lastMessage || lastMessage.role !== "assistant") {
161
- throw new Error("Consolidation worker returned no assistant message");
162
- }
163
- if (lastMessage.stopReason === "error" || lastMessage.stopReason === "aborted") {
164
- throw new Error(lastMessage.errorMessage || "Consolidation worker failed");
165
- }
166
- return lastMessage.content
167
- .filter((part) => part.type === "text")
168
- .map((part) => part.text)
169
- .join("\n")
170
- .trim();
145
+ return result.output;
171
146
  }
172
147
  async function buildInlineConsolidationResponse(options, messages) {
173
148
  const transcript = clipTranscript(serializeConversation(messages), INLINE_TRANSCRIPT_MAX_CHARS);
174
149
  const currentMemory = clipTranscript(await readChannelMemory(options.channelDir), 8_000);
150
+ const currentSession = clipTranscript(await readChannelSession(options.channelDir), 8_000);
175
151
  const currentHistory = clipTranscript(await readChannelHistory(options.channelDir), 8_000);
176
- const prompt = `Channel memory file:
152
+ const prompt = `Current SESSION.md:
153
+ ${currentSession || "(empty)"}
154
+
155
+ Channel memory file:
177
156
  ${currentMemory || "(empty)"}
178
157
 
179
158
  Channel history file:
@@ -181,7 +160,7 @@ ${currentHistory || "(empty)"}
181
160
 
182
161
  Conversation chunk to persist:
183
162
  ${transcript || "(empty)"}`;
184
- const rawResponse = await runWorkerPrompt(options.model, options.resolveApiKey, INLINE_CONSOLIDATION_SYSTEM_PROMPT, prompt);
163
+ const rawResponse = await runWorkerPrompt("memory-inline-consolidation", options.model, options.resolveApiKey, INLINE_CONSOLIDATION_SYSTEM_PROMPT, prompt, INLINE_CONSOLIDATION_TIMEOUT_MS);
185
164
  return parseConsolidationResponse(rawResponse);
186
165
  }
187
166
  export async function runInlineConsolidation(options) {
@@ -218,7 +197,7 @@ async function cleanupChannelMemory(options, currentMemory) {
218
197
  }
219
198
  const prompt = `Current MEMORY.md:
220
199
  ${currentMemory}`;
221
- const nextMemory = await runWorkerPrompt(options.model, options.resolveApiKey, MEMORY_CLEANUP_SYSTEM_PROMPT, prompt);
200
+ const nextMemory = await runWorkerPrompt("memory-cleanup", options.model, options.resolveApiKey, MEMORY_CLEANUP_SYSTEM_PROMPT, prompt, MEMORY_CLEANUP_TIMEOUT_MS);
222
201
  await rewriteChannelMemory(options.channelDir, nextMemory);
223
202
  return true;
224
203
  }
@@ -234,7 +213,7 @@ async function foldChannelHistory(options, currentHistory) {
234
213
  const recentSections = sections.slice(-HISTORY_RECENT_BLOCKS_TO_KEEP);
235
214
  const prompt = `Older history blocks to fold:
236
215
  ${olderSections.map((section) => `## ${section.heading}\n\n${section.content}`).join("\n\n")}`;
237
- const foldedSummary = await runWorkerPrompt(options.model, options.resolveApiKey, HISTORY_FOLDING_SYSTEM_PROMPT, prompt);
216
+ const foldedSummary = await runWorkerPrompt("history-folding", options.model, options.resolveApiKey, HISTORY_FOLDING_SYSTEM_PROMPT, prompt, HISTORY_FOLDING_TIMEOUT_MS);
238
217
  const foldedHeading = `## Folded History Through ${olderSections[olderSections.length - 1]?.heading ?? new Date().toISOString()}`;
239
218
  const rebuiltHistory = [
240
219
  "# Channel History",
@@ -1 +1 @@
1
- {"version":3,"file":"memory-consolidation.js","sourceRoot":"","sources":["../src/memory-consolidation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport { Agent } from \"@mariozechner/pi-agent-core\";\nimport type { Api, AssistantMessage, Message, Model } from \"@mariozechner/pi-ai\";\nimport {\n\tconvertToLlm,\n\tgetLatestCompactionEntry,\n\ttype SessionEntry,\n\ttype SessionMessageEntry,\n\tserializeConversation,\n} from \"@mariozechner/pi-coding-agent\";\nimport {\n\tappendChannelHistoryBlock,\n\tappendChannelMemoryUpdate,\n\treadChannelHistory,\n\treadChannelMemory,\n\trewriteChannelHistory,\n\trewriteChannelMemory,\n\tsplitMarkdownSections,\n} from \"./memory-files.js\";\n\nconst INLINE_TRANSCRIPT_MAX_CHARS = 28_000;\nconst MEMORY_CLEANUP_LENGTH_THRESHOLD = 8_000;\nconst MEMORY_UPDATE_BLOCK_THRESHOLD = 6;\nconst HISTORY_LENGTH_THRESHOLD = 16_000;\nconst HISTORY_BLOCK_THRESHOLD = 8;\nconst HISTORY_RECENT_BLOCKS_TO_KEEP = 4;\n\nconst INLINE_CONSOLIDATION_SYSTEM_PROMPT = `You are a runtime memory consolidation worker for Pipiclaw.\n\nReturn strict JSON only. Do not wrap in Markdown fences.\n\nOutput schema:\n{\n \"memoryEntries\": [\"string\"],\n \"historyBlock\": \"string\"\n}\n\nRules:\n- memoryEntries: concise durable facts, decisions, preferences, constraints, current work state, or open loops that should survive compaction.\n- Each memoryEntries item must be a standalone sentence fragment suitable for a Markdown bullet without the bullet prefix.\n- Do not include raw transcript quotes unless essential.\n- Do not include ephemeral chatter, obvious one-shot acknowledgements, or formatting instructions.\n- historyBlock: concise Markdown summarizing the conversation chunk for later recovery.\n- Prefer short bullets and short paragraphs.\n- If there is nothing worth storing, return empty values.`;\n\nconst MEMORY_CLEANUP_SYSTEM_PROMPT = `You are rewriting a Pipiclaw channel MEMORY.md file.\n\nReturn Markdown only. Do not use code fences.\n\nGoals:\n- Keep only durable and useful channel memory.\n- Remove outdated entries, duplicates, and verbose phrasing.\n- Organize the result with stable sections where relevant.\n- Prefer concise bullets over prose.\n\nSuggested sections:\n- ## Identity / Participants\n- ## Preferences\n- ## Ongoing Work\n- ## Constraints\n- ## Decisions\n- ## Open Loops\n\nOmit empty sections.`;\n\nconst HISTORY_FOLDING_SYSTEM_PROMPT = `You are folding older HISTORY.md blocks for Pipiclaw.\n\nReturn Markdown only. Do not use code fences.\n\nGoals:\n- Compress older history blocks into one concise summary block.\n- Keep important decisions, milestones, and unresolved outcomes.\n- Remove redundancy and transcript-like detail.\n- Preserve a chronological narrative at a high level.`;\n\nexport interface ConsolidationRunOptions {\n\tchannelDir: string;\n\tmodel: Model<Api>;\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tmessages: AgentMessage[];\n\tsessionEntries?: SessionEntry[];\n}\n\nexport interface InlineConsolidationResult {\n\tskipped: boolean;\n\tappendedMemoryEntries: number;\n\tappendedHistoryBlock: boolean;\n}\n\nexport interface BackgroundMaintenanceResult {\n\tcleanedMemory: boolean;\n\tfoldedHistory: boolean;\n}\n\ninterface ConsolidationResponse {\n\tmemoryEntries: string[];\n\thistoryBlock: string;\n}\n\nfunction normalizeText(text: string): string {\n\treturn text.replace(/\\r/g, \"\").trim();\n}\n\nfunction clipTranscript(text: string, maxChars: number): string {\n\tconst normalized = normalizeText(text);\n\tif (normalized.length <= maxChars) {\n\t\treturn normalized;\n\t}\n\n\tconst headChars = Math.floor(maxChars * 0.35);\n\tconst tailChars = maxChars - headChars;\n\treturn `${normalized.slice(0, headChars)}\\n\\n[... omitted middle section ...]\\n\\n${normalized.slice(-tailChars)}`;\n}\n\nfunction extractJsonObject(text: string): string {\n\tconst trimmed = text.trim();\n\tif (trimmed.startsWith(\"{\") && trimmed.endsWith(\"}\")) {\n\t\treturn trimmed;\n\t}\n\n\tconst fenceMatch = trimmed.match(/```(?:json)?\\s*([\\s\\S]*?)```/i);\n\tif (fenceMatch?.[1]) {\n\t\treturn fenceMatch[1].trim();\n\t}\n\n\tconst firstBrace = trimmed.indexOf(\"{\");\n\tconst lastBrace = trimmed.lastIndexOf(\"}\");\n\tif (firstBrace >= 0 && lastBrace > firstBrace) {\n\t\treturn trimmed.slice(firstBrace, lastBrace + 1);\n\t}\n\n\treturn trimmed;\n}\n\nfunction parseConsolidationResponse(text: string): ConsolidationResponse {\n\tconst parsed = JSON.parse(extractJsonObject(text)) as Partial<ConsolidationResponse>;\n\treturn {\n\t\tmemoryEntries: Array.isArray(parsed.memoryEntries)\n\t\t\t? parsed.memoryEntries\n\t\t\t\t\t.map((entry) => (typeof entry === \"string\" ? entry.trim() : \"\"))\n\t\t\t\t\t.filter((entry) => entry.length > 0)\n\t\t\t: [],\n\t\thistoryBlock: typeof parsed.historyBlock === \"string\" ? parsed.historyBlock.trim() : \"\",\n\t};\n}\n\nfunction getLatestCompactionBoundary(entries: SessionEntry[]): number {\n\tconst latestCompaction = getLatestCompactionEntry(entries);\n\tif (!latestCompaction) {\n\t\treturn 0;\n\t}\n\n\tconst boundaryIndex = entries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId);\n\treturn boundaryIndex >= 0 ? boundaryIndex : 0;\n}\n\nfunction isMessage(entry: SessionEntry): entry is SessionMessageEntry {\n\treturn entry.type === \"message\";\n}\n\nfunction isStandardAgentMessage(message: AgentMessage): message is Message {\n\treturn (\n\t\ttypeof message === \"object\" &&\n\t\tmessage !== null &&\n\t\t\"role\" in message &&\n\t\t(message.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\")\n\t);\n}\n\nfunction buildMessagesForConsolidation(messages: AgentMessage[]): Message[] {\n\treturn messages.filter(isStandardAgentMessage);\n}\n\nfunction extractMessagesFromSessionEntries(entries: SessionEntry[]): AgentMessage[] {\n\treturn entries.filter(isMessage).map((entry) => entry.message);\n}\n\nfunction hasMeaningfulMessages(messages: Message[]): boolean {\n\tlet meaningfulCount = 0;\n\tfor (const message of messages) {\n\t\tif (message.role === \"user\") {\n\t\t\tconst text =\n\t\t\t\ttypeof message.content === \"string\"\n\t\t\t\t\t? message.content\n\t\t\t\t\t: message.content.map((part) => (part.type === \"text\" ? part.text : \"[image]\")).join(\"\\n\");\n\t\t\tif (text.trim()) meaningfulCount++;\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst text = message.content\n\t\t\t\t.filter(\n\t\t\t\t\t(part): part is Extract<AssistantMessage[\"content\"][number], { type: \"text\" }> => part.type === \"text\",\n\t\t\t\t)\n\t\t\t\t.map((part) => part.text)\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (text.trim()) meaningfulCount++;\n\t\t}\n\t\tif (meaningfulCount >= 2) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction countMatchingSectionHeadings(content: string, prefix: string): number {\n\treturn splitMarkdownSections(content).filter((section) => section.heading.startsWith(prefix)).length;\n}\n\nasync function runWorkerPrompt(\n\tmodel: Model<Api>,\n\tresolveApiKey: (model: Model<Api>) => Promise<string>,\n\tsystemPrompt: string,\n\tprompt: string,\n): Promise<string> {\n\tconst apiKey = await resolveApiKey(model);\n\tconst worker = new Agent({\n\t\tinitialState: {\n\t\t\tsystemPrompt,\n\t\t\tmodel,\n\t\t\tthinkingLevel: \"off\",\n\t\t\ttools: [],\n\t\t},\n\t\tconvertToLlm,\n\t\tgetApiKey: async () => apiKey,\n\t});\n\n\tawait worker.prompt(prompt);\n\tawait worker.waitForIdle();\n\n\tconst lastMessage = worker.state.messages[worker.state.messages.length - 1];\n\tif (!lastMessage || lastMessage.role !== \"assistant\") {\n\t\tthrow new Error(\"Consolidation worker returned no assistant message\");\n\t}\n\n\tif (lastMessage.stopReason === \"error\" || lastMessage.stopReason === \"aborted\") {\n\t\tthrow new Error(lastMessage.errorMessage || \"Consolidation worker failed\");\n\t}\n\n\treturn lastMessage.content\n\t\t.filter((part): part is Extract<AssistantMessage[\"content\"][number], { type: \"text\" }> => part.type === \"text\")\n\t\t.map((part) => part.text)\n\t\t.join(\"\\n\")\n\t\t.trim();\n}\n\nasync function buildInlineConsolidationResponse(\n\toptions: ConsolidationRunOptions,\n\tmessages: Message[],\n): Promise<ConsolidationResponse> {\n\tconst transcript = clipTranscript(serializeConversation(messages), INLINE_TRANSCRIPT_MAX_CHARS);\n\tconst currentMemory = clipTranscript(await readChannelMemory(options.channelDir), 8_000);\n\tconst currentHistory = clipTranscript(await readChannelHistory(options.channelDir), 8_000);\n\n\tconst prompt = `Channel memory file:\n${currentMemory || \"(empty)\"}\n\nChannel history file:\n${currentHistory || \"(empty)\"}\n\nConversation chunk to persist:\n${transcript || \"(empty)\"}`;\n\n\tconst rawResponse = await runWorkerPrompt(\n\t\toptions.model,\n\t\toptions.resolveApiKey,\n\t\tINLINE_CONSOLIDATION_SYSTEM_PROMPT,\n\t\tprompt,\n\t);\n\treturn parseConsolidationResponse(rawResponse);\n}\n\nexport async function runInlineConsolidation(options: ConsolidationRunOptions): Promise<InlineConsolidationResult> {\n\tconst sourceEntries = options.sessionEntries ?? [];\n\tconst relevantEntries =\n\t\tsourceEntries.length > 0 ? sourceEntries.slice(getLatestCompactionBoundary(sourceEntries)) : sourceEntries;\n\tconst relevantMessages = buildMessagesForConsolidation(\n\t\trelevantEntries.length > 0 ? extractMessagesFromSessionEntries(relevantEntries) : options.messages,\n\t);\n\n\tif (!hasMeaningfulMessages(relevantMessages)) {\n\t\treturn { skipped: true, appendedMemoryEntries: 0, appendedHistoryBlock: false };\n\t}\n\n\tconst response = await buildInlineConsolidationResponse(options, relevantMessages);\n\tconst timestamp = new Date().toISOString();\n\n\tif (response.memoryEntries.length > 0) {\n\t\tawait appendChannelMemoryUpdate(options.channelDir, {\n\t\t\ttimestamp,\n\t\t\tentries: response.memoryEntries,\n\t\t});\n\t}\n\n\tif (response.historyBlock.trim()) {\n\t\tawait appendChannelHistoryBlock(options.channelDir, {\n\t\t\ttimestamp,\n\t\t\tcontent: response.historyBlock,\n\t\t});\n\t}\n\n\treturn {\n\t\tskipped: false,\n\t\tappendedMemoryEntries: response.memoryEntries.length,\n\t\tappendedHistoryBlock: response.historyBlock.trim().length > 0,\n\t};\n}\n\nasync function cleanupChannelMemory(options: ConsolidationRunOptions, currentMemory: string): Promise<boolean> {\n\tif (\n\t\tcurrentMemory.length < MEMORY_CLEANUP_LENGTH_THRESHOLD &&\n\t\tcountMatchingSectionHeadings(currentMemory, \"Update \") < MEMORY_UPDATE_BLOCK_THRESHOLD\n\t) {\n\t\treturn false;\n\t}\n\n\tconst prompt = `Current MEMORY.md:\n${currentMemory}`;\n\tconst nextMemory = await runWorkerPrompt(options.model, options.resolveApiKey, MEMORY_CLEANUP_SYSTEM_PROMPT, prompt);\n\tawait rewriteChannelMemory(options.channelDir, nextMemory);\n\treturn true;\n}\n\nasync function foldChannelHistory(options: ConsolidationRunOptions, currentHistory: string): Promise<boolean> {\n\tconst sections = splitMarkdownSections(currentHistory);\n\tif (currentHistory.length < HISTORY_LENGTH_THRESHOLD && sections.length < HISTORY_BLOCK_THRESHOLD) {\n\t\treturn false;\n\t}\n\n\tif (sections.length <= HISTORY_RECENT_BLOCKS_TO_KEEP) {\n\t\treturn false;\n\t}\n\n\tconst olderSections = sections.slice(0, -HISTORY_RECENT_BLOCKS_TO_KEEP);\n\tconst recentSections = sections.slice(-HISTORY_RECENT_BLOCKS_TO_KEEP);\n\tconst prompt = `Older history blocks to fold:\n${olderSections.map((section) => `## ${section.heading}\\n\\n${section.content}`).join(\"\\n\\n\")}`;\n\tconst foldedSummary = await runWorkerPrompt(\n\t\toptions.model,\n\t\toptions.resolveApiKey,\n\t\tHISTORY_FOLDING_SYSTEM_PROMPT,\n\t\tprompt,\n\t);\n\n\tconst foldedHeading = `## Folded History Through ${olderSections[olderSections.length - 1]?.heading ?? new Date().toISOString()}`;\n\tconst rebuiltHistory = [\n\t\t\"# Channel History\",\n\t\t\"\",\n\t\tfoldedHeading,\n\t\t\"\",\n\t\tnormalizeText(foldedSummary),\n\t\t\"\",\n\t\t...recentSections.flatMap((section) => [`## ${section.heading}`, \"\", normalizeText(section.content), \"\"]),\n\t].join(\"\\n\");\n\n\tawait rewriteChannelHistory(options.channelDir, rebuiltHistory);\n\treturn true;\n}\n\nexport async function runBackgroundMaintenance(options: ConsolidationRunOptions): Promise<BackgroundMaintenanceResult> {\n\tconst currentMemory = await readChannelMemory(options.channelDir);\n\tconst currentHistory = await readChannelHistory(options.channelDir);\n\n\tconst cleanedMemory = await cleanupChannelMemory(options, currentMemory);\n\tconst foldedHistory = await foldChannelHistory(options, currentHistory);\n\n\treturn { cleanedMemory, foldedHistory };\n}\n"]}
1
+ {"version":3,"file":"memory-consolidation.js","sourceRoot":"","sources":["../src/memory-consolidation.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { Api, AssistantMessage, Message, Model } from \"@mariozechner/pi-ai\";\nimport {\n\tgetLatestCompactionEntry,\n\ttype SessionEntry,\n\ttype SessionMessageEntry,\n\tserializeConversation,\n} from \"@mariozechner/pi-coding-agent\";\nimport { parseJsonObject } from \"./llm-json.js\";\nimport {\n\tappendChannelHistoryBlock,\n\tappendChannelMemoryUpdate,\n\treadChannelHistory,\n\treadChannelMemory,\n\treadChannelSession,\n\trewriteChannelHistory,\n\trewriteChannelMemory,\n\tsplitMarkdownSections,\n} from \"./memory-files.js\";\nimport { runSidecarTask } from \"./sidecar-worker.js\";\n\nconst INLINE_TRANSCRIPT_MAX_CHARS = 28_000;\nconst MEMORY_CLEANUP_LENGTH_THRESHOLD = 8_000;\nconst MEMORY_UPDATE_BLOCK_THRESHOLD = 6;\nconst HISTORY_LENGTH_THRESHOLD = 16_000;\nconst HISTORY_BLOCK_THRESHOLD = 8;\nconst HISTORY_RECENT_BLOCKS_TO_KEEP = 4;\nconst INLINE_CONSOLIDATION_TIMEOUT_MS = 20_000;\nconst MEMORY_CLEANUP_TIMEOUT_MS = 30_000;\nconst HISTORY_FOLDING_TIMEOUT_MS = 30_000;\n\nconst INLINE_CONSOLIDATION_SYSTEM_PROMPT = `You are a runtime memory consolidation worker for Pipiclaw.\n\nReturn strict JSON only. Do not wrap in Markdown fences.\n\nOutput schema:\n{\n \"memoryEntries\": [\"string\"],\n \"historyBlock\": \"string\"\n}\n\nRules:\n- memoryEntries: concise durable facts, decisions, preferences, constraints, current work state, or open loops that should survive compaction.\n- Each memoryEntries item must be a standalone sentence fragment suitable for a Markdown bullet without the bullet prefix.\n- Do not include raw transcript quotes unless essential.\n- Do not include ephemeral chatter, obvious one-shot acknowledgements, or formatting instructions.\n- Prefer leaving highly volatile step-by-step execution state in SESSION.md rather than promoting it into durable memory.\n- historyBlock: concise Markdown summarizing the conversation chunk for later recovery.\n- Prefer short bullets and short paragraphs.\n- If there is nothing worth storing, return empty values.`;\n\nconst MEMORY_CLEANUP_SYSTEM_PROMPT = `You are rewriting a Pipiclaw channel MEMORY.md file.\n\nReturn Markdown only. Do not use code fences.\n\nGoals:\n- Keep only durable and useful channel memory.\n- Remove outdated entries, duplicates, and verbose phrasing.\n- Organize the result with stable sections where relevant.\n- Prefer concise bullets over prose.\n- Remove content that is clearly transient session-state and belongs in SESSION.md instead.\n\nSuggested sections:\n- ## Identity / Participants\n- ## Preferences\n- ## Ongoing Work\n- ## Constraints\n- ## Decisions\n- ## Open Loops\n\nOmit empty sections.`;\n\nconst HISTORY_FOLDING_SYSTEM_PROMPT = `You are folding older HISTORY.md blocks for Pipiclaw.\n\nReturn Markdown only. Do not use code fences.\n\nGoals:\n- Compress older history blocks into one concise summary block.\n- Keep important decisions, milestones, and unresolved outcomes.\n- Remove redundancy and transcript-like detail.\n- Preserve a chronological narrative at a high level.`;\n\nexport interface ConsolidationRunOptions {\n\tchannelDir: string;\n\tmodel: Model<Api>;\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tmessages: AgentMessage[];\n\tsessionEntries?: SessionEntry[];\n}\n\nexport interface InlineConsolidationResult {\n\tskipped: boolean;\n\tappendedMemoryEntries: number;\n\tappendedHistoryBlock: boolean;\n}\n\nexport interface BackgroundMaintenanceResult {\n\tcleanedMemory: boolean;\n\tfoldedHistory: boolean;\n}\n\ninterface ConsolidationResponse {\n\tmemoryEntries: string[];\n\thistoryBlock: string;\n}\n\nfunction normalizeText(text: string): string {\n\treturn text.replace(/\\r/g, \"\").trim();\n}\n\nfunction clipTranscript(text: string, maxChars: number): string {\n\tconst normalized = normalizeText(text);\n\tif (normalized.length <= maxChars) {\n\t\treturn normalized;\n\t}\n\n\tconst headChars = Math.floor(maxChars * 0.35);\n\tconst tailChars = maxChars - headChars;\n\treturn `${normalized.slice(0, headChars)}\\n\\n[... omitted middle section ...]\\n\\n${normalized.slice(-tailChars)}`;\n}\n\nfunction parseConsolidationResponse(text: string): ConsolidationResponse {\n\tconst parsed = parseJsonObject(text) as Partial<ConsolidationResponse>;\n\treturn {\n\t\tmemoryEntries: Array.isArray(parsed.memoryEntries)\n\t\t\t? parsed.memoryEntries\n\t\t\t\t\t.map((entry) => (typeof entry === \"string\" ? entry.trim() : \"\"))\n\t\t\t\t\t.filter((entry) => entry.length > 0)\n\t\t\t: [],\n\t\thistoryBlock: typeof parsed.historyBlock === \"string\" ? parsed.historyBlock.trim() : \"\",\n\t};\n}\n\nfunction getLatestCompactionBoundary(entries: SessionEntry[]): number {\n\tconst latestCompaction = getLatestCompactionEntry(entries);\n\tif (!latestCompaction) {\n\t\treturn 0;\n\t}\n\n\tconst boundaryIndex = entries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId);\n\treturn boundaryIndex >= 0 ? boundaryIndex : 0;\n}\n\nfunction isMessage(entry: SessionEntry): entry is SessionMessageEntry {\n\treturn entry.type === \"message\";\n}\n\nfunction isStandardAgentMessage(message: AgentMessage): message is Message {\n\treturn (\n\t\ttypeof message === \"object\" &&\n\t\tmessage !== null &&\n\t\t\"role\" in message &&\n\t\t(message.role === \"user\" || message.role === \"assistant\" || message.role === \"toolResult\")\n\t);\n}\n\nfunction buildMessagesForConsolidation(messages: AgentMessage[]): Message[] {\n\treturn messages.filter(isStandardAgentMessage);\n}\n\nfunction extractMessagesFromSessionEntries(entries: SessionEntry[]): AgentMessage[] {\n\treturn entries.filter(isMessage).map((entry) => entry.message);\n}\n\nfunction hasMeaningfulMessages(messages: Message[]): boolean {\n\tlet meaningfulCount = 0;\n\tfor (const message of messages) {\n\t\tif (message.role === \"user\") {\n\t\t\tconst text =\n\t\t\t\ttypeof message.content === \"string\"\n\t\t\t\t\t? message.content\n\t\t\t\t\t: message.content.map((part) => (part.type === \"text\" ? part.text : \"[image]\")).join(\"\\n\");\n\t\t\tif (text.trim()) meaningfulCount++;\n\t\t} else if (message.role === \"assistant\") {\n\t\t\tconst text = message.content\n\t\t\t\t.filter(\n\t\t\t\t\t(part): part is Extract<AssistantMessage[\"content\"][number], { type: \"text\" }> => part.type === \"text\",\n\t\t\t\t)\n\t\t\t\t.map((part) => part.text)\n\t\t\t\t.join(\"\\n\");\n\t\t\tif (text.trim()) meaningfulCount++;\n\t\t}\n\t\tif (meaningfulCount >= 2) {\n\t\t\treturn true;\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction countMatchingSectionHeadings(content: string, prefix: string): number {\n\treturn splitMarkdownSections(content).filter((section) => section.heading.startsWith(prefix)).length;\n}\n\nasync function runWorkerPrompt(\n\tname: string,\n\tmodel: Model<Api>,\n\tresolveApiKey: (model: Model<Api>) => Promise<string>,\n\tsystemPrompt: string,\n\tprompt: string,\n\ttimeoutMs: number,\n): Promise<string> {\n\tconst result = await runSidecarTask({\n\t\tname,\n\t\tmodel,\n\t\tresolveApiKey,\n\t\tsystemPrompt,\n\t\tprompt,\n\t\ttimeoutMs,\n\t\tparse: (text) => text.trim(),\n\t});\n\treturn result.output;\n}\n\nasync function buildInlineConsolidationResponse(\n\toptions: ConsolidationRunOptions,\n\tmessages: Message[],\n): Promise<ConsolidationResponse> {\n\tconst transcript = clipTranscript(serializeConversation(messages), INLINE_TRANSCRIPT_MAX_CHARS);\n\tconst currentMemory = clipTranscript(await readChannelMemory(options.channelDir), 8_000);\n\tconst currentSession = clipTranscript(await readChannelSession(options.channelDir), 8_000);\n\tconst currentHistory = clipTranscript(await readChannelHistory(options.channelDir), 8_000);\n\n\tconst prompt = `Current SESSION.md:\n${currentSession || \"(empty)\"}\n\nChannel memory file:\n${currentMemory || \"(empty)\"}\n\nChannel history file:\n${currentHistory || \"(empty)\"}\n\nConversation chunk to persist:\n${transcript || \"(empty)\"}`;\n\n\tconst rawResponse = await runWorkerPrompt(\n\t\t\"memory-inline-consolidation\",\n\t\toptions.model,\n\t\toptions.resolveApiKey,\n\t\tINLINE_CONSOLIDATION_SYSTEM_PROMPT,\n\t\tprompt,\n\t\tINLINE_CONSOLIDATION_TIMEOUT_MS,\n\t);\n\treturn parseConsolidationResponse(rawResponse);\n}\n\nexport async function runInlineConsolidation(options: ConsolidationRunOptions): Promise<InlineConsolidationResult> {\n\tconst sourceEntries = options.sessionEntries ?? [];\n\tconst relevantEntries =\n\t\tsourceEntries.length > 0 ? sourceEntries.slice(getLatestCompactionBoundary(sourceEntries)) : sourceEntries;\n\tconst relevantMessages = buildMessagesForConsolidation(\n\t\trelevantEntries.length > 0 ? extractMessagesFromSessionEntries(relevantEntries) : options.messages,\n\t);\n\n\tif (!hasMeaningfulMessages(relevantMessages)) {\n\t\treturn { skipped: true, appendedMemoryEntries: 0, appendedHistoryBlock: false };\n\t}\n\n\tconst response = await buildInlineConsolidationResponse(options, relevantMessages);\n\tconst timestamp = new Date().toISOString();\n\n\tif (response.memoryEntries.length > 0) {\n\t\tawait appendChannelMemoryUpdate(options.channelDir, {\n\t\t\ttimestamp,\n\t\t\tentries: response.memoryEntries,\n\t\t});\n\t}\n\n\tif (response.historyBlock.trim()) {\n\t\tawait appendChannelHistoryBlock(options.channelDir, {\n\t\t\ttimestamp,\n\t\t\tcontent: response.historyBlock,\n\t\t});\n\t}\n\n\treturn {\n\t\tskipped: false,\n\t\tappendedMemoryEntries: response.memoryEntries.length,\n\t\tappendedHistoryBlock: response.historyBlock.trim().length > 0,\n\t};\n}\n\nasync function cleanupChannelMemory(options: ConsolidationRunOptions, currentMemory: string): Promise<boolean> {\n\tif (\n\t\tcurrentMemory.length < MEMORY_CLEANUP_LENGTH_THRESHOLD &&\n\t\tcountMatchingSectionHeadings(currentMemory, \"Update \") < MEMORY_UPDATE_BLOCK_THRESHOLD\n\t) {\n\t\treturn false;\n\t}\n\n\tconst prompt = `Current MEMORY.md:\n${currentMemory}`;\n\tconst nextMemory = await runWorkerPrompt(\n\t\t\"memory-cleanup\",\n\t\toptions.model,\n\t\toptions.resolveApiKey,\n\t\tMEMORY_CLEANUP_SYSTEM_PROMPT,\n\t\tprompt,\n\t\tMEMORY_CLEANUP_TIMEOUT_MS,\n\t);\n\tawait rewriteChannelMemory(options.channelDir, nextMemory);\n\treturn true;\n}\n\nasync function foldChannelHistory(options: ConsolidationRunOptions, currentHistory: string): Promise<boolean> {\n\tconst sections = splitMarkdownSections(currentHistory);\n\tif (currentHistory.length < HISTORY_LENGTH_THRESHOLD && sections.length < HISTORY_BLOCK_THRESHOLD) {\n\t\treturn false;\n\t}\n\n\tif (sections.length <= HISTORY_RECENT_BLOCKS_TO_KEEP) {\n\t\treturn false;\n\t}\n\n\tconst olderSections = sections.slice(0, -HISTORY_RECENT_BLOCKS_TO_KEEP);\n\tconst recentSections = sections.slice(-HISTORY_RECENT_BLOCKS_TO_KEEP);\n\tconst prompt = `Older history blocks to fold:\n${olderSections.map((section) => `## ${section.heading}\\n\\n${section.content}`).join(\"\\n\\n\")}`;\n\tconst foldedSummary = await runWorkerPrompt(\n\t\t\"history-folding\",\n\t\toptions.model,\n\t\toptions.resolveApiKey,\n\t\tHISTORY_FOLDING_SYSTEM_PROMPT,\n\t\tprompt,\n\t\tHISTORY_FOLDING_TIMEOUT_MS,\n\t);\n\n\tconst foldedHeading = `## Folded History Through ${olderSections[olderSections.length - 1]?.heading ?? new Date().toISOString()}`;\n\tconst rebuiltHistory = [\n\t\t\"# Channel History\",\n\t\t\"\",\n\t\tfoldedHeading,\n\t\t\"\",\n\t\tnormalizeText(foldedSummary),\n\t\t\"\",\n\t\t...recentSections.flatMap((section) => [`## ${section.heading}`, \"\", normalizeText(section.content), \"\"]),\n\t].join(\"\\n\");\n\n\tawait rewriteChannelHistory(options.channelDir, rebuiltHistory);\n\treturn true;\n}\n\nexport async function runBackgroundMaintenance(options: ConsolidationRunOptions): Promise<BackgroundMaintenanceResult> {\n\tconst currentMemory = await readChannelMemory(options.channelDir);\n\tconst currentHistory = await readChannelHistory(options.channelDir);\n\n\tconst cleanedMemory = await cleanupChannelMemory(options, currentMemory);\n\tconst foldedHistory = await foldChannelHistory(options, currentHistory);\n\n\treturn { cleanedMemory, foldedHistory };\n}\n"]}
@@ -8,12 +8,15 @@ export interface HistoryBlock {
8
8
  }
9
9
  export declare function getChannelMemoryPath(channelDir: string): string;
10
10
  export declare function getChannelHistoryPath(channelDir: string): string;
11
+ export declare function getChannelSessionPath(channelDir: string): string;
11
12
  export declare function ensureChannelMemoryFiles(channelDir: string): Promise<void>;
12
13
  export declare function ensureChannelMemoryFilesSync(channelDir: string): void;
13
14
  export declare function readChannelMemory(channelDir: string): Promise<string>;
14
15
  export declare function readChannelHistory(channelDir: string): Promise<string>;
16
+ export declare function readChannelSession(channelDir: string): Promise<string>;
15
17
  export declare function rewriteChannelMemory(channelDir: string, content: string): Promise<void>;
16
18
  export declare function rewriteChannelHistory(channelDir: string, content: string): Promise<void>;
19
+ export declare function rewriteChannelSession(channelDir: string, content: string): Promise<void>;
17
20
  export declare function appendChannelMemoryUpdate(channelDir: string, block: MemoryUpdateBlock): Promise<void>;
18
21
  export declare function appendChannelHistoryBlock(channelDir: string, block: HistoryBlock): Promise<void>;
19
22
  export interface MarkdownSection {
@@ -1 +1 @@
1
- {"version":3,"file":"memory-files.d.ts","sourceRoot":"","sources":["../src/memory-files.ts"],"names":[],"mappings":"AA0BA,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAsB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhF;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAYrE;AASD,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE3E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE5E;AAED,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7F;AAED,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9F;AAED,wBAAsB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3G;AAED,wBAAsB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtG;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAmCxE","sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from \"fs\";\nimport { mkdir, readFile, rename, writeFile } from \"fs/promises\";\nimport { dirname, join } from \"path\";\n\nconst DEFAULT_CHANNEL_MEMORY = `# Channel Memory\n\nThis file stores durable channel-specific memory.\n\n- It is not preloaded into session context.\n- Read it on demand when prior decisions, preferences, or long-running work matter.\n- The runtime may append updates here during consolidation.\n\n## Durable Facts\n\n<!-- Stable facts, preferences, and ongoing commitments can accumulate here. -->\n`;\n\nconst DEFAULT_CHANNEL_HISTORY = `# Channel History\n\nThis file stores summarized older channel history.\n\n- It is not preloaded into session context.\n- Read it on demand when older context matters.\n- The runtime may append and fold history blocks here during consolidation.\n`;\n\nexport interface MemoryUpdateBlock {\n\ttimestamp: string;\n\tentries: string[];\n}\n\nexport interface HistoryBlock {\n\ttimestamp: string;\n\tcontent: string;\n}\n\nfunction normalizeContent(content: string): string {\n\treturn content.trim().length > 0 ? `${content.trim()}\\n` : \"\";\n}\n\nasync function writeAtomically(path: string, content: string): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true });\n\tconst tempPath = `${path}.tmp`;\n\tawait writeFile(tempPath, content, \"utf-8\");\n\tawait rename(tempPath, path);\n}\n\nfunction ensureTrailingNewlines(content: string): string {\n\treturn content.trimEnd().length > 0 ? `${content.trimEnd()}\\n\\n` : \"\";\n}\n\nexport function getChannelMemoryPath(channelDir: string): string {\n\treturn join(channelDir, \"MEMORY.md\");\n}\n\nexport function getChannelHistoryPath(channelDir: string): string {\n\treturn join(channelDir, \"HISTORY.md\");\n}\n\nexport async function ensureChannelMemoryFiles(channelDir: string): Promise<void> {\n\tensureChannelMemoryFilesSync(channelDir);\n}\n\nexport function ensureChannelMemoryFilesSync(channelDir: string): void {\n\tconst memoryPath = getChannelMemoryPath(channelDir);\n\tconst historyPath = getChannelHistoryPath(channelDir);\n\n\tmkdirSync(channelDir, { recursive: true });\n\n\tif (!existsSync(memoryPath)) {\n\t\twriteFileSync(memoryPath, DEFAULT_CHANNEL_MEMORY, \"utf-8\");\n\t}\n\tif (!existsSync(historyPath)) {\n\t\twriteFileSync(historyPath, DEFAULT_CHANNEL_HISTORY, \"utf-8\");\n\t}\n}\n\nasync function readTextFile(path: string): Promise<string> {\n\tif (!existsSync(path)) {\n\t\treturn \"\";\n\t}\n\treturn readFile(path, \"utf-8\");\n}\n\nexport async function readChannelMemory(channelDir: string): Promise<string> {\n\treturn readTextFile(getChannelMemoryPath(channelDir));\n}\n\nexport async function readChannelHistory(channelDir: string): Promise<string> {\n\treturn readTextFile(getChannelHistoryPath(channelDir));\n}\n\nexport async function rewriteChannelMemory(channelDir: string, content: string): Promise<void> {\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst nextContent = normalizeContent(content) || DEFAULT_CHANNEL_MEMORY;\n\tawait writeAtomically(getChannelMemoryPath(channelDir), nextContent);\n}\n\nexport async function rewriteChannelHistory(channelDir: string, content: string): Promise<void> {\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst nextContent = normalizeContent(content) || DEFAULT_CHANNEL_HISTORY;\n\tawait writeAtomically(getChannelHistoryPath(channelDir), nextContent);\n}\n\nexport async function appendChannelMemoryUpdate(channelDir: string, block: MemoryUpdateBlock): Promise<void> {\n\tif (block.entries.length === 0) {\n\t\treturn;\n\t}\n\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst path = getChannelMemoryPath(channelDir);\n\tconst existing = await readTextFile(path);\n\tconst renderedBlock = [`## Update ${block.timestamp}`, ...block.entries.map((entry) => `- ${entry.trim()}`)].join(\n\t\t\"\\n\",\n\t);\n\tawait writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\\n`);\n}\n\nexport async function appendChannelHistoryBlock(channelDir: string, block: HistoryBlock): Promise<void> {\n\tconst trimmedContent = block.content.trim();\n\tif (!trimmedContent) {\n\t\treturn;\n\t}\n\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst path = getChannelHistoryPath(channelDir);\n\tconst existing = await readTextFile(path);\n\tconst renderedBlock = [`## ${block.timestamp}`, trimmedContent].join(\"\\n\\n\");\n\tawait writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\\n`);\n}\n\nexport interface MarkdownSection {\n\theading: string;\n\tcontent: string;\n}\n\nexport function splitMarkdownSections(content: string): MarkdownSection[] {\n\tconst normalized = content.replace(/\\r/g, \"\").trim();\n\tif (!normalized) {\n\t\treturn [];\n\t}\n\n\tconst lines = normalized.split(\"\\n\");\n\tconst sections: MarkdownSection[] = [];\n\tlet currentHeading = \"\";\n\tlet currentLines: string[] = [];\n\n\tconst flush = (): void => {\n\t\tif (!currentHeading) {\n\t\t\treturn;\n\t\t}\n\t\tsections.push({\n\t\t\theading: currentHeading,\n\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t});\n\t};\n\n\tfor (const line of lines) {\n\t\tif (line.startsWith(\"## \")) {\n\t\t\tflush();\n\t\t\tcurrentHeading = line.slice(3).trim();\n\t\t\tcurrentLines = [];\n\t\t\tcontinue;\n\t\t}\n\t\tif (currentHeading) {\n\t\t\tcurrentLines.push(line);\n\t\t}\n\t}\n\n\tflush();\n\treturn sections;\n}\n"]}
1
+ {"version":3,"file":"memory-files.d.ts","sourceRoot":"","sources":["../src/memory-files.ts"],"names":[],"mappings":"AA+DA,MAAM,WAAW,iBAAiB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CAChB;AAiBD,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAE/D;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAgB,qBAAqB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAEhE;AAED,wBAAsB,wBAAwB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAEhF;AAED,wBAAgB,4BAA4B,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI,CAgBrE;AASD,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE3E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE5E;AAED,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAE5E;AAED,wBAAsB,oBAAoB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI7F;AAED,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9F;AAED,wBAAsB,qBAAqB,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAI9F;AAED,wBAAsB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAY3G;AAED,wBAAsB,yBAAyB,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAWtG;AAED,MAAM,WAAW,eAAe;IAC/B,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;CAChB;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,GAAG,eAAe,EAAE,CAmCxE"}
@@ -21,6 +21,42 @@ This file stores summarized older channel history.
21
21
  - Read it on demand when older context matters.
22
22
  - The runtime may append and fold history blocks here during consolidation.
23
23
  `;
24
+ const DEFAULT_CHANNEL_SESSION = `# Session Title
25
+
26
+ <!-- A short title for the current active work in this channel. -->
27
+
28
+ # Current State
29
+
30
+ <!-- What is actively being worked on right now. -->
31
+
32
+ # User Intent
33
+
34
+ <!-- What the user is currently trying to achieve. -->
35
+
36
+ # Active Files
37
+
38
+ <!-- Important files or directories currently in focus. -->
39
+
40
+ # Decisions
41
+
42
+ <!-- Recent decisions that matter to the current work. -->
43
+
44
+ # Constraints
45
+
46
+ <!-- Current constraints, assumptions, or important guardrails. -->
47
+
48
+ # Errors & Corrections
49
+
50
+ <!-- Recent failures, corrections, and things to avoid repeating. -->
51
+
52
+ # Next Steps
53
+
54
+ <!-- Likely next actions if work resumes later. -->
55
+
56
+ # Worklog
57
+
58
+ <!-- Very terse notes about recent progress. -->
59
+ `;
24
60
  function normalizeContent(content) {
25
61
  return content.trim().length > 0 ? `${content.trim()}\n` : "";
26
62
  }
@@ -39,12 +75,16 @@ export function getChannelMemoryPath(channelDir) {
39
75
  export function getChannelHistoryPath(channelDir) {
40
76
  return join(channelDir, "HISTORY.md");
41
77
  }
78
+ export function getChannelSessionPath(channelDir) {
79
+ return join(channelDir, "SESSION.md");
80
+ }
42
81
  export async function ensureChannelMemoryFiles(channelDir) {
43
82
  ensureChannelMemoryFilesSync(channelDir);
44
83
  }
45
84
  export function ensureChannelMemoryFilesSync(channelDir) {
46
85
  const memoryPath = getChannelMemoryPath(channelDir);
47
86
  const historyPath = getChannelHistoryPath(channelDir);
87
+ const sessionPath = getChannelSessionPath(channelDir);
48
88
  mkdirSync(channelDir, { recursive: true });
49
89
  if (!existsSync(memoryPath)) {
50
90
  writeFileSync(memoryPath, DEFAULT_CHANNEL_MEMORY, "utf-8");
@@ -52,6 +92,9 @@ export function ensureChannelMemoryFilesSync(channelDir) {
52
92
  if (!existsSync(historyPath)) {
53
93
  writeFileSync(historyPath, DEFAULT_CHANNEL_HISTORY, "utf-8");
54
94
  }
95
+ if (!existsSync(sessionPath)) {
96
+ writeFileSync(sessionPath, DEFAULT_CHANNEL_SESSION, "utf-8");
97
+ }
55
98
  }
56
99
  async function readTextFile(path) {
57
100
  if (!existsSync(path)) {
@@ -65,6 +108,9 @@ export async function readChannelMemory(channelDir) {
65
108
  export async function readChannelHistory(channelDir) {
66
109
  return readTextFile(getChannelHistoryPath(channelDir));
67
110
  }
111
+ export async function readChannelSession(channelDir) {
112
+ return readTextFile(getChannelSessionPath(channelDir));
113
+ }
68
114
  export async function rewriteChannelMemory(channelDir, content) {
69
115
  await ensureChannelMemoryFiles(channelDir);
70
116
  const nextContent = normalizeContent(content) || DEFAULT_CHANNEL_MEMORY;
@@ -75,6 +121,11 @@ export async function rewriteChannelHistory(channelDir, content) {
75
121
  const nextContent = normalizeContent(content) || DEFAULT_CHANNEL_HISTORY;
76
122
  await writeAtomically(getChannelHistoryPath(channelDir), nextContent);
77
123
  }
124
+ export async function rewriteChannelSession(channelDir, content) {
125
+ await ensureChannelMemoryFiles(channelDir);
126
+ const nextContent = normalizeContent(content) || DEFAULT_CHANNEL_SESSION;
127
+ await writeAtomically(getChannelSessionPath(channelDir), nextContent);
128
+ }
78
129
  export async function appendChannelMemoryUpdate(channelDir, block) {
79
130
  if (block.entries.length === 0) {
80
131
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"memory-files.js","sourceRoot":"","sources":["../src/memory-files.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from \"fs\";\nimport { mkdir, readFile, rename, writeFile } from \"fs/promises\";\nimport { dirname, join } from \"path\";\n\nconst DEFAULT_CHANNEL_MEMORY = `# Channel Memory\n\nThis file stores durable channel-specific memory.\n\n- It is not preloaded into session context.\n- Read it on demand when prior decisions, preferences, or long-running work matter.\n- The runtime may append updates here during consolidation.\n\n## Durable Facts\n\n<!-- Stable facts, preferences, and ongoing commitments can accumulate here. -->\n`;\n\nconst DEFAULT_CHANNEL_HISTORY = `# Channel History\n\nThis file stores summarized older channel history.\n\n- It is not preloaded into session context.\n- Read it on demand when older context matters.\n- The runtime may append and fold history blocks here during consolidation.\n`;\n\nexport interface MemoryUpdateBlock {\n\ttimestamp: string;\n\tentries: string[];\n}\n\nexport interface HistoryBlock {\n\ttimestamp: string;\n\tcontent: string;\n}\n\nfunction normalizeContent(content: string): string {\n\treturn content.trim().length > 0 ? `${content.trim()}\\n` : \"\";\n}\n\nasync function writeAtomically(path: string, content: string): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true });\n\tconst tempPath = `${path}.tmp`;\n\tawait writeFile(tempPath, content, \"utf-8\");\n\tawait rename(tempPath, path);\n}\n\nfunction ensureTrailingNewlines(content: string): string {\n\treturn content.trimEnd().length > 0 ? `${content.trimEnd()}\\n\\n` : \"\";\n}\n\nexport function getChannelMemoryPath(channelDir: string): string {\n\treturn join(channelDir, \"MEMORY.md\");\n}\n\nexport function getChannelHistoryPath(channelDir: string): string {\n\treturn join(channelDir, \"HISTORY.md\");\n}\n\nexport async function ensureChannelMemoryFiles(channelDir: string): Promise<void> {\n\tensureChannelMemoryFilesSync(channelDir);\n}\n\nexport function ensureChannelMemoryFilesSync(channelDir: string): void {\n\tconst memoryPath = getChannelMemoryPath(channelDir);\n\tconst historyPath = getChannelHistoryPath(channelDir);\n\n\tmkdirSync(channelDir, { recursive: true });\n\n\tif (!existsSync(memoryPath)) {\n\t\twriteFileSync(memoryPath, DEFAULT_CHANNEL_MEMORY, \"utf-8\");\n\t}\n\tif (!existsSync(historyPath)) {\n\t\twriteFileSync(historyPath, DEFAULT_CHANNEL_HISTORY, \"utf-8\");\n\t}\n}\n\nasync function readTextFile(path: string): Promise<string> {\n\tif (!existsSync(path)) {\n\t\treturn \"\";\n\t}\n\treturn readFile(path, \"utf-8\");\n}\n\nexport async function readChannelMemory(channelDir: string): Promise<string> {\n\treturn readTextFile(getChannelMemoryPath(channelDir));\n}\n\nexport async function readChannelHistory(channelDir: string): Promise<string> {\n\treturn readTextFile(getChannelHistoryPath(channelDir));\n}\n\nexport async function rewriteChannelMemory(channelDir: string, content: string): Promise<void> {\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst nextContent = normalizeContent(content) || DEFAULT_CHANNEL_MEMORY;\n\tawait writeAtomically(getChannelMemoryPath(channelDir), nextContent);\n}\n\nexport async function rewriteChannelHistory(channelDir: string, content: string): Promise<void> {\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst nextContent = normalizeContent(content) || DEFAULT_CHANNEL_HISTORY;\n\tawait writeAtomically(getChannelHistoryPath(channelDir), nextContent);\n}\n\nexport async function appendChannelMemoryUpdate(channelDir: string, block: MemoryUpdateBlock): Promise<void> {\n\tif (block.entries.length === 0) {\n\t\treturn;\n\t}\n\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst path = getChannelMemoryPath(channelDir);\n\tconst existing = await readTextFile(path);\n\tconst renderedBlock = [`## Update ${block.timestamp}`, ...block.entries.map((entry) => `- ${entry.trim()}`)].join(\n\t\t\"\\n\",\n\t);\n\tawait writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\\n`);\n}\n\nexport async function appendChannelHistoryBlock(channelDir: string, block: HistoryBlock): Promise<void> {\n\tconst trimmedContent = block.content.trim();\n\tif (!trimmedContent) {\n\t\treturn;\n\t}\n\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst path = getChannelHistoryPath(channelDir);\n\tconst existing = await readTextFile(path);\n\tconst renderedBlock = [`## ${block.timestamp}`, trimmedContent].join(\"\\n\\n\");\n\tawait writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\\n`);\n}\n\nexport interface MarkdownSection {\n\theading: string;\n\tcontent: string;\n}\n\nexport function splitMarkdownSections(content: string): MarkdownSection[] {\n\tconst normalized = content.replace(/\\r/g, \"\").trim();\n\tif (!normalized) {\n\t\treturn [];\n\t}\n\n\tconst lines = normalized.split(\"\\n\");\n\tconst sections: MarkdownSection[] = [];\n\tlet currentHeading = \"\";\n\tlet currentLines: string[] = [];\n\n\tconst flush = (): void => {\n\t\tif (!currentHeading) {\n\t\t\treturn;\n\t\t}\n\t\tsections.push({\n\t\t\theading: currentHeading,\n\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t});\n\t};\n\n\tfor (const line of lines) {\n\t\tif (line.startsWith(\"## \")) {\n\t\t\tflush();\n\t\t\tcurrentHeading = line.slice(3).trim();\n\t\t\tcurrentLines = [];\n\t\t\tcontinue;\n\t\t}\n\t\tif (currentHeading) {\n\t\t\tcurrentLines.push(line);\n\t\t}\n\t}\n\n\tflush();\n\treturn sections;\n}\n"]}
1
+ {"version":3,"file":"memory-files.js","sourceRoot":"","sources":["../src/memory-files.ts"],"names":[],"mappings":"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","sourcesContent":["import { existsSync, mkdirSync, writeFileSync } from \"fs\";\nimport { mkdir, readFile, rename, writeFile } from \"fs/promises\";\nimport { dirname, join } from \"path\";\n\nconst DEFAULT_CHANNEL_MEMORY = `# Channel Memory\n\nThis file stores durable channel-specific memory.\n\n- It is not preloaded into session context.\n- Read it on demand when prior decisions, preferences, or long-running work matter.\n- The runtime may append updates here during consolidation.\n\n## Durable Facts\n\n<!-- Stable facts, preferences, and ongoing commitments can accumulate here. -->\n`;\n\nconst DEFAULT_CHANNEL_HISTORY = `# Channel History\n\nThis file stores summarized older channel history.\n\n- It is not preloaded into session context.\n- Read it on demand when older context matters.\n- The runtime may append and fold history blocks here during consolidation.\n`;\n\nconst DEFAULT_CHANNEL_SESSION = `# Session Title\n\n<!-- A short title for the current active work in this channel. -->\n\n# Current State\n\n<!-- What is actively being worked on right now. -->\n\n# User Intent\n\n<!-- What the user is currently trying to achieve. -->\n\n# Active Files\n\n<!-- Important files or directories currently in focus. -->\n\n# Decisions\n\n<!-- Recent decisions that matter to the current work. -->\n\n# Constraints\n\n<!-- Current constraints, assumptions, or important guardrails. -->\n\n# Errors & Corrections\n\n<!-- Recent failures, corrections, and things to avoid repeating. -->\n\n# Next Steps\n\n<!-- Likely next actions if work resumes later. -->\n\n# Worklog\n\n<!-- Very terse notes about recent progress. -->\n`;\n\nexport interface MemoryUpdateBlock {\n\ttimestamp: string;\n\tentries: string[];\n}\n\nexport interface HistoryBlock {\n\ttimestamp: string;\n\tcontent: string;\n}\n\nfunction normalizeContent(content: string): string {\n\treturn content.trim().length > 0 ? `${content.trim()}\\n` : \"\";\n}\n\nasync function writeAtomically(path: string, content: string): Promise<void> {\n\tawait mkdir(dirname(path), { recursive: true });\n\tconst tempPath = `${path}.tmp`;\n\tawait writeFile(tempPath, content, \"utf-8\");\n\tawait rename(tempPath, path);\n}\n\nfunction ensureTrailingNewlines(content: string): string {\n\treturn content.trimEnd().length > 0 ? `${content.trimEnd()}\\n\\n` : \"\";\n}\n\nexport function getChannelMemoryPath(channelDir: string): string {\n\treturn join(channelDir, \"MEMORY.md\");\n}\n\nexport function getChannelHistoryPath(channelDir: string): string {\n\treturn join(channelDir, \"HISTORY.md\");\n}\n\nexport function getChannelSessionPath(channelDir: string): string {\n\treturn join(channelDir, \"SESSION.md\");\n}\n\nexport async function ensureChannelMemoryFiles(channelDir: string): Promise<void> {\n\tensureChannelMemoryFilesSync(channelDir);\n}\n\nexport function ensureChannelMemoryFilesSync(channelDir: string): void {\n\tconst memoryPath = getChannelMemoryPath(channelDir);\n\tconst historyPath = getChannelHistoryPath(channelDir);\n\tconst sessionPath = getChannelSessionPath(channelDir);\n\n\tmkdirSync(channelDir, { recursive: true });\n\n\tif (!existsSync(memoryPath)) {\n\t\twriteFileSync(memoryPath, DEFAULT_CHANNEL_MEMORY, \"utf-8\");\n\t}\n\tif (!existsSync(historyPath)) {\n\t\twriteFileSync(historyPath, DEFAULT_CHANNEL_HISTORY, \"utf-8\");\n\t}\n\tif (!existsSync(sessionPath)) {\n\t\twriteFileSync(sessionPath, DEFAULT_CHANNEL_SESSION, \"utf-8\");\n\t}\n}\n\nasync function readTextFile(path: string): Promise<string> {\n\tif (!existsSync(path)) {\n\t\treturn \"\";\n\t}\n\treturn readFile(path, \"utf-8\");\n}\n\nexport async function readChannelMemory(channelDir: string): Promise<string> {\n\treturn readTextFile(getChannelMemoryPath(channelDir));\n}\n\nexport async function readChannelHistory(channelDir: string): Promise<string> {\n\treturn readTextFile(getChannelHistoryPath(channelDir));\n}\n\nexport async function readChannelSession(channelDir: string): Promise<string> {\n\treturn readTextFile(getChannelSessionPath(channelDir));\n}\n\nexport async function rewriteChannelMemory(channelDir: string, content: string): Promise<void> {\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst nextContent = normalizeContent(content) || DEFAULT_CHANNEL_MEMORY;\n\tawait writeAtomically(getChannelMemoryPath(channelDir), nextContent);\n}\n\nexport async function rewriteChannelHistory(channelDir: string, content: string): Promise<void> {\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst nextContent = normalizeContent(content) || DEFAULT_CHANNEL_HISTORY;\n\tawait writeAtomically(getChannelHistoryPath(channelDir), nextContent);\n}\n\nexport async function rewriteChannelSession(channelDir: string, content: string): Promise<void> {\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst nextContent = normalizeContent(content) || DEFAULT_CHANNEL_SESSION;\n\tawait writeAtomically(getChannelSessionPath(channelDir), nextContent);\n}\n\nexport async function appendChannelMemoryUpdate(channelDir: string, block: MemoryUpdateBlock): Promise<void> {\n\tif (block.entries.length === 0) {\n\t\treturn;\n\t}\n\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst path = getChannelMemoryPath(channelDir);\n\tconst existing = await readTextFile(path);\n\tconst renderedBlock = [`## Update ${block.timestamp}`, ...block.entries.map((entry) => `- ${entry.trim()}`)].join(\n\t\t\"\\n\",\n\t);\n\tawait writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\\n`);\n}\n\nexport async function appendChannelHistoryBlock(channelDir: string, block: HistoryBlock): Promise<void> {\n\tconst trimmedContent = block.content.trim();\n\tif (!trimmedContent) {\n\t\treturn;\n\t}\n\n\tawait ensureChannelMemoryFiles(channelDir);\n\tconst path = getChannelHistoryPath(channelDir);\n\tconst existing = await readTextFile(path);\n\tconst renderedBlock = [`## ${block.timestamp}`, trimmedContent].join(\"\\n\\n\");\n\tawait writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\\n`);\n}\n\nexport interface MarkdownSection {\n\theading: string;\n\tcontent: string;\n}\n\nexport function splitMarkdownSections(content: string): MarkdownSection[] {\n\tconst normalized = content.replace(/\\r/g, \"\").trim();\n\tif (!normalized) {\n\t\treturn [];\n\t}\n\n\tconst lines = normalized.split(\"\\n\");\n\tconst sections: MarkdownSection[] = [];\n\tlet currentHeading = \"\";\n\tlet currentLines: string[] = [];\n\n\tconst flush = (): void => {\n\t\tif (!currentHeading) {\n\t\t\treturn;\n\t\t}\n\t\tsections.push({\n\t\t\theading: currentHeading,\n\t\t\tcontent: currentLines.join(\"\\n\").trim(),\n\t\t});\n\t};\n\n\tfor (const line of lines) {\n\t\tif (line.startsWith(\"## \")) {\n\t\t\tflush();\n\t\t\tcurrentHeading = line.slice(3).trim();\n\t\t\tcurrentLines = [];\n\t\t\tcontinue;\n\t\t}\n\t\tif (currentHeading) {\n\t\t\tcurrentLines.push(line);\n\t\t}\n\t}\n\n\tflush();\n\treturn sections;\n}\n"]}
@@ -1,6 +1,7 @@
1
1
  import type { AgentMessage } from "@mariozechner/pi-agent-core";
2
2
  import type { Api, Model } from "@mariozechner/pi-ai";
3
3
  import type { ExtensionFactory, SessionEntry } from "@mariozechner/pi-coding-agent";
4
+ import type { PipiclawSessionMemorySettings } from "./context.js";
4
5
  export type ConsolidationReason = "compaction" | "new-session";
5
6
  export interface MemoryLifecycleOptions {
6
7
  channelId: string;
@@ -9,13 +10,21 @@ export interface MemoryLifecycleOptions {
9
10
  getSessionEntries: () => SessionEntry[];
10
11
  getModel: () => Model<Api>;
11
12
  resolveApiKey: (model: Model<Api>) => Promise<string>;
13
+ getSessionMemorySettings: () => PipiclawSessionMemorySettings;
12
14
  }
13
15
  export declare class MemoryLifecycle {
14
16
  private options;
15
17
  private backgroundQueue;
18
+ private turnsSinceSessionUpdate;
19
+ private toolCallsSinceSessionUpdate;
20
+ private sessionUpdatePending;
16
21
  constructor(options: MemoryLifecycleOptions);
17
22
  private buildRunOptions;
18
23
  createExtensionFactory(): ExtensionFactory;
24
+ noteToolCall(): void;
25
+ noteCompletedAssistantTurn(): void;
26
+ private refreshSessionMemory;
27
+ private enqueueSessionMemoryUpdate;
19
28
  private consolidateBeforeContextDrop;
20
29
  private handleSessionBeforeCompact;
21
30
  private handleSessionCompact;
@@ -1 +1 @@
1
- {"version":3,"file":"memory-lifecycle.d.ts","sourceRoot":"","sources":["../src/memory-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EACX,gBAAgB,EAIhB,YAAY,EAEZ,MAAM,+BAA+B,CAAC;AASvC,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,aAAa,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,YAAY,EAAE,CAAC;IAClC,iBAAiB,EAAE,MAAM,YAAY,EAAE,CAAC;IACxC,QAAQ,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;CACtD;AAED,qBAAa,eAAe;IAGf,OAAO,CAAC,OAAO;IAF3B,OAAO,CAAC,eAAe,CAAoC;IAE3D,YAAoB,OAAO,EAAE,sBAAsB,EAAI;IAEvD,OAAO,CAAC,eAAe;IAUvB,sBAAsB,IAAI,gBAAgB,CAezC;YAEa,4BAA4B;YAiB5B,0BAA0B;IAIxC,OAAO,CAAC,oBAAoB;YAId,yBAAyB;IAQvC,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,4BAA4B;IAYpC,OAAO,CAAC,mBAAmB;CAW3B","sourcesContent":["import type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { Api, Model } from \"@mariozechner/pi-ai\";\nimport type {\n\tExtensionFactory,\n\tSessionBeforeCompactEvent,\n\tSessionBeforeSwitchEvent,\n\tSessionCompactEvent,\n\tSessionEntry,\n\tSessionSwitchEvent,\n} from \"@mariozechner/pi-coding-agent\";\nimport * as log from \"./log.js\";\nimport {\n\ttype BackgroundMaintenanceResult,\n\ttype ConsolidationRunOptions,\n\trunBackgroundMaintenance,\n\trunInlineConsolidation,\n} from \"./memory-consolidation.js\";\n\nexport type ConsolidationReason = \"compaction\" | \"new-session\";\n\nexport interface MemoryLifecycleOptions {\n\tchannelId: string;\n\tchannelDir: string;\n\tgetMessages: () => AgentMessage[];\n\tgetSessionEntries: () => SessionEntry[];\n\tgetModel: () => Model<Api>;\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n}\n\nexport class MemoryLifecycle {\n\tprivate backgroundQueue: Promise<void> = Promise.resolve();\n\n\tconstructor(private options: MemoryLifecycleOptions) {}\n\n\tprivate buildRunOptions(messages?: AgentMessage[], sessionEntries?: SessionEntry[]): ConsolidationRunOptions {\n\t\treturn {\n\t\t\tchannelDir: this.options.channelDir,\n\t\t\tmodel: this.options.getModel(),\n\t\t\tresolveApiKey: this.options.resolveApiKey,\n\t\t\tmessages: messages ?? this.options.getMessages(),\n\t\t\tsessionEntries: sessionEntries ?? this.options.getSessionEntries(),\n\t\t};\n\t}\n\n\tcreateExtensionFactory(): ExtensionFactory {\n\t\treturn (pi) => {\n\t\t\tpi.on(\"session_before_compact\", async (event: SessionBeforeCompactEvent) => {\n\t\t\t\tawait this.handleSessionBeforeCompact(event);\n\t\t\t});\n\t\t\tpi.on(\"session_compact\", async (event: SessionCompactEvent) => {\n\t\t\t\tthis.handleSessionCompact(event);\n\t\t\t});\n\t\t\tpi.on(\"session_before_switch\", async (event: SessionBeforeSwitchEvent) => {\n\t\t\t\tawait this.handleSessionBeforeSwitch(event);\n\t\t\t});\n\t\t\tpi.on(\"session_switch\", async (event: SessionSwitchEvent) => {\n\t\t\t\tthis.handleSessionSwitch(event);\n\t\t\t});\n\t\t};\n\t}\n\n\tprivate async consolidateBeforeContextDrop(\n\t\treason: ConsolidationReason,\n\t\tmessages?: AgentMessage[],\n\t\tsessionEntries?: SessionEntry[],\n\t): Promise<void> {\n\t\tlog.logInfo(`[${this.options.channelId}] Memory consolidation starting (${reason})`);\n\t\ttry {\n\t\t\tconst result = await runInlineConsolidation(this.buildRunOptions(messages, sessionEntries));\n\t\t\tlog.logInfo(\n\t\t\t\t`[${this.options.channelId}] Memory consolidation finished (${reason}): memory entries=${result.appendedMemoryEntries}, history=${result.appendedHistoryBlock ? \"yes\" : \"no\"}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logWarning(`[${this.options.channelId}] Memory consolidation failed (${reason})`, message);\n\t\t}\n\t}\n\n\tprivate async handleSessionBeforeCompact(event: SessionBeforeCompactEvent): Promise<void> {\n\t\tawait this.consolidateBeforeContextDrop(\"compaction\", event.preparation.messagesToSummarize);\n\t}\n\n\tprivate handleSessionCompact(_event: SessionCompactEvent): void {\n\t\tthis.enqueueBackgroundMaintenance();\n\t}\n\n\tprivate async handleSessionBeforeSwitch(event: SessionBeforeSwitchEvent): Promise<void> {\n\t\tif (event.reason !== \"new\") {\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.consolidateBeforeContextDrop(\"new-session\");\n\t}\n\n\tprivate handleSessionSwitch(event: SessionSwitchEvent): void {\n\t\tif (event.reason !== \"new\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.enqueueBackgroundMaintenance();\n\t}\n\n\tprivate enqueueBackgroundMaintenance(): void {\n\t\tthis.backgroundQueue = this.backgroundQueue\n\t\t\t.then(async () => {\n\t\t\t\tconst result = await runBackgroundMaintenance(this.buildRunOptions([], []));\n\t\t\t\tthis.logBackgroundResult(result);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tlog.logWarning(`[${this.options.channelId}] Background memory maintenance failed`, message);\n\t\t\t});\n\t}\n\n\tprivate logBackgroundResult(result: BackgroundMaintenanceResult): void {\n\t\tif (!result.cleanedMemory && !result.foldedHistory) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst details = [\n\t\t\t`memory cleanup=${result.cleanedMemory ? \"yes\" : \"no\"}`,\n\t\t\t`history fold=${result.foldedHistory ? \"yes\" : \"no\"}`,\n\t\t].join(\", \");\n\t\tlog.logInfo(`[${this.options.channelId}] Background memory maintenance complete: ${details}`);\n\t}\n}\n"]}
1
+ {"version":3,"file":"memory-lifecycle.d.ts","sourceRoot":"","sources":["../src/memory-lifecycle.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AACtD,OAAO,KAAK,EACX,gBAAgB,EAIhB,YAAY,EAEZ,MAAM,+BAA+B,CAAC;AACvC,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,cAAc,CAAC;AAUlE,MAAM,MAAM,mBAAmB,GAAG,YAAY,GAAG,aAAa,CAAC;AAE/D,MAAM,WAAW,sBAAsB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,YAAY,EAAE,CAAC;IAClC,iBAAiB,EAAE,MAAM,YAAY,EAAE,CAAC;IACxC,QAAQ,EAAE,MAAM,KAAK,CAAC,GAAG,CAAC,CAAC;IAC3B,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,wBAAwB,EAAE,MAAM,6BAA6B,CAAC;CAC9D;AAED,qBAAa,eAAe;IAMf,OAAO,CAAC,OAAO;IAL3B,OAAO,CAAC,eAAe,CAAoC;IAC3D,OAAO,CAAC,uBAAuB,CAAK;IACpC,OAAO,CAAC,2BAA2B,CAAK;IACxC,OAAO,CAAC,oBAAoB,CAAS;gBAEjB,OAAO,EAAE,sBAAsB;IAEnD,OAAO,CAAC,eAAe;IAUvB,sBAAsB,IAAI,gBAAgB;IAiB1C,YAAY,IAAI,IAAI;IAOpB,0BAA0B,IAAI,IAAI;YAepB,oBAAoB;IAwBlC,OAAO,CAAC,0BAA0B;YAmBpB,4BAA4B;YAyB5B,0BAA0B;IAIxC,OAAO,CAAC,oBAAoB;YAId,yBAAyB;IAQvC,OAAO,CAAC,mBAAmB;IAQ3B,OAAO,CAAC,4BAA4B;IAYpC,OAAO,CAAC,mBAAmB;CAW3B"}
@@ -1,10 +1,13 @@
1
1
  import * as log from "./log.js";
2
2
  import { runBackgroundMaintenance, runInlineConsolidation, } from "./memory-consolidation.js";
3
+ import { updateChannelSessionMemory } from "./session-memory.js";
3
4
  export class MemoryLifecycle {
4
- options;
5
- backgroundQueue = Promise.resolve();
6
5
  constructor(options) {
7
6
  this.options = options;
7
+ this.backgroundQueue = Promise.resolve();
8
+ this.turnsSinceSessionUpdate = 0;
9
+ this.toolCallsSinceSessionUpdate = 0;
10
+ this.sessionUpdatePending = false;
8
11
  }
9
12
  buildRunOptions(messages, sessionEntries) {
10
13
  return {
@@ -31,7 +34,69 @@ export class MemoryLifecycle {
31
34
  });
32
35
  };
33
36
  }
37
+ noteToolCall() {
38
+ if (!this.options.getSessionMemorySettings().enabled) {
39
+ return;
40
+ }
41
+ this.toolCallsSinceSessionUpdate++;
42
+ }
43
+ noteCompletedAssistantTurn() {
44
+ const settings = this.options.getSessionMemorySettings();
45
+ if (!settings.enabled) {
46
+ return;
47
+ }
48
+ this.turnsSinceSessionUpdate++;
49
+ if (this.turnsSinceSessionUpdate >= settings.minTurnsBetweenUpdate ||
50
+ this.toolCallsSinceSessionUpdate >= settings.minToolCallsBetweenUpdate) {
51
+ this.enqueueSessionMemoryUpdate("threshold");
52
+ }
53
+ }
54
+ async refreshSessionMemory(reason) {
55
+ const settings = this.options.getSessionMemorySettings();
56
+ if (!settings.enabled) {
57
+ return false;
58
+ }
59
+ try {
60
+ await updateChannelSessionMemory({
61
+ channelDir: this.options.channelDir,
62
+ messages: this.options.getMessages(),
63
+ model: this.options.getModel(),
64
+ resolveApiKey: this.options.resolveApiKey,
65
+ });
66
+ this.turnsSinceSessionUpdate = 0;
67
+ this.toolCallsSinceSessionUpdate = 0;
68
+ log.logInfo(`[${this.options.channelId}] Session memory updated (${reason})`);
69
+ return true;
70
+ }
71
+ catch (error) {
72
+ const message = error instanceof Error ? error.message : String(error);
73
+ log.logWarning(`[${this.options.channelId}] Session memory update failed (${reason})`, message);
74
+ return false;
75
+ }
76
+ }
77
+ enqueueSessionMemoryUpdate(reason) {
78
+ if (this.sessionUpdatePending) {
79
+ return;
80
+ }
81
+ this.sessionUpdatePending = true;
82
+ this.backgroundQueue = this.backgroundQueue
83
+ .then(async () => {
84
+ await this.refreshSessionMemory(reason);
85
+ })
86
+ .catch((error) => {
87
+ const message = error instanceof Error ? error.message : String(error);
88
+ log.logWarning(`[${this.options.channelId}] Session memory queue failed`, message);
89
+ })
90
+ .finally(() => {
91
+ this.sessionUpdatePending = false;
92
+ });
93
+ }
34
94
  async consolidateBeforeContextDrop(reason, messages, sessionEntries) {
95
+ const settings = this.options.getSessionMemorySettings();
96
+ if ((reason === "compaction" && settings.forceRefreshBeforeCompact) ||
97
+ (reason === "new-session" && settings.forceRefreshBeforeNewSession)) {
98
+ await this.refreshSessionMemory(reason);
99
+ }
35
100
  log.logInfo(`[${this.options.channelId}] Memory consolidation starting (${reason})`);
36
101
  try {
37
102
  const result = await runInlineConsolidation(this.buildRunOptions(messages, sessionEntries));
@@ -1 +1 @@
1
- {"version":3,"file":"memory-lifecycle.js","sourceRoot":"","sources":["../src/memory-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { Api, Model } from \"@mariozechner/pi-ai\";\nimport type {\n\tExtensionFactory,\n\tSessionBeforeCompactEvent,\n\tSessionBeforeSwitchEvent,\n\tSessionCompactEvent,\n\tSessionEntry,\n\tSessionSwitchEvent,\n} from \"@mariozechner/pi-coding-agent\";\nimport * as log from \"./log.js\";\nimport {\n\ttype BackgroundMaintenanceResult,\n\ttype ConsolidationRunOptions,\n\trunBackgroundMaintenance,\n\trunInlineConsolidation,\n} from \"./memory-consolidation.js\";\n\nexport type ConsolidationReason = \"compaction\" | \"new-session\";\n\nexport interface MemoryLifecycleOptions {\n\tchannelId: string;\n\tchannelDir: string;\n\tgetMessages: () => AgentMessage[];\n\tgetSessionEntries: () => SessionEntry[];\n\tgetModel: () => Model<Api>;\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n}\n\nexport class MemoryLifecycle {\n\tprivate backgroundQueue: Promise<void> = Promise.resolve();\n\n\tconstructor(private options: MemoryLifecycleOptions) {}\n\n\tprivate buildRunOptions(messages?: AgentMessage[], sessionEntries?: SessionEntry[]): ConsolidationRunOptions {\n\t\treturn {\n\t\t\tchannelDir: this.options.channelDir,\n\t\t\tmodel: this.options.getModel(),\n\t\t\tresolveApiKey: this.options.resolveApiKey,\n\t\t\tmessages: messages ?? this.options.getMessages(),\n\t\t\tsessionEntries: sessionEntries ?? this.options.getSessionEntries(),\n\t\t};\n\t}\n\n\tcreateExtensionFactory(): ExtensionFactory {\n\t\treturn (pi) => {\n\t\t\tpi.on(\"session_before_compact\", async (event: SessionBeforeCompactEvent) => {\n\t\t\t\tawait this.handleSessionBeforeCompact(event);\n\t\t\t});\n\t\t\tpi.on(\"session_compact\", async (event: SessionCompactEvent) => {\n\t\t\t\tthis.handleSessionCompact(event);\n\t\t\t});\n\t\t\tpi.on(\"session_before_switch\", async (event: SessionBeforeSwitchEvent) => {\n\t\t\t\tawait this.handleSessionBeforeSwitch(event);\n\t\t\t});\n\t\t\tpi.on(\"session_switch\", async (event: SessionSwitchEvent) => {\n\t\t\t\tthis.handleSessionSwitch(event);\n\t\t\t});\n\t\t};\n\t}\n\n\tprivate async consolidateBeforeContextDrop(\n\t\treason: ConsolidationReason,\n\t\tmessages?: AgentMessage[],\n\t\tsessionEntries?: SessionEntry[],\n\t): Promise<void> {\n\t\tlog.logInfo(`[${this.options.channelId}] Memory consolidation starting (${reason})`);\n\t\ttry {\n\t\t\tconst result = await runInlineConsolidation(this.buildRunOptions(messages, sessionEntries));\n\t\t\tlog.logInfo(\n\t\t\t\t`[${this.options.channelId}] Memory consolidation finished (${reason}): memory entries=${result.appendedMemoryEntries}, history=${result.appendedHistoryBlock ? \"yes\" : \"no\"}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logWarning(`[${this.options.channelId}] Memory consolidation failed (${reason})`, message);\n\t\t}\n\t}\n\n\tprivate async handleSessionBeforeCompact(event: SessionBeforeCompactEvent): Promise<void> {\n\t\tawait this.consolidateBeforeContextDrop(\"compaction\", event.preparation.messagesToSummarize);\n\t}\n\n\tprivate handleSessionCompact(_event: SessionCompactEvent): void {\n\t\tthis.enqueueBackgroundMaintenance();\n\t}\n\n\tprivate async handleSessionBeforeSwitch(event: SessionBeforeSwitchEvent): Promise<void> {\n\t\tif (event.reason !== \"new\") {\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.consolidateBeforeContextDrop(\"new-session\");\n\t}\n\n\tprivate handleSessionSwitch(event: SessionSwitchEvent): void {\n\t\tif (event.reason !== \"new\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.enqueueBackgroundMaintenance();\n\t}\n\n\tprivate enqueueBackgroundMaintenance(): void {\n\t\tthis.backgroundQueue = this.backgroundQueue\n\t\t\t.then(async () => {\n\t\t\t\tconst result = await runBackgroundMaintenance(this.buildRunOptions([], []));\n\t\t\t\tthis.logBackgroundResult(result);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tlog.logWarning(`[${this.options.channelId}] Background memory maintenance failed`, message);\n\t\t\t});\n\t}\n\n\tprivate logBackgroundResult(result: BackgroundMaintenanceResult): void {\n\t\tif (!result.cleanedMemory && !result.foldedHistory) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst details = [\n\t\t\t`memory cleanup=${result.cleanedMemory ? \"yes\" : \"no\"}`,\n\t\t\t`history fold=${result.foldedHistory ? \"yes\" : \"no\"}`,\n\t\t].join(\", \");\n\t\tlog.logInfo(`[${this.options.channelId}] Background memory maintenance complete: ${details}`);\n\t}\n}\n"]}
1
+ {"version":3,"file":"memory-lifecycle.js","sourceRoot":"","sources":["../src/memory-lifecycle.ts"],"names":[],"mappings":"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","sourcesContent":["import type { AgentMessage } from \"@mariozechner/pi-agent-core\";\nimport type { Api, Model } from \"@mariozechner/pi-ai\";\nimport type {\n\tExtensionFactory,\n\tSessionBeforeCompactEvent,\n\tSessionBeforeSwitchEvent,\n\tSessionCompactEvent,\n\tSessionEntry,\n\tSessionSwitchEvent,\n} from \"@mariozechner/pi-coding-agent\";\nimport type { PipiclawSessionMemorySettings } from \"./context.js\";\nimport * as log from \"./log.js\";\nimport {\n\ttype BackgroundMaintenanceResult,\n\ttype ConsolidationRunOptions,\n\trunBackgroundMaintenance,\n\trunInlineConsolidation,\n} from \"./memory-consolidation.js\";\nimport { updateChannelSessionMemory } from \"./session-memory.js\";\n\nexport type ConsolidationReason = \"compaction\" | \"new-session\";\n\nexport interface MemoryLifecycleOptions {\n\tchannelId: string;\n\tchannelDir: string;\n\tgetMessages: () => AgentMessage[];\n\tgetSessionEntries: () => SessionEntry[];\n\tgetModel: () => Model<Api>;\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tgetSessionMemorySettings: () => PipiclawSessionMemorySettings;\n}\n\nexport class MemoryLifecycle {\n\tprivate backgroundQueue: Promise<void> = Promise.resolve();\n\tprivate turnsSinceSessionUpdate = 0;\n\tprivate toolCallsSinceSessionUpdate = 0;\n\tprivate sessionUpdatePending = false;\n\n\tconstructor(private options: MemoryLifecycleOptions) {}\n\n\tprivate buildRunOptions(messages?: AgentMessage[], sessionEntries?: SessionEntry[]): ConsolidationRunOptions {\n\t\treturn {\n\t\t\tchannelDir: this.options.channelDir,\n\t\t\tmodel: this.options.getModel(),\n\t\t\tresolveApiKey: this.options.resolveApiKey,\n\t\t\tmessages: messages ?? this.options.getMessages(),\n\t\t\tsessionEntries: sessionEntries ?? this.options.getSessionEntries(),\n\t\t};\n\t}\n\n\tcreateExtensionFactory(): ExtensionFactory {\n\t\treturn (pi) => {\n\t\t\tpi.on(\"session_before_compact\", async (event: SessionBeforeCompactEvent) => {\n\t\t\t\tawait this.handleSessionBeforeCompact(event);\n\t\t\t});\n\t\t\tpi.on(\"session_compact\", async (event: SessionCompactEvent) => {\n\t\t\t\tthis.handleSessionCompact(event);\n\t\t\t});\n\t\t\tpi.on(\"session_before_switch\", async (event: SessionBeforeSwitchEvent) => {\n\t\t\t\tawait this.handleSessionBeforeSwitch(event);\n\t\t\t});\n\t\t\tpi.on(\"session_switch\", async (event: SessionSwitchEvent) => {\n\t\t\t\tthis.handleSessionSwitch(event);\n\t\t\t});\n\t\t};\n\t}\n\n\tnoteToolCall(): void {\n\t\tif (!this.options.getSessionMemorySettings().enabled) {\n\t\t\treturn;\n\t\t}\n\t\tthis.toolCallsSinceSessionUpdate++;\n\t}\n\n\tnoteCompletedAssistantTurn(): void {\n\t\tconst settings = this.options.getSessionMemorySettings();\n\t\tif (!settings.enabled) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.turnsSinceSessionUpdate++;\n\t\tif (\n\t\t\tthis.turnsSinceSessionUpdate >= settings.minTurnsBetweenUpdate ||\n\t\t\tthis.toolCallsSinceSessionUpdate >= settings.minToolCallsBetweenUpdate\n\t\t) {\n\t\t\tthis.enqueueSessionMemoryUpdate(\"threshold\");\n\t\t}\n\t}\n\n\tprivate async refreshSessionMemory(reason: \"threshold\" | ConsolidationReason): Promise<boolean> {\n\t\tconst settings = this.options.getSessionMemorySettings();\n\t\tif (!settings.enabled) {\n\t\t\treturn false;\n\t\t}\n\n\t\ttry {\n\t\t\tawait updateChannelSessionMemory({\n\t\t\t\tchannelDir: this.options.channelDir,\n\t\t\t\tmessages: this.options.getMessages(),\n\t\t\t\tmodel: this.options.getModel(),\n\t\t\t\tresolveApiKey: this.options.resolveApiKey,\n\t\t\t});\n\t\t\tthis.turnsSinceSessionUpdate = 0;\n\t\t\tthis.toolCallsSinceSessionUpdate = 0;\n\t\t\tlog.logInfo(`[${this.options.channelId}] Session memory updated (${reason})`);\n\t\t\treturn true;\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logWarning(`[${this.options.channelId}] Session memory update failed (${reason})`, message);\n\t\t\treturn false;\n\t\t}\n\t}\n\n\tprivate enqueueSessionMemoryUpdate(reason: \"threshold\"): void {\n\t\tif (this.sessionUpdatePending) {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.sessionUpdatePending = true;\n\t\tthis.backgroundQueue = this.backgroundQueue\n\t\t\t.then(async () => {\n\t\t\t\tawait this.refreshSessionMemory(reason);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tlog.logWarning(`[${this.options.channelId}] Session memory queue failed`, message);\n\t\t\t})\n\t\t\t.finally(() => {\n\t\t\t\tthis.sessionUpdatePending = false;\n\t\t\t});\n\t}\n\n\tprivate async consolidateBeforeContextDrop(\n\t\treason: ConsolidationReason,\n\t\tmessages?: AgentMessage[],\n\t\tsessionEntries?: SessionEntry[],\n\t): Promise<void> {\n\t\tconst settings = this.options.getSessionMemorySettings();\n\t\tif (\n\t\t\t(reason === \"compaction\" && settings.forceRefreshBeforeCompact) ||\n\t\t\t(reason === \"new-session\" && settings.forceRefreshBeforeNewSession)\n\t\t) {\n\t\t\tawait this.refreshSessionMemory(reason);\n\t\t}\n\n\t\tlog.logInfo(`[${this.options.channelId}] Memory consolidation starting (${reason})`);\n\t\ttry {\n\t\t\tconst result = await runInlineConsolidation(this.buildRunOptions(messages, sessionEntries));\n\t\t\tlog.logInfo(\n\t\t\t\t`[${this.options.channelId}] Memory consolidation finished (${reason}): memory entries=${result.appendedMemoryEntries}, history=${result.appendedHistoryBlock ? \"yes\" : \"no\"}`,\n\t\t\t);\n\t\t} catch (error) {\n\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\tlog.logWarning(`[${this.options.channelId}] Memory consolidation failed (${reason})`, message);\n\t\t}\n\t}\n\n\tprivate async handleSessionBeforeCompact(event: SessionBeforeCompactEvent): Promise<void> {\n\t\tawait this.consolidateBeforeContextDrop(\"compaction\", event.preparation.messagesToSummarize);\n\t}\n\n\tprivate handleSessionCompact(_event: SessionCompactEvent): void {\n\t\tthis.enqueueBackgroundMaintenance();\n\t}\n\n\tprivate async handleSessionBeforeSwitch(event: SessionBeforeSwitchEvent): Promise<void> {\n\t\tif (event.reason !== \"new\") {\n\t\t\treturn;\n\t\t}\n\n\t\tawait this.consolidateBeforeContextDrop(\"new-session\");\n\t}\n\n\tprivate handleSessionSwitch(event: SessionSwitchEvent): void {\n\t\tif (event.reason !== \"new\") {\n\t\t\treturn;\n\t\t}\n\n\t\tthis.enqueueBackgroundMaintenance();\n\t}\n\n\tprivate enqueueBackgroundMaintenance(): void {\n\t\tthis.backgroundQueue = this.backgroundQueue\n\t\t\t.then(async () => {\n\t\t\t\tconst result = await runBackgroundMaintenance(this.buildRunOptions([], []));\n\t\t\t\tthis.logBackgroundResult(result);\n\t\t\t})\n\t\t\t.catch((error: unknown) => {\n\t\t\t\tconst message = error instanceof Error ? error.message : String(error);\n\t\t\t\tlog.logWarning(`[${this.options.channelId}] Background memory maintenance failed`, message);\n\t\t\t});\n\t}\n\n\tprivate logBackgroundResult(result: BackgroundMaintenanceResult): void {\n\t\tif (!result.cleanedMemory && !result.foldedHistory) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst details = [\n\t\t\t`memory cleanup=${result.cleanedMemory ? \"yes\" : \"no\"}`,\n\t\t\t`history fold=${result.foldedHistory ? \"yes\" : \"no\"}`,\n\t\t].join(\", \");\n\t\tlog.logInfo(`[${this.options.channelId}] Background memory maintenance complete: ${details}`);\n\t}\n}\n"]}