@oyasmi/pipiclaw 0.2.2 → 0.3.1

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 (49) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +48 -16
  3. package/dist/agent.d.ts +2 -2
  4. package/dist/agent.d.ts.map +1 -1
  5. package/dist/agent.js +119 -106
  6. package/dist/agent.js.map +1 -1
  7. package/dist/command-extension.d.ts +14 -0
  8. package/dist/command-extension.d.ts.map +1 -0
  9. package/dist/command-extension.js +112 -0
  10. package/dist/command-extension.js.map +1 -0
  11. package/dist/commands.d.ts +1 -1
  12. package/dist/commands.d.ts.map +1 -1
  13. package/dist/commands.js +22 -25
  14. package/dist/commands.js.map +1 -1
  15. package/dist/config-loader.d.ts +2 -6
  16. package/dist/config-loader.d.ts.map +1 -1
  17. package/dist/config-loader.js +4 -60
  18. package/dist/config-loader.js.map +1 -1
  19. package/dist/context.d.ts +4 -23
  20. package/dist/context.d.ts.map +1 -1
  21. package/dist/context.js +7 -122
  22. package/dist/context.js.map +1 -1
  23. package/dist/dingtalk.d.ts.map +1 -1
  24. package/dist/dingtalk.js +5 -0
  25. package/dist/dingtalk.js.map +1 -1
  26. package/dist/main.d.ts.map +1 -1
  27. package/dist/main.js +27 -15
  28. package/dist/main.js.map +1 -1
  29. package/dist/memory-consolidation.d.ts +22 -0
  30. package/dist/memory-consolidation.d.ts.map +1 -0
  31. package/dist/memory-consolidation.js +258 -0
  32. package/dist/memory-consolidation.js.map +1 -0
  33. package/dist/memory-files.d.ts +24 -0
  34. package/dist/memory-files.d.ts.map +1 -0
  35. package/dist/memory-files.js +131 -0
  36. package/dist/memory-files.js.map +1 -0
  37. package/dist/memory-lifecycle.d.ts +27 -0
  38. package/dist/memory-lifecycle.d.ts.map +1 -0
  39. package/dist/memory-lifecycle.js +85 -0
  40. package/dist/memory-lifecycle.js.map +1 -0
  41. package/dist/prompt-builder.d.ts +1 -2
  42. package/dist/prompt-builder.d.ts.map +1 -1
  43. package/dist/prompt-builder.js +35 -88
  44. package/dist/prompt-builder.js.map +1 -1
  45. package/dist/store.d.ts +2 -1
  46. package/dist/store.d.ts.map +1 -1
  47. package/dist/store.js +2 -1
  48. package/dist/store.js.map +1 -1
  49. package/package.json +2 -2
@@ -0,0 +1,258 @@
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";
4
+ const INLINE_TRANSCRIPT_MAX_CHARS = 28_000;
5
+ const MEMORY_CLEANUP_LENGTH_THRESHOLD = 8_000;
6
+ const MEMORY_UPDATE_BLOCK_THRESHOLD = 6;
7
+ const HISTORY_LENGTH_THRESHOLD = 16_000;
8
+ const HISTORY_BLOCK_THRESHOLD = 8;
9
+ const HISTORY_RECENT_BLOCKS_TO_KEEP = 4;
10
+ const INLINE_CONSOLIDATION_SYSTEM_PROMPT = `You are a runtime memory consolidation worker for Pipiclaw.
11
+
12
+ Return strict JSON only. Do not wrap in Markdown fences.
13
+
14
+ Output schema:
15
+ {
16
+ "memoryEntries": ["string"],
17
+ "historyBlock": "string"
18
+ }
19
+
20
+ Rules:
21
+ - memoryEntries: concise durable facts, decisions, preferences, constraints, current work state, or open loops that should survive compaction.
22
+ - Each memoryEntries item must be a standalone sentence fragment suitable for a Markdown bullet without the bullet prefix.
23
+ - Do not include raw transcript quotes unless essential.
24
+ - Do not include ephemeral chatter, obvious one-shot acknowledgements, or formatting instructions.
25
+ - historyBlock: concise Markdown summarizing the conversation chunk for later recovery.
26
+ - Prefer short bullets and short paragraphs.
27
+ - If there is nothing worth storing, return empty values.`;
28
+ const MEMORY_CLEANUP_SYSTEM_PROMPT = `You are rewriting a Pipiclaw channel MEMORY.md file.
29
+
30
+ Return Markdown only. Do not use code fences.
31
+
32
+ Goals:
33
+ - Keep only durable and useful channel memory.
34
+ - Remove outdated entries, duplicates, and verbose phrasing.
35
+ - Organize the result with stable sections where relevant.
36
+ - Prefer concise bullets over prose.
37
+
38
+ Suggested sections:
39
+ - ## Identity / Participants
40
+ - ## Preferences
41
+ - ## Ongoing Work
42
+ - ## Constraints
43
+ - ## Decisions
44
+ - ## Open Loops
45
+
46
+ Omit empty sections.`;
47
+ const HISTORY_FOLDING_SYSTEM_PROMPT = `You are folding older HISTORY.md blocks for Pipiclaw.
48
+
49
+ Return Markdown only. Do not use code fences.
50
+
51
+ Goals:
52
+ - Compress older history blocks into one concise summary block.
53
+ - Keep important decisions, milestones, and unresolved outcomes.
54
+ - Remove redundancy and transcript-like detail.
55
+ - Preserve a chronological narrative at a high level.`;
56
+ function normalizeText(text) {
57
+ return text.replace(/\r/g, "").trim();
58
+ }
59
+ function clipTranscript(text, maxChars) {
60
+ const normalized = normalizeText(text);
61
+ if (normalized.length <= maxChars) {
62
+ return normalized;
63
+ }
64
+ const headChars = Math.floor(maxChars * 0.35);
65
+ const tailChars = maxChars - headChars;
66
+ return `${normalized.slice(0, headChars)}\n\n[... omitted middle section ...]\n\n${normalized.slice(-tailChars)}`;
67
+ }
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
+ function parseConsolidationResponse(text) {
85
+ const parsed = JSON.parse(extractJsonObject(text));
86
+ return {
87
+ memoryEntries: Array.isArray(parsed.memoryEntries)
88
+ ? parsed.memoryEntries
89
+ .map((entry) => (typeof entry === "string" ? entry.trim() : ""))
90
+ .filter((entry) => entry.length > 0)
91
+ : [],
92
+ historyBlock: typeof parsed.historyBlock === "string" ? parsed.historyBlock.trim() : "",
93
+ };
94
+ }
95
+ function getLatestCompactionBoundary(entries) {
96
+ const latestCompaction = getLatestCompactionEntry(entries);
97
+ if (!latestCompaction) {
98
+ return 0;
99
+ }
100
+ const boundaryIndex = entries.findIndex((entry) => entry.id === latestCompaction.firstKeptEntryId);
101
+ return boundaryIndex >= 0 ? boundaryIndex : 0;
102
+ }
103
+ function isMessage(entry) {
104
+ return entry.type === "message";
105
+ }
106
+ function isStandardAgentMessage(message) {
107
+ return (typeof message === "object" &&
108
+ message !== null &&
109
+ "role" in message &&
110
+ (message.role === "user" || message.role === "assistant" || message.role === "toolResult"));
111
+ }
112
+ function buildMessagesForConsolidation(messages) {
113
+ return messages.filter(isStandardAgentMessage);
114
+ }
115
+ function extractMessagesFromSessionEntries(entries) {
116
+ return entries.filter(isMessage).map((entry) => entry.message);
117
+ }
118
+ function hasMeaningfulMessages(messages) {
119
+ let meaningfulCount = 0;
120
+ for (const message of messages) {
121
+ if (message.role === "user") {
122
+ const text = typeof message.content === "string"
123
+ ? message.content
124
+ : message.content.map((part) => (part.type === "text" ? part.text : "[image]")).join("\n");
125
+ if (text.trim())
126
+ meaningfulCount++;
127
+ }
128
+ else if (message.role === "assistant") {
129
+ const text = message.content
130
+ .filter((part) => part.type === "text")
131
+ .map((part) => part.text)
132
+ .join("\n");
133
+ if (text.trim())
134
+ meaningfulCount++;
135
+ }
136
+ if (meaningfulCount >= 2) {
137
+ return true;
138
+ }
139
+ }
140
+ return false;
141
+ }
142
+ function countMatchingSectionHeadings(content, prefix) {
143
+ return splitMarkdownSections(content).filter((section) => section.heading.startsWith(prefix)).length;
144
+ }
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,
156
+ });
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();
171
+ }
172
+ async function buildInlineConsolidationResponse(options, messages) {
173
+ const transcript = clipTranscript(serializeConversation(messages), INLINE_TRANSCRIPT_MAX_CHARS);
174
+ const currentMemory = clipTranscript(await readChannelMemory(options.channelDir), 8_000);
175
+ const currentHistory = clipTranscript(await readChannelHistory(options.channelDir), 8_000);
176
+ const prompt = `Channel memory file:
177
+ ${currentMemory || "(empty)"}
178
+
179
+ Channel history file:
180
+ ${currentHistory || "(empty)"}
181
+
182
+ Conversation chunk to persist:
183
+ ${transcript || "(empty)"}`;
184
+ const rawResponse = await runWorkerPrompt(options.model, options.resolveApiKey, INLINE_CONSOLIDATION_SYSTEM_PROMPT, prompt);
185
+ return parseConsolidationResponse(rawResponse);
186
+ }
187
+ export async function runInlineConsolidation(options) {
188
+ const sourceEntries = options.sessionEntries ?? [];
189
+ const relevantEntries = sourceEntries.length > 0 ? sourceEntries.slice(getLatestCompactionBoundary(sourceEntries)) : sourceEntries;
190
+ const relevantMessages = buildMessagesForConsolidation(relevantEntries.length > 0 ? extractMessagesFromSessionEntries(relevantEntries) : options.messages);
191
+ if (!hasMeaningfulMessages(relevantMessages)) {
192
+ return { skipped: true, appendedMemoryEntries: 0, appendedHistoryBlock: false };
193
+ }
194
+ const response = await buildInlineConsolidationResponse(options, relevantMessages);
195
+ const timestamp = new Date().toISOString();
196
+ if (response.memoryEntries.length > 0) {
197
+ await appendChannelMemoryUpdate(options.channelDir, {
198
+ timestamp,
199
+ entries: response.memoryEntries,
200
+ });
201
+ }
202
+ if (response.historyBlock.trim()) {
203
+ await appendChannelHistoryBlock(options.channelDir, {
204
+ timestamp,
205
+ content: response.historyBlock,
206
+ });
207
+ }
208
+ return {
209
+ skipped: false,
210
+ appendedMemoryEntries: response.memoryEntries.length,
211
+ appendedHistoryBlock: response.historyBlock.trim().length > 0,
212
+ };
213
+ }
214
+ async function cleanupChannelMemory(options, currentMemory) {
215
+ if (currentMemory.length < MEMORY_CLEANUP_LENGTH_THRESHOLD &&
216
+ countMatchingSectionHeadings(currentMemory, "Update ") < MEMORY_UPDATE_BLOCK_THRESHOLD) {
217
+ return false;
218
+ }
219
+ const prompt = `Current MEMORY.md:
220
+ ${currentMemory}`;
221
+ const nextMemory = await runWorkerPrompt(options.model, options.resolveApiKey, MEMORY_CLEANUP_SYSTEM_PROMPT, prompt);
222
+ await rewriteChannelMemory(options.channelDir, nextMemory);
223
+ return true;
224
+ }
225
+ async function foldChannelHistory(options, currentHistory) {
226
+ const sections = splitMarkdownSections(currentHistory);
227
+ if (currentHistory.length < HISTORY_LENGTH_THRESHOLD && sections.length < HISTORY_BLOCK_THRESHOLD) {
228
+ return false;
229
+ }
230
+ if (sections.length <= HISTORY_RECENT_BLOCKS_TO_KEEP) {
231
+ return false;
232
+ }
233
+ const olderSections = sections.slice(0, -HISTORY_RECENT_BLOCKS_TO_KEEP);
234
+ const recentSections = sections.slice(-HISTORY_RECENT_BLOCKS_TO_KEEP);
235
+ const prompt = `Older history blocks to fold:
236
+ ${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);
238
+ const foldedHeading = `## Folded History Through ${olderSections[olderSections.length - 1]?.heading ?? new Date().toISOString()}`;
239
+ const rebuiltHistory = [
240
+ "# Channel History",
241
+ "",
242
+ foldedHeading,
243
+ "",
244
+ normalizeText(foldedSummary),
245
+ "",
246
+ ...recentSections.flatMap((section) => [`## ${section.heading}`, "", normalizeText(section.content), ""]),
247
+ ].join("\n");
248
+ await rewriteChannelHistory(options.channelDir, rebuiltHistory);
249
+ return true;
250
+ }
251
+ export async function runBackgroundMaintenance(options) {
252
+ const currentMemory = await readChannelMemory(options.channelDir);
253
+ const currentHistory = await readChannelHistory(options.channelDir);
254
+ const cleanedMemory = await cleanupChannelMemory(options, currentMemory);
255
+ const foldedHistory = await foldChannelHistory(options, currentHistory);
256
+ return { cleanedMemory, foldedHistory };
257
+ }
258
+ //# sourceMappingURL=memory-consolidation.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,24 @@
1
+ export interface MemoryUpdateBlock {
2
+ timestamp: string;
3
+ entries: string[];
4
+ }
5
+ export interface HistoryBlock {
6
+ timestamp: string;
7
+ content: string;
8
+ }
9
+ export declare function getChannelMemoryPath(channelDir: string): string;
10
+ export declare function getChannelHistoryPath(channelDir: string): string;
11
+ export declare function ensureChannelMemoryFiles(channelDir: string): Promise<void>;
12
+ export declare function ensureChannelMemoryFilesSync(channelDir: string): void;
13
+ export declare function readChannelMemory(channelDir: string): Promise<string>;
14
+ export declare function readChannelHistory(channelDir: string): Promise<string>;
15
+ export declare function rewriteChannelMemory(channelDir: string, content: string): Promise<void>;
16
+ export declare function rewriteChannelHistory(channelDir: string, content: string): Promise<void>;
17
+ export declare function appendChannelMemoryUpdate(channelDir: string, block: MemoryUpdateBlock): Promise<void>;
18
+ export declare function appendChannelHistoryBlock(channelDir: string, block: HistoryBlock): Promise<void>;
19
+ export interface MarkdownSection {
20
+ heading: string;
21
+ content: string;
22
+ }
23
+ export declare function splitMarkdownSections(content: string): MarkdownSection[];
24
+ //# sourceMappingURL=memory-files.d.ts.map
@@ -0,0 +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"]}
@@ -0,0 +1,131 @@
1
+ import { existsSync, mkdirSync, writeFileSync } from "fs";
2
+ import { mkdir, readFile, rename, writeFile } from "fs/promises";
3
+ import { dirname, join } from "path";
4
+ const DEFAULT_CHANNEL_MEMORY = `# Channel Memory
5
+
6
+ This file stores durable channel-specific memory.
7
+
8
+ - It is not preloaded into session context.
9
+ - Read it on demand when prior decisions, preferences, or long-running work matter.
10
+ - The runtime may append updates here during consolidation.
11
+
12
+ ## Durable Facts
13
+
14
+ <!-- Stable facts, preferences, and ongoing commitments can accumulate here. -->
15
+ `;
16
+ const DEFAULT_CHANNEL_HISTORY = `# Channel History
17
+
18
+ This file stores summarized older channel history.
19
+
20
+ - It is not preloaded into session context.
21
+ - Read it on demand when older context matters.
22
+ - The runtime may append and fold history blocks here during consolidation.
23
+ `;
24
+ function normalizeContent(content) {
25
+ return content.trim().length > 0 ? `${content.trim()}\n` : "";
26
+ }
27
+ async function writeAtomically(path, content) {
28
+ await mkdir(dirname(path), { recursive: true });
29
+ const tempPath = `${path}.tmp`;
30
+ await writeFile(tempPath, content, "utf-8");
31
+ await rename(tempPath, path);
32
+ }
33
+ function ensureTrailingNewlines(content) {
34
+ return content.trimEnd().length > 0 ? `${content.trimEnd()}\n\n` : "";
35
+ }
36
+ export function getChannelMemoryPath(channelDir) {
37
+ return join(channelDir, "MEMORY.md");
38
+ }
39
+ export function getChannelHistoryPath(channelDir) {
40
+ return join(channelDir, "HISTORY.md");
41
+ }
42
+ export async function ensureChannelMemoryFiles(channelDir) {
43
+ ensureChannelMemoryFilesSync(channelDir);
44
+ }
45
+ export function ensureChannelMemoryFilesSync(channelDir) {
46
+ const memoryPath = getChannelMemoryPath(channelDir);
47
+ const historyPath = getChannelHistoryPath(channelDir);
48
+ mkdirSync(channelDir, { recursive: true });
49
+ if (!existsSync(memoryPath)) {
50
+ writeFileSync(memoryPath, DEFAULT_CHANNEL_MEMORY, "utf-8");
51
+ }
52
+ if (!existsSync(historyPath)) {
53
+ writeFileSync(historyPath, DEFAULT_CHANNEL_HISTORY, "utf-8");
54
+ }
55
+ }
56
+ async function readTextFile(path) {
57
+ if (!existsSync(path)) {
58
+ return "";
59
+ }
60
+ return readFile(path, "utf-8");
61
+ }
62
+ export async function readChannelMemory(channelDir) {
63
+ return readTextFile(getChannelMemoryPath(channelDir));
64
+ }
65
+ export async function readChannelHistory(channelDir) {
66
+ return readTextFile(getChannelHistoryPath(channelDir));
67
+ }
68
+ export async function rewriteChannelMemory(channelDir, content) {
69
+ await ensureChannelMemoryFiles(channelDir);
70
+ const nextContent = normalizeContent(content) || DEFAULT_CHANNEL_MEMORY;
71
+ await writeAtomically(getChannelMemoryPath(channelDir), nextContent);
72
+ }
73
+ export async function rewriteChannelHistory(channelDir, content) {
74
+ await ensureChannelMemoryFiles(channelDir);
75
+ const nextContent = normalizeContent(content) || DEFAULT_CHANNEL_HISTORY;
76
+ await writeAtomically(getChannelHistoryPath(channelDir), nextContent);
77
+ }
78
+ export async function appendChannelMemoryUpdate(channelDir, block) {
79
+ if (block.entries.length === 0) {
80
+ return;
81
+ }
82
+ await ensureChannelMemoryFiles(channelDir);
83
+ const path = getChannelMemoryPath(channelDir);
84
+ const existing = await readTextFile(path);
85
+ const renderedBlock = [`## Update ${block.timestamp}`, ...block.entries.map((entry) => `- ${entry.trim()}`)].join("\n");
86
+ await writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\n`);
87
+ }
88
+ export async function appendChannelHistoryBlock(channelDir, block) {
89
+ const trimmedContent = block.content.trim();
90
+ if (!trimmedContent) {
91
+ return;
92
+ }
93
+ await ensureChannelMemoryFiles(channelDir);
94
+ const path = getChannelHistoryPath(channelDir);
95
+ const existing = await readTextFile(path);
96
+ const renderedBlock = [`## ${block.timestamp}`, trimmedContent].join("\n\n");
97
+ await writeAtomically(path, `${ensureTrailingNewlines(existing)}${renderedBlock}\n`);
98
+ }
99
+ export function splitMarkdownSections(content) {
100
+ const normalized = content.replace(/\r/g, "").trim();
101
+ if (!normalized) {
102
+ return [];
103
+ }
104
+ const lines = normalized.split("\n");
105
+ const sections = [];
106
+ let currentHeading = "";
107
+ let currentLines = [];
108
+ const flush = () => {
109
+ if (!currentHeading) {
110
+ return;
111
+ }
112
+ sections.push({
113
+ heading: currentHeading,
114
+ content: currentLines.join("\n").trim(),
115
+ });
116
+ };
117
+ for (const line of lines) {
118
+ if (line.startsWith("## ")) {
119
+ flush();
120
+ currentHeading = line.slice(3).trim();
121
+ currentLines = [];
122
+ continue;
123
+ }
124
+ if (currentHeading) {
125
+ currentLines.push(line);
126
+ }
127
+ }
128
+ flush();
129
+ return sections;
130
+ }
131
+ //# sourceMappingURL=memory-files.js.map
@@ -0,0 +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"]}
@@ -0,0 +1,27 @@
1
+ import type { AgentMessage } from "@mariozechner/pi-agent-core";
2
+ import type { Api, Model } from "@mariozechner/pi-ai";
3
+ import type { ExtensionFactory, SessionEntry } from "@mariozechner/pi-coding-agent";
4
+ export type ConsolidationReason = "compaction" | "new-session";
5
+ export interface MemoryLifecycleOptions {
6
+ channelId: string;
7
+ channelDir: string;
8
+ getMessages: () => AgentMessage[];
9
+ getSessionEntries: () => SessionEntry[];
10
+ getModel: () => Model<Api>;
11
+ resolveApiKey: (model: Model<Api>) => Promise<string>;
12
+ }
13
+ export declare class MemoryLifecycle {
14
+ private options;
15
+ private backgroundQueue;
16
+ constructor(options: MemoryLifecycleOptions);
17
+ private buildRunOptions;
18
+ createExtensionFactory(): ExtensionFactory;
19
+ private consolidateBeforeContextDrop;
20
+ private handleSessionBeforeCompact;
21
+ private handleSessionCompact;
22
+ private handleSessionBeforeSwitch;
23
+ private handleSessionSwitch;
24
+ private enqueueBackgroundMaintenance;
25
+ private logBackgroundResult;
26
+ }
27
+ //# sourceMappingURL=memory-lifecycle.d.ts.map
@@ -0,0 +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"]}
@@ -0,0 +1,85 @@
1
+ import * as log from "./log.js";
2
+ import { runBackgroundMaintenance, runInlineConsolidation, } from "./memory-consolidation.js";
3
+ export class MemoryLifecycle {
4
+ options;
5
+ backgroundQueue = Promise.resolve();
6
+ constructor(options) {
7
+ this.options = options;
8
+ }
9
+ buildRunOptions(messages, sessionEntries) {
10
+ return {
11
+ channelDir: this.options.channelDir,
12
+ model: this.options.getModel(),
13
+ resolveApiKey: this.options.resolveApiKey,
14
+ messages: messages ?? this.options.getMessages(),
15
+ sessionEntries: sessionEntries ?? this.options.getSessionEntries(),
16
+ };
17
+ }
18
+ createExtensionFactory() {
19
+ return (pi) => {
20
+ pi.on("session_before_compact", async (event) => {
21
+ await this.handleSessionBeforeCompact(event);
22
+ });
23
+ pi.on("session_compact", async (event) => {
24
+ this.handleSessionCompact(event);
25
+ });
26
+ pi.on("session_before_switch", async (event) => {
27
+ await this.handleSessionBeforeSwitch(event);
28
+ });
29
+ pi.on("session_switch", async (event) => {
30
+ this.handleSessionSwitch(event);
31
+ });
32
+ };
33
+ }
34
+ async consolidateBeforeContextDrop(reason, messages, sessionEntries) {
35
+ log.logInfo(`[${this.options.channelId}] Memory consolidation starting (${reason})`);
36
+ try {
37
+ const result = await runInlineConsolidation(this.buildRunOptions(messages, sessionEntries));
38
+ log.logInfo(`[${this.options.channelId}] Memory consolidation finished (${reason}): memory entries=${result.appendedMemoryEntries}, history=${result.appendedHistoryBlock ? "yes" : "no"}`);
39
+ }
40
+ catch (error) {
41
+ const message = error instanceof Error ? error.message : String(error);
42
+ log.logWarning(`[${this.options.channelId}] Memory consolidation failed (${reason})`, message);
43
+ }
44
+ }
45
+ async handleSessionBeforeCompact(event) {
46
+ await this.consolidateBeforeContextDrop("compaction", event.preparation.messagesToSummarize);
47
+ }
48
+ handleSessionCompact(_event) {
49
+ this.enqueueBackgroundMaintenance();
50
+ }
51
+ async handleSessionBeforeSwitch(event) {
52
+ if (event.reason !== "new") {
53
+ return;
54
+ }
55
+ await this.consolidateBeforeContextDrop("new-session");
56
+ }
57
+ handleSessionSwitch(event) {
58
+ if (event.reason !== "new") {
59
+ return;
60
+ }
61
+ this.enqueueBackgroundMaintenance();
62
+ }
63
+ enqueueBackgroundMaintenance() {
64
+ this.backgroundQueue = this.backgroundQueue
65
+ .then(async () => {
66
+ const result = await runBackgroundMaintenance(this.buildRunOptions([], []));
67
+ this.logBackgroundResult(result);
68
+ })
69
+ .catch((error) => {
70
+ const message = error instanceof Error ? error.message : String(error);
71
+ log.logWarning(`[${this.options.channelId}] Background memory maintenance failed`, message);
72
+ });
73
+ }
74
+ logBackgroundResult(result) {
75
+ if (!result.cleanedMemory && !result.foldedHistory) {
76
+ return;
77
+ }
78
+ const details = [
79
+ `memory cleanup=${result.cleanedMemory ? "yes" : "no"}`,
80
+ `history fold=${result.foldedHistory ? "yes" : "no"}`,
81
+ ].join(", ");
82
+ log.logInfo(`[${this.options.channelId}] Background memory maintenance complete: ${details}`);
83
+ }
84
+ }
85
+ //# sourceMappingURL=memory-lifecycle.js.map
@@ -0,0 +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"]}