@oyasmi/pipiclaw 0.4.0 → 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.
- package/README.md +43 -5
- package/dist/agent.d.ts.map +1 -1
- package/dist/agent.js +156 -57
- package/dist/agent.js.map +1 -1
- package/dist/context.d.ts +18 -0
- package/dist/context.d.ts.map +1 -1
- package/dist/context.js +26 -0
- package/dist/context.js.map +1 -1
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/llm-json.d.ts +7 -0
- package/dist/llm-json.d.ts.map +1 -0
- package/dist/llm-json.js +77 -0
- package/dist/llm-json.js.map +1 -0
- package/dist/markdown-sections.d.ts +6 -0
- package/dist/markdown-sections.d.ts.map +1 -0
- package/dist/markdown-sections.js +34 -0
- package/dist/markdown-sections.js.map +1 -0
- package/dist/memory-candidates.d.ts +21 -0
- package/dist/memory-candidates.d.ts.map +1 -0
- package/dist/memory-candidates.js +126 -0
- package/dist/memory-candidates.js.map +1 -0
- package/dist/memory-consolidation.d.ts.map +1 -1
- package/dist/memory-consolidation.js +28 -49
- package/dist/memory-consolidation.js.map +1 -1
- package/dist/memory-files.d.ts +3 -0
- package/dist/memory-files.d.ts.map +1 -1
- package/dist/memory-files.js +51 -0
- package/dist/memory-files.js.map +1 -1
- package/dist/memory-lifecycle.d.ts +9 -0
- package/dist/memory-lifecycle.d.ts.map +1 -1
- package/dist/memory-lifecycle.js +66 -0
- package/dist/memory-lifecycle.js.map +1 -1
- package/dist/memory-recall.d.ts +29 -0
- package/dist/memory-recall.d.ts.map +1 -0
- package/dist/memory-recall.js +218 -0
- package/dist/memory-recall.js.map +1 -0
- package/dist/prompt-builder.d.ts.map +1 -1
- package/dist/prompt-builder.js +7 -2
- package/dist/prompt-builder.js.map +1 -1
- package/dist/session-memory-files.d.ts +2 -0
- package/dist/session-memory-files.d.ts.map +1 -0
- package/dist/session-memory-files.js +2 -0
- package/dist/session-memory-files.js.map +1 -0
- package/dist/session-memory.d.ts +22 -0
- package/dist/session-memory.d.ts.map +1 -0
- package/dist/session-memory.js +274 -0
- package/dist/session-memory.js.map +1 -0
- package/dist/sidecar-worker.d.ts +27 -0
- package/dist/sidecar-worker.d.ts.map +1 -0
- package/dist/sidecar-worker.js +105 -0
- package/dist/sidecar-worker.js.map +1 -0
- package/dist/sub-agents.d.ts +10 -0
- package/dist/sub-agents.d.ts.map +1 -1
- package/dist/sub-agents.js +90 -0
- package/dist/sub-agents.js.map +1 -1
- package/dist/tools/index.d.ts +3 -0
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +2 -0
- package/dist/tools/index.js.map +1 -1
- package/dist/tools/subagent.d.ts +6 -0
- package/dist/tools/subagent.d.ts.map +1 -1
- package/dist/tools/subagent.js +127 -12
- package/dist/tools/subagent.js.map +1 -1
- package/docs/improve-memory/design.md +537 -0
- package/docs/improve-memory/interfaces-and-tests.md +473 -0
- package/docs/improve-memory/spec.md +357 -0
- package/docs/memory-rfc.md +7 -1
- package/docs/proj-review.md +188 -0
- package/docs/test-supplementation-plan.md +553 -0
- package/package.json +3 -1
package/dist/memory-files.js
CHANGED
|
@@ -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;
|
package/dist/memory-files.js.map
CHANGED
|
@@ -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;
|
|
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"}
|
package/dist/memory-lifecycle.js
CHANGED
|
@@ -1,9 +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
5
|
constructor(options) {
|
|
5
6
|
this.options = options;
|
|
6
7
|
this.backgroundQueue = Promise.resolve();
|
|
8
|
+
this.turnsSinceSessionUpdate = 0;
|
|
9
|
+
this.toolCallsSinceSessionUpdate = 0;
|
|
10
|
+
this.sessionUpdatePending = false;
|
|
7
11
|
}
|
|
8
12
|
buildRunOptions(messages, sessionEntries) {
|
|
9
13
|
return {
|
|
@@ -30,7 +34,69 @@ export class MemoryLifecycle {
|
|
|
30
34
|
});
|
|
31
35
|
};
|
|
32
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
|
+
}
|
|
33
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
|
+
}
|
|
34
100
|
log.logInfo(`[${this.options.channelId}] Memory consolidation starting (${reason})`);
|
|
35
101
|
try {
|
|
36
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":"AAUA,OAAO,KAAK,GAAG,MAAM,UAAU,CAAC;AAChC,OAAO,EAGN,wBAAwB,EACxB,sBAAsB,GACtB,MAAM,2BAA2B,CAAC;AAanC,MAAM,OAAO,eAAe;IAG3B,YAAoB,OAA+B;QAA/B,YAAO,GAAP,OAAO,CAAwB;QAF3C,oBAAe,GAAkB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEL,CAAC;IAE/C,eAAe,CAAC,QAAyB,EAAE,cAA+B;QACjF,OAAO;YACN,UAAU,EAAE,IAAI,CAAC,OAAO,CAAC,UAAU;YACnC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE;YAC9B,aAAa,EAAE,IAAI,CAAC,OAAO,CAAC,aAAa;YACzC,QAAQ,EAAE,QAAQ,IAAI,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;YAChD,cAAc,EAAE,cAAc,IAAI,IAAI,CAAC,OAAO,CAAC,iBAAiB,EAAE;SAClE,CAAC;IACH,CAAC;IAED,sBAAsB;QACrB,OAAO,CAAC,EAAE,EAAE,EAAE;YACb,EAAE,CAAC,EAAE,CAAC,wBAAwB,EAAE,KAAK,EAAE,KAAgC,EAAE,EAAE;gBAC1E,MAAM,IAAI,CAAC,0BAA0B,CAAC,KAAK,CAAC,CAAC;YAC9C,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,EAAE,CAAC,iBAAiB,EAAE,KAAK,EAAE,KAA0B,EAAE,EAAE;gBAC7D,IAAI,CAAC,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAClC,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,EAAE,CAAC,uBAAuB,EAAE,KAAK,EAAE,KAA+B,EAAE,EAAE;gBACxE,MAAM,IAAI,CAAC,yBAAyB,CAAC,KAAK,CAAC,CAAC;YAC7C,CAAC,CAAC,CAAC;YACH,EAAE,CAAC,EAAE,CAAC,gBAAgB,EAAE,KAAK,EAAE,KAAyB,EAAE,EAAE;gBAC3D,IAAI,CAAC,mBAAmB,CAAC,KAAK,CAAC,CAAC;YACjC,CAAC,CAAC,CAAC;QACJ,CAAC,CAAC;IACH,CAAC;IAEO,KAAK,CAAC,4BAA4B,CACzC,MAA2B,EAC3B,QAAyB,EACzB,cAA+B;QAE/B,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,oCAAoC,MAAM,GAAG,CAAC,CAAC;QACrF,IAAI,CAAC;YACJ,MAAM,MAAM,GAAG,MAAM,sBAAsB,CAAC,IAAI,CAAC,eAAe,CAAC,QAAQ,EAAE,cAAc,CAAC,CAAC,CAAC;YAC5F,GAAG,CAAC,OAAO,CACV,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,oCAAoC,MAAM,qBAAqB,MAAM,CAAC,qBAAqB,aAAa,MAAM,CAAC,oBAAoB,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAC9K,CAAC;QACH,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,kCAAkC,MAAM,GAAG,EAAE,OAAO,CAAC,CAAC;QAChG,CAAC;IACF,CAAC;IAEO,KAAK,CAAC,0BAA0B,CAAC,KAAgC;QACxE,MAAM,IAAI,CAAC,4BAA4B,CAAC,YAAY,EAAE,KAAK,CAAC,WAAW,CAAC,mBAAmB,CAAC,CAAC;IAC9F,CAAC;IAEO,oBAAoB,CAAC,MAA2B;QACvD,IAAI,CAAC,4BAA4B,EAAE,CAAC;IACrC,CAAC;IAEO,KAAK,CAAC,yBAAyB,CAAC,KAA+B;QACtE,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC5B,OAAO;QACR,CAAC;QAED,MAAM,IAAI,CAAC,4BAA4B,CAAC,aAAa,CAAC,CAAC;IACxD,CAAC;IAEO,mBAAmB,CAAC,KAAyB;QACpD,IAAI,KAAK,CAAC,MAAM,KAAK,KAAK,EAAE,CAAC;YAC5B,OAAO;QACR,CAAC;QAED,IAAI,CAAC,4BAA4B,EAAE,CAAC;IACrC,CAAC;IAEO,4BAA4B;QACnC,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe;aACzC,IAAI,CAAC,KAAK,IAAI,EAAE;YAChB,MAAM,MAAM,GAAG,MAAM,wBAAwB,CAAC,IAAI,CAAC,eAAe,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC;YAC5E,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,CAAC;QAClC,CAAC,CAAC;aACD,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YACzB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;YACvE,GAAG,CAAC,UAAU,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,wCAAwC,EAAE,OAAO,CAAC,CAAC;QAC7F,CAAC,CAAC,CAAC;IACL,CAAC;IAEO,mBAAmB,CAAC,MAAmC;QAC9D,IAAI,CAAC,MAAM,CAAC,aAAa,IAAI,CAAC,MAAM,CAAC,aAAa,EAAE,CAAC;YACpD,OAAO;QACR,CAAC;QAED,MAAM,OAAO,GAAG;YACf,kBAAkB,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;YACvD,gBAAgB,MAAM,CAAC,aAAa,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE;SACrD,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACb,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,6CAA6C,OAAO,EAAE,CAAC,CAAC;IAC/F,CAAC;CACD","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"]}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { Api, Model } from "@mariozechner/pi-ai";
|
|
2
|
+
import { type MemoryCandidate, type MemoryCandidateCache } from "./memory-candidates.js";
|
|
3
|
+
export interface RecallRequest {
|
|
4
|
+
query: string;
|
|
5
|
+
workspaceDir: string;
|
|
6
|
+
channelDir: string;
|
|
7
|
+
allowedSources?: MemoryCandidate["source"][];
|
|
8
|
+
maxCandidates: number;
|
|
9
|
+
maxInjected: number;
|
|
10
|
+
maxChars: number;
|
|
11
|
+
rerankWithModel: boolean;
|
|
12
|
+
autoRerank?: boolean;
|
|
13
|
+
model: Model<Api>;
|
|
14
|
+
resolveApiKey: (model: Model<Api>) => Promise<string>;
|
|
15
|
+
candidateCache?: MemoryCandidateCache;
|
|
16
|
+
}
|
|
17
|
+
export interface RecalledMemory {
|
|
18
|
+
source: MemoryCandidate["source"];
|
|
19
|
+
path: string;
|
|
20
|
+
title: string;
|
|
21
|
+
content: string;
|
|
22
|
+
score: number;
|
|
23
|
+
}
|
|
24
|
+
export interface RecallResult {
|
|
25
|
+
items: RecalledMemory[];
|
|
26
|
+
renderedText: string;
|
|
27
|
+
}
|
|
28
|
+
export declare function recallRelevantMemory(request: RecallRequest): Promise<RecallResult>;
|
|
29
|
+
//# sourceMappingURL=memory-recall.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-recall.d.ts","sourceRoot":"","sources":["../src/memory-recall.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAEtD,OAAO,EAAyB,KAAK,eAAe,EAAE,KAAK,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAGhH,MAAM,WAAW,aAAa;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,UAAU,EAAE,MAAM,CAAC;IACnB,cAAc,CAAC,EAAE,eAAe,CAAC,QAAQ,CAAC,EAAE,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC;IAClB,aAAa,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACtD,cAAc,CAAC,EAAE,oBAAoB,CAAC;CACtC;AAED,MAAM,WAAW,cAAc;IAC9B,MAAM,EAAE,eAAe,CAAC,QAAQ,CAAC,CAAC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;CACrB;AAsMD,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAkDxF"}
|
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
import { parseJsonObject } from "./llm-json.js";
|
|
2
|
+
import { buildMemoryCandidates } from "./memory-candidates.js";
|
|
3
|
+
import { runSidecarTask } from "./sidecar-worker.js";
|
|
4
|
+
const RERANK_SYSTEM_PROMPT = `You are selecting which memory snippets are most relevant to the current user turn.
|
|
5
|
+
|
|
6
|
+
Return strict JSON only:
|
|
7
|
+
{
|
|
8
|
+
"selectedIds": ["candidate-id"]
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
Rules:
|
|
12
|
+
- Select only snippets that are clearly useful for answering the current turn.
|
|
13
|
+
- Prefer current work state, constraints, active files, recent corrections, and durable decisions.
|
|
14
|
+
- If nothing is clearly useful, return an empty array.
|
|
15
|
+
- Do not rewrite the candidates. Only return candidate ids.`;
|
|
16
|
+
const HAN_REGEX = /\p{Script=Han}/u;
|
|
17
|
+
const TOKEN_PART_REGEX = /[\p{Script=Han}]+|[\p{L}\p{N}_./-]+/gu;
|
|
18
|
+
const MEMORY_RECALL_RERANK_TIMEOUT_MS = 5_000;
|
|
19
|
+
function containsHanText(text) {
|
|
20
|
+
return HAN_REGEX.test(text);
|
|
21
|
+
}
|
|
22
|
+
function tokenizeHanPart(part) {
|
|
23
|
+
const chars = Array.from(part);
|
|
24
|
+
const tokens = [];
|
|
25
|
+
for (const size of [2, 3]) {
|
|
26
|
+
if (chars.length < size) {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
for (let index = 0; index <= chars.length - size; index++) {
|
|
30
|
+
tokens.push(chars.slice(index, index + size).join(""));
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return tokens;
|
|
34
|
+
}
|
|
35
|
+
function tokenize(text) {
|
|
36
|
+
const parts = text.toLowerCase().match(TOKEN_PART_REGEX) ?? [];
|
|
37
|
+
const tokens = [];
|
|
38
|
+
for (const part of parts) {
|
|
39
|
+
if (containsHanText(part)) {
|
|
40
|
+
tokens.push(...tokenizeHanPart(part));
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
if (part.length >= 2) {
|
|
44
|
+
tokens.push(part);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return Array.from(new Set(tokens));
|
|
48
|
+
}
|
|
49
|
+
function computeTokenOverlapScore(queryTokens, text, weight) {
|
|
50
|
+
const haystack = new Set(tokenize(text));
|
|
51
|
+
let score = 0;
|
|
52
|
+
for (const token of queryTokens) {
|
|
53
|
+
if (haystack.has(token)) {
|
|
54
|
+
score += weight;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return score;
|
|
58
|
+
}
|
|
59
|
+
function computeRecencyBoost(timestamp) {
|
|
60
|
+
if (!timestamp)
|
|
61
|
+
return 0;
|
|
62
|
+
const timestampMs = Date.parse(timestamp);
|
|
63
|
+
if (!Number.isFinite(timestampMs))
|
|
64
|
+
return 0;
|
|
65
|
+
const ageMs = Date.now() - timestampMs;
|
|
66
|
+
const dayMs = 24 * 60 * 60 * 1000;
|
|
67
|
+
if (ageMs <= dayMs)
|
|
68
|
+
return 8;
|
|
69
|
+
if (ageMs <= 7 * dayMs)
|
|
70
|
+
return 5;
|
|
71
|
+
if (ageMs <= 30 * dayMs)
|
|
72
|
+
return 2;
|
|
73
|
+
return 0;
|
|
74
|
+
}
|
|
75
|
+
function scoreCandidate(queryTokens, candidate) {
|
|
76
|
+
let score = candidate.priority;
|
|
77
|
+
score += computeTokenOverlapScore(queryTokens, candidate.title, 10);
|
|
78
|
+
score += computeTokenOverlapScore(queryTokens, candidate.content, 3);
|
|
79
|
+
score += computeTokenOverlapScore(queryTokens, candidate.path, 6);
|
|
80
|
+
score += computeRecencyBoost(candidate.timestamp);
|
|
81
|
+
return score;
|
|
82
|
+
}
|
|
83
|
+
function buildFallbackCandidates(request, candidates, existing) {
|
|
84
|
+
if (!containsHanText(request.query) && existing.length > 0) {
|
|
85
|
+
return existing;
|
|
86
|
+
}
|
|
87
|
+
const seen = new Set(existing.map(({ candidate }) => candidate.id));
|
|
88
|
+
const seeded = [...existing];
|
|
89
|
+
const limit = Math.max(request.maxCandidates, request.maxInjected);
|
|
90
|
+
for (const candidate of candidates
|
|
91
|
+
.filter((item) => item.source === "channel-session" || item.source === "channel-memory")
|
|
92
|
+
.sort((a, b) => b.priority - a.priority || a.title.localeCompare(b.title))) {
|
|
93
|
+
if (seen.has(candidate.id)) {
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
seeded.push({ candidate, score: candidate.priority });
|
|
97
|
+
seen.add(candidate.id);
|
|
98
|
+
if (seeded.length >= limit) {
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
return seeded;
|
|
103
|
+
}
|
|
104
|
+
async function rerankCandidates(request, candidates) {
|
|
105
|
+
if ((!request.rerankWithModel && !request.autoRerank) || candidates.length <= request.maxInjected) {
|
|
106
|
+
return candidates;
|
|
107
|
+
}
|
|
108
|
+
const renderedCandidates = candidates
|
|
109
|
+
.map(({ candidate, score }) => {
|
|
110
|
+
const clippedContent = candidate.content.length > 300 ? `${candidate.content.slice(0, 300)}...` : candidate.content;
|
|
111
|
+
return [
|
|
112
|
+
`id: ${candidate.id}`,
|
|
113
|
+
`source: ${candidate.source}`,
|
|
114
|
+
`title: ${candidate.title}`,
|
|
115
|
+
`path: ${candidate.path}`,
|
|
116
|
+
`score: ${score}`,
|
|
117
|
+
`content: ${clippedContent}`,
|
|
118
|
+
].join("\n");
|
|
119
|
+
})
|
|
120
|
+
.join("\n\n---\n\n");
|
|
121
|
+
try {
|
|
122
|
+
const result = await runSidecarTask({
|
|
123
|
+
name: "memory-recall-rerank",
|
|
124
|
+
model: request.model,
|
|
125
|
+
resolveApiKey: request.resolveApiKey,
|
|
126
|
+
systemPrompt: RERANK_SYSTEM_PROMPT,
|
|
127
|
+
prompt: `User turn:\n${request.query.trim()}\n\nCandidates:\n${renderedCandidates}`,
|
|
128
|
+
timeoutMs: MEMORY_RECALL_RERANK_TIMEOUT_MS,
|
|
129
|
+
parse: (text) => {
|
|
130
|
+
const parsed = parseJsonObject(text);
|
|
131
|
+
return Array.isArray(parsed.selectedIds)
|
|
132
|
+
? parsed.selectedIds.filter((id) => typeof id === "string" && id.trim().length > 0)
|
|
133
|
+
: [];
|
|
134
|
+
},
|
|
135
|
+
});
|
|
136
|
+
const selectedIds = new Set(result.output);
|
|
137
|
+
if (selectedIds.size === 0) {
|
|
138
|
+
return candidates;
|
|
139
|
+
}
|
|
140
|
+
const selected = candidates.filter(({ candidate }) => selectedIds.has(candidate.id));
|
|
141
|
+
return selected.length > 0 ? selected : candidates;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
return candidates;
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
function renderRecallResult(items, maxChars) {
|
|
148
|
+
if (items.length === 0) {
|
|
149
|
+
return "";
|
|
150
|
+
}
|
|
151
|
+
const lines = ["<runtime_context>", "Relevant context for this turn:"];
|
|
152
|
+
for (const item of items) {
|
|
153
|
+
lines.push("");
|
|
154
|
+
lines.push(`[${item.source}/${item.title}]`);
|
|
155
|
+
lines.push(`Path: ${item.path}`);
|
|
156
|
+
lines.push(item.content);
|
|
157
|
+
}
|
|
158
|
+
lines.push("</runtime_context>");
|
|
159
|
+
const rendered = lines.join("\n");
|
|
160
|
+
if (rendered.length <= maxChars) {
|
|
161
|
+
return rendered;
|
|
162
|
+
}
|
|
163
|
+
const clippedLines = ["<runtime_context>", "Relevant context for this turn:"];
|
|
164
|
+
let usedChars = clippedLines.join("\n").length + "</runtime_context>".length + 2;
|
|
165
|
+
for (const item of items) {
|
|
166
|
+
const block = ["", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content].join("\n");
|
|
167
|
+
if (usedChars + block.length > maxChars) {
|
|
168
|
+
break;
|
|
169
|
+
}
|
|
170
|
+
clippedLines.push("", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content);
|
|
171
|
+
usedChars += block.length;
|
|
172
|
+
}
|
|
173
|
+
clippedLines.push("</runtime_context>");
|
|
174
|
+
return clippedLines.join("\n");
|
|
175
|
+
}
|
|
176
|
+
export async function recallRelevantMemory(request) {
|
|
177
|
+
const query = request.query.trim();
|
|
178
|
+
if (!query) {
|
|
179
|
+
return { items: [], renderedText: "" };
|
|
180
|
+
}
|
|
181
|
+
const candidates = await buildMemoryCandidates({
|
|
182
|
+
workspaceDir: request.workspaceDir,
|
|
183
|
+
channelDir: request.channelDir,
|
|
184
|
+
cache: request.candidateCache,
|
|
185
|
+
});
|
|
186
|
+
const filteredCandidates = request.allowedSources?.length
|
|
187
|
+
? candidates.filter((candidate) => request.allowedSources?.includes(candidate.source))
|
|
188
|
+
: candidates;
|
|
189
|
+
if (filteredCandidates.length === 0) {
|
|
190
|
+
return { items: [], renderedText: "" };
|
|
191
|
+
}
|
|
192
|
+
const queryTokens = tokenize(query);
|
|
193
|
+
const scored = filteredCandidates
|
|
194
|
+
.map((candidate) => ({ candidate, score: scoreCandidate(queryTokens, candidate) }))
|
|
195
|
+
.filter(({ score }) => score > 0)
|
|
196
|
+
.sort((a, b) => b.score - a.score || a.candidate.title.localeCompare(b.candidate.title));
|
|
197
|
+
const shortlist = buildFallbackCandidates(request, filteredCandidates, scored)
|
|
198
|
+
.sort((a, b) => b.score - a.score ||
|
|
199
|
+
b.candidate.priority - a.candidate.priority ||
|
|
200
|
+
a.candidate.title.localeCompare(b.candidate.title))
|
|
201
|
+
.slice(0, Math.max(request.maxCandidates, request.maxInjected));
|
|
202
|
+
if (shortlist.length === 0) {
|
|
203
|
+
return { items: [], renderedText: "" };
|
|
204
|
+
}
|
|
205
|
+
const reranked = await rerankCandidates(request, shortlist);
|
|
206
|
+
const items = reranked.slice(0, request.maxInjected).map(({ candidate, score }) => ({
|
|
207
|
+
source: candidate.source,
|
|
208
|
+
path: candidate.path,
|
|
209
|
+
title: candidate.title,
|
|
210
|
+
content: candidate.content,
|
|
211
|
+
score,
|
|
212
|
+
}));
|
|
213
|
+
return {
|
|
214
|
+
items,
|
|
215
|
+
renderedText: renderRecallResult(items, request.maxChars),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
//# sourceMappingURL=memory-recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory-recall.js","sourceRoot":"","sources":["../src/memory-recall.ts"],"names":[],"mappings":"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","sourcesContent":["import type { Api, Model } from \"@mariozechner/pi-ai\";\nimport { parseJsonObject } from \"./llm-json.js\";\nimport { buildMemoryCandidates, type MemoryCandidate, type MemoryCandidateCache } from \"./memory-candidates.js\";\nimport { runSidecarTask } from \"./sidecar-worker.js\";\n\nexport interface RecallRequest {\n\tquery: string;\n\tworkspaceDir: string;\n\tchannelDir: string;\n\tallowedSources?: MemoryCandidate[\"source\"][];\n\tmaxCandidates: number;\n\tmaxInjected: number;\n\tmaxChars: number;\n\trerankWithModel: boolean;\n\tautoRerank?: boolean;\n\tmodel: Model<Api>;\n\tresolveApiKey: (model: Model<Api>) => Promise<string>;\n\tcandidateCache?: MemoryCandidateCache;\n}\n\nexport interface RecalledMemory {\n\tsource: MemoryCandidate[\"source\"];\n\tpath: string;\n\ttitle: string;\n\tcontent: string;\n\tscore: number;\n}\n\nexport interface RecallResult {\n\titems: RecalledMemory[];\n\trenderedText: string;\n}\n\nconst RERANK_SYSTEM_PROMPT = `You are selecting which memory snippets are most relevant to the current user turn.\n\nReturn strict JSON only:\n{\n \"selectedIds\": [\"candidate-id\"]\n}\n\nRules:\n- Select only snippets that are clearly useful for answering the current turn.\n- Prefer current work state, constraints, active files, recent corrections, and durable decisions.\n- If nothing is clearly useful, return an empty array.\n- Do not rewrite the candidates. Only return candidate ids.`;\n\nconst HAN_REGEX = /\\p{Script=Han}/u;\nconst TOKEN_PART_REGEX = /[\\p{Script=Han}]+|[\\p{L}\\p{N}_./-]+/gu;\nconst MEMORY_RECALL_RERANK_TIMEOUT_MS = 5_000;\n\nfunction containsHanText(text: string): boolean {\n\treturn HAN_REGEX.test(text);\n}\n\nfunction tokenizeHanPart(part: string): string[] {\n\tconst chars = Array.from(part);\n\tconst tokens: string[] = [];\n\tfor (const size of [2, 3]) {\n\t\tif (chars.length < size) {\n\t\t\tcontinue;\n\t\t}\n\t\tfor (let index = 0; index <= chars.length - size; index++) {\n\t\t\ttokens.push(chars.slice(index, index + size).join(\"\"));\n\t\t}\n\t}\n\treturn tokens;\n}\n\nfunction tokenize(text: string): string[] {\n\tconst parts = text.toLowerCase().match(TOKEN_PART_REGEX) ?? [];\n\tconst tokens: string[] = [];\n\tfor (const part of parts) {\n\t\tif (containsHanText(part)) {\n\t\t\ttokens.push(...tokenizeHanPart(part));\n\t\t\tcontinue;\n\t\t}\n\t\tif (part.length >= 2) {\n\t\t\ttokens.push(part);\n\t\t}\n\t}\n\treturn Array.from(new Set(tokens));\n}\n\nfunction computeTokenOverlapScore(queryTokens: string[], text: string, weight: number): number {\n\tconst haystack = new Set(tokenize(text));\n\tlet score = 0;\n\tfor (const token of queryTokens) {\n\t\tif (haystack.has(token)) {\n\t\t\tscore += weight;\n\t\t}\n\t}\n\treturn score;\n}\n\nfunction computeRecencyBoost(timestamp: string | undefined): number {\n\tif (!timestamp) return 0;\n\tconst timestampMs = Date.parse(timestamp);\n\tif (!Number.isFinite(timestampMs)) return 0;\n\n\tconst ageMs = Date.now() - timestampMs;\n\tconst dayMs = 24 * 60 * 60 * 1000;\n\tif (ageMs <= dayMs) return 8;\n\tif (ageMs <= 7 * dayMs) return 5;\n\tif (ageMs <= 30 * dayMs) return 2;\n\treturn 0;\n}\n\nfunction scoreCandidate(queryTokens: string[], candidate: MemoryCandidate): number {\n\tlet score = candidate.priority;\n\tscore += computeTokenOverlapScore(queryTokens, candidate.title, 10);\n\tscore += computeTokenOverlapScore(queryTokens, candidate.content, 3);\n\tscore += computeTokenOverlapScore(queryTokens, candidate.path, 6);\n\tscore += computeRecencyBoost(candidate.timestamp);\n\treturn score;\n}\n\nfunction buildFallbackCandidates(\n\trequest: RecallRequest,\n\tcandidates: MemoryCandidate[],\n\texisting: Array<{ candidate: MemoryCandidate; score: number }>,\n): Array<{ candidate: MemoryCandidate; score: number }> {\n\tif (!containsHanText(request.query) && existing.length > 0) {\n\t\treturn existing;\n\t}\n\n\tconst seen = new Set(existing.map(({ candidate }) => candidate.id));\n\tconst seeded = [...existing];\n\tconst limit = Math.max(request.maxCandidates, request.maxInjected);\n\n\tfor (const candidate of candidates\n\t\t.filter((item) => item.source === \"channel-session\" || item.source === \"channel-memory\")\n\t\t.sort((a, b) => b.priority - a.priority || a.title.localeCompare(b.title))) {\n\t\tif (seen.has(candidate.id)) {\n\t\t\tcontinue;\n\t\t}\n\t\tseeded.push({ candidate, score: candidate.priority });\n\t\tseen.add(candidate.id);\n\t\tif (seeded.length >= limit) {\n\t\t\tbreak;\n\t\t}\n\t}\n\n\treturn seeded;\n}\n\nasync function rerankCandidates(\n\trequest: RecallRequest,\n\tcandidates: Array<{ candidate: MemoryCandidate; score: number }>,\n): Promise<Array<{ candidate: MemoryCandidate; score: number }>> {\n\tif ((!request.rerankWithModel && !request.autoRerank) || candidates.length <= request.maxInjected) {\n\t\treturn candidates;\n\t}\n\n\tconst renderedCandidates = candidates\n\t\t.map(({ candidate, score }) => {\n\t\t\tconst clippedContent =\n\t\t\t\tcandidate.content.length > 300 ? `${candidate.content.slice(0, 300)}...` : candidate.content;\n\t\t\treturn [\n\t\t\t\t`id: ${candidate.id}`,\n\t\t\t\t`source: ${candidate.source}`,\n\t\t\t\t`title: ${candidate.title}`,\n\t\t\t\t`path: ${candidate.path}`,\n\t\t\t\t`score: ${score}`,\n\t\t\t\t`content: ${clippedContent}`,\n\t\t\t].join(\"\\n\");\n\t\t})\n\t\t.join(\"\\n\\n---\\n\\n\");\n\n\ttry {\n\t\tconst result = await runSidecarTask({\n\t\t\tname: \"memory-recall-rerank\",\n\t\t\tmodel: request.model,\n\t\t\tresolveApiKey: request.resolveApiKey,\n\t\t\tsystemPrompt: RERANK_SYSTEM_PROMPT,\n\t\t\tprompt: `User turn:\\n${request.query.trim()}\\n\\nCandidates:\\n${renderedCandidates}`,\n\t\t\ttimeoutMs: MEMORY_RECALL_RERANK_TIMEOUT_MS,\n\t\t\tparse: (text) => {\n\t\t\t\tconst parsed = parseJsonObject(text) as { selectedIds?: unknown };\n\t\t\t\treturn Array.isArray(parsed.selectedIds)\n\t\t\t\t\t? parsed.selectedIds.filter((id): id is string => typeof id === \"string\" && id.trim().length > 0)\n\t\t\t\t\t: [];\n\t\t\t},\n\t\t});\n\n\t\tconst selectedIds = new Set(result.output);\n\t\tif (selectedIds.size === 0) {\n\t\t\treturn candidates;\n\t\t}\n\n\t\tconst selected = candidates.filter(({ candidate }) => selectedIds.has(candidate.id));\n\t\treturn selected.length > 0 ? selected : candidates;\n\t} catch {\n\t\treturn candidates;\n\t}\n}\n\nfunction renderRecallResult(items: RecalledMemory[], maxChars: number): string {\n\tif (items.length === 0) {\n\t\treturn \"\";\n\t}\n\n\tconst lines: string[] = [\"<runtime_context>\", \"Relevant context for this turn:\"];\n\tfor (const item of items) {\n\t\tlines.push(\"\");\n\t\tlines.push(`[${item.source}/${item.title}]`);\n\t\tlines.push(`Path: ${item.path}`);\n\t\tlines.push(item.content);\n\t}\n\tlines.push(\"</runtime_context>\");\n\n\tconst rendered = lines.join(\"\\n\");\n\tif (rendered.length <= maxChars) {\n\t\treturn rendered;\n\t}\n\n\tconst clippedLines = [\"<runtime_context>\", \"Relevant context for this turn:\"];\n\tlet usedChars = clippedLines.join(\"\\n\").length + \"</runtime_context>\".length + 2;\n\tfor (const item of items) {\n\t\tconst block = [\"\", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content].join(\"\\n\");\n\t\tif (usedChars + block.length > maxChars) {\n\t\t\tbreak;\n\t\t}\n\t\tclippedLines.push(\"\", `[${item.source}/${item.title}]`, `Path: ${item.path}`, item.content);\n\t\tusedChars += block.length;\n\t}\n\tclippedLines.push(\"</runtime_context>\");\n\treturn clippedLines.join(\"\\n\");\n}\n\nexport async function recallRelevantMemory(request: RecallRequest): Promise<RecallResult> {\n\tconst query = request.query.trim();\n\tif (!query) {\n\t\treturn { items: [], renderedText: \"\" };\n\t}\n\n\tconst candidates = await buildMemoryCandidates({\n\t\tworkspaceDir: request.workspaceDir,\n\t\tchannelDir: request.channelDir,\n\t\tcache: request.candidateCache,\n\t});\n\tconst filteredCandidates = request.allowedSources?.length\n\t\t? candidates.filter((candidate) => request.allowedSources?.includes(candidate.source))\n\t\t: candidates;\n\tif (filteredCandidates.length === 0) {\n\t\treturn { items: [], renderedText: \"\" };\n\t}\n\n\tconst queryTokens = tokenize(query);\n\tconst scored = filteredCandidates\n\t\t.map((candidate) => ({ candidate, score: scoreCandidate(queryTokens, candidate) }))\n\t\t.filter(({ score }) => score > 0)\n\t\t.sort((a, b) => b.score - a.score || a.candidate.title.localeCompare(b.candidate.title));\n\n\tconst shortlist = buildFallbackCandidates(request, filteredCandidates, scored)\n\t\t.sort(\n\t\t\t(a, b) =>\n\t\t\t\tb.score - a.score ||\n\t\t\t\tb.candidate.priority - a.candidate.priority ||\n\t\t\t\ta.candidate.title.localeCompare(b.candidate.title),\n\t\t)\n\t\t.slice(0, Math.max(request.maxCandidates, request.maxInjected));\n\n\tif (shortlist.length === 0) {\n\t\treturn { items: [], renderedText: \"\" };\n\t}\n\n\tconst reranked = await rerankCandidates(request, shortlist);\n\tconst items = reranked.slice(0, request.maxInjected).map(({ candidate, score }) => ({\n\t\tsource: candidate.source,\n\t\tpath: candidate.path,\n\t\ttitle: candidate.title,\n\t\tcontent: candidate.content,\n\t\tscore,\n\t}));\n\n\treturn {\n\t\titems,\n\t\trenderedText: renderRecallResult(items, request.maxChars),\n\t};\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-builder.d.ts","sourceRoot":"","sources":["../src/prompt-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,yBAAyB;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,uBAAuB,CACtC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,yBAA8B,GACrC,MAAM,
|
|
1
|
+
{"version":3,"file":"prompt-builder.d.ts","sourceRoot":"","sources":["../src/prompt-builder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAElD,MAAM,WAAW,yBAAyB;IACzC,YAAY,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,uBAAuB,CACtC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,aAAa,EAAE,aAAa,EAC5B,OAAO,GAAE,yBAA8B,GACrC,MAAM,CAyJR"}
|