pi-harness-runtime 1.1.95 → 1.1.97
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/CHANGELOG.md +33 -0
- package/harness/loop-runtime.ts +3 -0
- package/index.ts +130 -29
- package/package.json +2 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/jev-judge/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/qdrant-skills/dist/client.d.ts +51 -0
- package/packages/qdrant-skills/dist/client.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/client.js +105 -0
- package/packages/qdrant-skills/dist/client.js.map +1 -0
- package/packages/qdrant-skills/dist/embedder.d.ts +39 -0
- package/packages/qdrant-skills/dist/embedder.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/embedder.js +101 -0
- package/packages/qdrant-skills/dist/embedder.js.map +1 -0
- package/packages/qdrant-skills/dist/index.d.ts +25 -0
- package/packages/qdrant-skills/dist/index.d.ts.map +1 -0
- package/packages/qdrant-skills/dist/index.js +25 -0
- package/packages/qdrant-skills/dist/index.js.map +1 -0
- package/packages/qdrant-skills/package.json +27 -0
- package/packages/qdrant-skills/src/client.ts +178 -0
- package/packages/qdrant-skills/src/embedder.ts +137 -0
- package/packages/qdrant-skills/src/index.ts +42 -0
- package/packages/quota-manager/package.json +1 -1
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-mcp-client/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/skills/dist/index.d.ts +40 -0
- package/packages/skills/dist/index.d.ts.map +1 -0
- package/packages/skills/dist/index.js +84 -0
- package/packages/skills/dist/index.js.map +1 -0
- package/packages/skills/dist/linter.d.ts +38 -0
- package/packages/skills/dist/linter.d.ts.map +1 -0
- package/packages/skills/dist/linter.js +323 -0
- package/packages/skills/dist/linter.js.map +1 -0
- package/packages/skills/dist/loader.d.ts +74 -0
- package/packages/skills/dist/loader.d.ts.map +1 -0
- package/packages/skills/dist/loader.js +280 -0
- package/packages/skills/dist/loader.js.map +1 -0
- package/packages/skills/dist/parser.d.ts +32 -0
- package/packages/skills/dist/parser.d.ts.map +1 -0
- package/packages/skills/dist/parser.js +459 -0
- package/packages/skills/dist/parser.js.map +1 -0
- package/packages/skills/dist/scanner.d.ts +33 -0
- package/packages/skills/dist/scanner.d.ts.map +1 -0
- package/packages/skills/dist/scanner.js +257 -0
- package/packages/skills/dist/scanner.js.map +1 -0
- package/packages/skills/dist/skill-commands.d.ts +33 -0
- package/packages/skills/dist/skill-commands.d.ts.map +1 -0
- package/packages/skills/dist/skill-commands.js +239 -0
- package/packages/skills/dist/skill-commands.js.map +1 -0
- package/packages/skills/dist/skill-tool.d.ts +120 -0
- package/packages/skills/dist/skill-tool.d.ts.map +1 -0
- package/packages/skills/dist/skill-tool.js +239 -0
- package/packages/skills/dist/skill-tool.js.map +1 -0
- package/packages/skills/dist/skill-versioning.d.ts +123 -0
- package/packages/skills/dist/skill-versioning.d.ts.map +1 -0
- package/packages/skills/dist/skill-versioning.js +240 -0
- package/packages/skills/dist/skill-versioning.js.map +1 -0
- package/packages/skills/dist/types.d.ts +226 -0
- package/packages/skills/dist/types.d.ts.map +1 -0
- package/packages/skills/dist/types.js +21 -0
- package/packages/skills/dist/types.js.map +1 -0
- package/packages/skills/package.json +46 -0
- package/packages/skills/src/index.ts +95 -0
- package/packages/skills/src/linter.ts +371 -0
- package/packages/skills/src/loader.ts +335 -0
- package/packages/skills/src/parser.ts +548 -0
- package/packages/skills/src/scanner.ts +322 -0
- package/packages/skills/src/skill-commands.ts +318 -0
- package/packages/skills/src/skill-tool.ts +324 -0
- package/packages/skills/src/skill-versioning.ts +345 -0
- package/packages/skills/src/types.ts +259 -0
- package/packages/skills/test/init-skills.test.ts +118 -0
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/trajectory/dist/src/curator.d.ts +189 -0
- package/packages/trajectory/dist/src/curator.d.ts.map +1 -0
- package/packages/trajectory/dist/src/curator.js +340 -0
- package/packages/trajectory/dist/src/curator.js.map +1 -0
- package/packages/trajectory/dist/src/journey-renderer.d.ts +53 -0
- package/packages/trajectory/dist/src/journey-renderer.d.ts.map +1 -0
- package/packages/trajectory/dist/src/journey-renderer.js +324 -0
- package/packages/trajectory/dist/src/journey-renderer.js.map +1 -0
- package/packages/trajectory/dist/src/learning-graph.d.ts +159 -0
- package/packages/trajectory/dist/src/learning-graph.d.ts.map +1 -0
- package/packages/trajectory/dist/src/learning-graph.js +258 -0
- package/packages/trajectory/dist/src/learning-graph.js.map +1 -0
- package/packages/trajectory/dist/src/memory-store.d.ts +146 -0
- package/packages/trajectory/dist/src/memory-store.d.ts.map +1 -0
- package/packages/trajectory/dist/src/memory-store.js +472 -0
- package/packages/trajectory/dist/src/memory-store.js.map +1 -0
- package/packages/trajectory/dist/src/review-fork.d.ts +164 -0
- package/packages/trajectory/dist/src/review-fork.d.ts.map +1 -0
- package/packages/trajectory/dist/src/review-fork.js +332 -0
- package/packages/trajectory/dist/src/review-fork.js.map +1 -0
- package/packages/trajectory/dist/src/write-approval.d.ts +136 -0
- package/packages/trajectory/dist/src/write-approval.d.ts.map +1 -0
- package/packages/trajectory/dist/src/write-approval.js +281 -0
- package/packages/trajectory/dist/src/write-approval.js.map +1 -0
- package/packages/trajectory/package.json +1 -1
- package/packages/trajectory/src/curator.ts +485 -0
- package/packages/trajectory/src/journey-renderer.ts +399 -0
- package/packages/trajectory/src/learning-graph.ts +363 -0
- package/packages/trajectory/src/memory-store.ts +578 -0
- package/packages/trajectory/src/review-fork.ts +450 -0
- package/packages/trajectory/src/write-approval.ts +372 -0
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/dist/blackboard.d.ts +98 -0
- package/packages/write-review/dist/blackboard.d.ts.map +1 -0
- package/packages/write-review/dist/blackboard.js +289 -0
- package/packages/write-review/dist/blackboard.js.map +1 -0
- package/packages/write-review/dist/event-bus.d.ts +106 -0
- package/packages/write-review/dist/event-bus.d.ts.map +1 -0
- package/packages/write-review/dist/event-bus.js +294 -0
- package/packages/write-review/dist/event-bus.js.map +1 -0
- package/packages/write-review/dist/gate.d.ts +37 -0
- package/packages/write-review/dist/gate.d.ts.map +1 -0
- package/packages/write-review/dist/gate.js +138 -0
- package/packages/write-review/dist/gate.js.map +1 -0
- package/packages/write-review/dist/index.d.ts +22 -0
- package/packages/write-review/dist/index.d.ts.map +1 -0
- package/packages/write-review/dist/index.js +60 -0
- package/packages/write-review/dist/index.js.map +1 -0
- package/packages/write-review/dist/injection.d.ts +40 -0
- package/packages/write-review/dist/injection.d.ts.map +1 -0
- package/packages/write-review/dist/injection.js +179 -0
- package/packages/write-review/dist/injection.js.map +1 -0
- package/packages/write-review/dist/review.d.ts +54 -0
- package/packages/write-review/dist/review.d.ts.map +1 -0
- package/packages/write-review/dist/review.js +154 -0
- package/packages/write-review/dist/review.js.map +1 -0
- package/packages/write-review/dist/trigger.d.ts +39 -0
- package/packages/write-review/dist/trigger.d.ts.map +1 -0
- package/packages/write-review/dist/trigger.js +107 -0
- package/packages/write-review/dist/trigger.js.map +1 -0
- package/packages/write-review/dist/types.d.ts +78 -0
- package/packages/write-review/dist/types.d.ts.map +1 -0
- package/packages/write-review/dist/types.js +26 -0
- package/packages/write-review/dist/types.js.map +1 -0
- package/packages/write-review/package.json +1 -1
- package/packages/write-review/src/event-bus.ts +351 -0
- package/packages/write-review/src/index.ts +1 -0
- package/packages/write-review/src/injection.ts +25 -3
- package/proactive-compact.ts +51 -7
- package/scripts/sync-skills.ts +275 -0
- package/scripts/sync-to-qdrant.ts +398 -0
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Memory Store - Persistent memory for agent notes and user preferences
|
|
3
|
+
*
|
|
4
|
+
* Based on Hermes Agent's memory system:
|
|
5
|
+
* - MEMORY.md: Agent's personal notes (environment facts, conventions, tool quirks)
|
|
6
|
+
* - USER.md: User preferences and patterns (Honcho dialectic modeling)
|
|
7
|
+
*
|
|
8
|
+
* Memory is injected into system prompt at session start.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
12
|
+
import { join, dirname } from "node:path";
|
|
13
|
+
import { homedir } from "node:os";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Memory entry for agent notes
|
|
17
|
+
*/
|
|
18
|
+
export interface MemoryEntry {
|
|
19
|
+
id: string;
|
|
20
|
+
content: string;
|
|
21
|
+
source: "agent" | "user" | "curator";
|
|
22
|
+
created_at: string;
|
|
23
|
+
updated_at: string;
|
|
24
|
+
tags?: string[];
|
|
25
|
+
fingerprint?: string; // For deduplication
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* User profile entry
|
|
30
|
+
*/
|
|
31
|
+
export interface UserProfileEntry {
|
|
32
|
+
id: string;
|
|
33
|
+
content: string;
|
|
34
|
+
category: "preference" | "style" | "workflow" | "context";
|
|
35
|
+
created_at: string;
|
|
36
|
+
updated_at: string;
|
|
37
|
+
confidence: number; // 0-1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Memory store configuration
|
|
42
|
+
*/
|
|
43
|
+
export interface MemoryStoreConfig {
|
|
44
|
+
memoryFile: string;
|
|
45
|
+
userFile: string;
|
|
46
|
+
maxMemoryTokens: number; // ~1300 tokens default
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Default memory store configuration
|
|
51
|
+
*/
|
|
52
|
+
export const DEFAULT_MEMORY_CONFIG: MemoryStoreConfig = {
|
|
53
|
+
memoryFile: "~/.pi-harness/memory/MEMORY.md",
|
|
54
|
+
userFile: "~/.pi-harness/memory/USER.md",
|
|
55
|
+
maxMemoryTokens: 1300,
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Expand ~ in paths
|
|
60
|
+
*/
|
|
61
|
+
function expandPath(path: string): string {
|
|
62
|
+
if (path.startsWith("~/")) {
|
|
63
|
+
return join(homedir(), path.slice(2));
|
|
64
|
+
}
|
|
65
|
+
return path;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Generate fingerprint for content deduplication
|
|
70
|
+
*/
|
|
71
|
+
function generateFingerprint(content: string): string {
|
|
72
|
+
// Simple hash for fingerprinting
|
|
73
|
+
let hash = 0;
|
|
74
|
+
for (let i = 0; i < content.length; i++) {
|
|
75
|
+
const char = content.charCodeAt(i);
|
|
76
|
+
hash = ((hash << 5) - hash) + char;
|
|
77
|
+
hash = hash & hash; // Convert to 32bit integer
|
|
78
|
+
}
|
|
79
|
+
return Math.abs(hash).toString(16);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Memory Store class
|
|
84
|
+
*/
|
|
85
|
+
export class MemoryStore {
|
|
86
|
+
private config: MemoryStoreConfig;
|
|
87
|
+
private memoryFile: string;
|
|
88
|
+
private userFile: string;
|
|
89
|
+
private memoryCache: MemoryEntry[] = [];
|
|
90
|
+
private userCache: UserProfileEntry[] = [];
|
|
91
|
+
private dirty = false;
|
|
92
|
+
|
|
93
|
+
constructor(config?: Partial<MemoryStoreConfig>) {
|
|
94
|
+
this.config = { ...DEFAULT_MEMORY_CONFIG, ...config };
|
|
95
|
+
this.memoryFile = expandPath(this.config.memoryFile);
|
|
96
|
+
this.userFile = expandPath(this.config.userFile);
|
|
97
|
+
this.load();
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Load memory from files
|
|
102
|
+
*/
|
|
103
|
+
load(): void {
|
|
104
|
+
this.memoryCache = this.loadMemoryFile(this.memoryFile);
|
|
105
|
+
this.userCache = this.loadUserFile(this.userFile);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Load memory entries from file
|
|
110
|
+
*/
|
|
111
|
+
private loadMemoryFile(filePath: string): MemoryEntry[] {
|
|
112
|
+
if (!existsSync(filePath)) {
|
|
113
|
+
return [];
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
try {
|
|
117
|
+
const content = readFileSync(filePath, "utf-8");
|
|
118
|
+
return this.parseMemoryMarkdown(content);
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(`[MemoryStore] Failed to load ${filePath}:`, err);
|
|
121
|
+
return [];
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Load user profile from file
|
|
127
|
+
*/
|
|
128
|
+
private loadUserFile(filePath: string): UserProfileEntry[] {
|
|
129
|
+
if (!existsSync(filePath)) {
|
|
130
|
+
return [];
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
try {
|
|
134
|
+
const content = readFileSync(filePath, "utf-8");
|
|
135
|
+
return this.parseUserMarkdown(content);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(`[MemoryStore] Failed to load ${filePath}:`, err);
|
|
138
|
+
return [];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Parse memory markdown format
|
|
144
|
+
*/
|
|
145
|
+
private parseMemoryMarkdown(content: string): MemoryEntry[] {
|
|
146
|
+
const entries: MemoryEntry[] = [];
|
|
147
|
+
const lines = content.split("\n");
|
|
148
|
+
let currentEntry: Partial<MemoryEntry> | null = null;
|
|
149
|
+
let currentContent: string[] = [];
|
|
150
|
+
|
|
151
|
+
for (const line of lines) {
|
|
152
|
+
// Check for entry separator
|
|
153
|
+
if (line.startsWith("---")) {
|
|
154
|
+
if (currentEntry && currentContent.length > 0) {
|
|
155
|
+
entries.push({
|
|
156
|
+
id: currentEntry.id!,
|
|
157
|
+
content: currentContent.join("\n").trim(),
|
|
158
|
+
source: currentEntry.source || "agent",
|
|
159
|
+
created_at: currentEntry.created_at!,
|
|
160
|
+
updated_at: currentEntry.updated_at!,
|
|
161
|
+
tags: currentEntry.tags,
|
|
162
|
+
fingerprint: generateFingerprint(currentContent.join("\n")),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
currentEntry = {};
|
|
166
|
+
currentContent = [];
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Parse metadata
|
|
171
|
+
if (line.includes(":") && currentEntry) {
|
|
172
|
+
const [key, ...valueParts] = line.split(":");
|
|
173
|
+
const value = valueParts.join(":").trim();
|
|
174
|
+
|
|
175
|
+
switch (key.trim()) {
|
|
176
|
+
case "id":
|
|
177
|
+
currentEntry!.id = value;
|
|
178
|
+
break;
|
|
179
|
+
case "source":
|
|
180
|
+
currentEntry!.source = value as MemoryEntry["source"];
|
|
181
|
+
break;
|
|
182
|
+
case "created":
|
|
183
|
+
case "created_at":
|
|
184
|
+
currentEntry!.created_at = value;
|
|
185
|
+
break;
|
|
186
|
+
case "updated":
|
|
187
|
+
case "updated_at":
|
|
188
|
+
currentEntry!.updated_at = value;
|
|
189
|
+
break;
|
|
190
|
+
case "tags":
|
|
191
|
+
currentEntry!.tags = value.split(",").map((t) => t.trim());
|
|
192
|
+
break;
|
|
193
|
+
}
|
|
194
|
+
} else if (currentEntry?.id) {
|
|
195
|
+
currentContent.push(line);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Push last entry
|
|
200
|
+
if (currentEntry && currentContent.length > 0) {
|
|
201
|
+
entries.push({
|
|
202
|
+
id: currentEntry.id!,
|
|
203
|
+
content: currentContent.join("\n").trim(),
|
|
204
|
+
source: currentEntry.source || "agent",
|
|
205
|
+
created_at: currentEntry.created_at!,
|
|
206
|
+
updated_at: currentEntry.updated_at!,
|
|
207
|
+
tags: currentEntry.tags,
|
|
208
|
+
fingerprint: generateFingerprint(currentContent.join("\n")),
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return entries;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Parse user profile markdown format
|
|
217
|
+
*/
|
|
218
|
+
private parseUserMarkdown(content: string): UserProfileEntry[] {
|
|
219
|
+
const entries: UserProfileEntry[] = [];
|
|
220
|
+
const lines = content.split("\n");
|
|
221
|
+
let currentEntry: Partial<UserProfileEntry> | null = null;
|
|
222
|
+
let currentContent: string[] = [];
|
|
223
|
+
let currentCategory = "preference";
|
|
224
|
+
|
|
225
|
+
for (const line of lines) {
|
|
226
|
+
// Check for category header
|
|
227
|
+
if (line.startsWith("## ")) {
|
|
228
|
+
currentCategory = line.slice(3).trim().toLowerCase();
|
|
229
|
+
continue;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Check for entry separator
|
|
233
|
+
if (line.startsWith("---")) {
|
|
234
|
+
if (currentEntry && currentContent.length > 0) {
|
|
235
|
+
entries.push({
|
|
236
|
+
id: currentEntry.id!,
|
|
237
|
+
content: currentContent.join("\n").trim(),
|
|
238
|
+
category: currentEntry.category || (currentCategory as UserProfileEntry["category"]),
|
|
239
|
+
created_at: currentEntry.created_at!,
|
|
240
|
+
updated_at: currentEntry.updated_at!,
|
|
241
|
+
confidence: currentEntry.confidence ?? 0.8,
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
currentEntry = {};
|
|
245
|
+
currentContent = [];
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Parse metadata
|
|
250
|
+
if (line.includes(":") && currentEntry) {
|
|
251
|
+
const [key, ...valueParts] = line.split(":");
|
|
252
|
+
const value = valueParts.join(":").trim();
|
|
253
|
+
|
|
254
|
+
switch (key.trim()) {
|
|
255
|
+
case "id":
|
|
256
|
+
currentEntry!.id = value;
|
|
257
|
+
break;
|
|
258
|
+
case "confidence":
|
|
259
|
+
currentEntry!.confidence = parseFloat(value);
|
|
260
|
+
break;
|
|
261
|
+
}
|
|
262
|
+
} else if (currentEntry?.id) {
|
|
263
|
+
currentContent.push(line);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Push last entry
|
|
268
|
+
if (currentEntry && currentContent.length > 0) {
|
|
269
|
+
entries.push({
|
|
270
|
+
id: currentEntry.id!,
|
|
271
|
+
content: currentContent.join("\n").trim(),
|
|
272
|
+
category: currentEntry.category || (currentCategory as UserProfileEntry["category"]),
|
|
273
|
+
created_at: currentEntry.created_at!,
|
|
274
|
+
updated_at: currentEntry.updated_at!,
|
|
275
|
+
confidence: currentEntry.confidence ?? 0.8,
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return entries;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
/**
|
|
283
|
+
* Save memory to file
|
|
284
|
+
*/
|
|
285
|
+
save(): void {
|
|
286
|
+
if (!this.dirty) return;
|
|
287
|
+
|
|
288
|
+
// Ensure directory exists
|
|
289
|
+
const memoryDir = dirname(this.memoryFile);
|
|
290
|
+
mkdirSync(memoryDir, { recursive: true });
|
|
291
|
+
|
|
292
|
+
// Write memory file
|
|
293
|
+
writeFileSync(this.memoryFile, this.serializeMemory(), "utf-8");
|
|
294
|
+
|
|
295
|
+
// Write user file
|
|
296
|
+
writeFileSync(this.userFile, this.serializeUser(), "utf-8");
|
|
297
|
+
|
|
298
|
+
this.dirty = false;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
/**
|
|
302
|
+
* Serialize memory entries to markdown
|
|
303
|
+
*/
|
|
304
|
+
private serializeMemory(): string {
|
|
305
|
+
if (this.memoryCache.length === 0) {
|
|
306
|
+
return "# Memory\n\nNo entries yet.\n";
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const lines: string[] = ["# Memory", "", "---"];
|
|
310
|
+
for (const entry of this.memoryCache) {
|
|
311
|
+
lines.push(`id: ${entry.id}`);
|
|
312
|
+
lines.push(`source: ${entry.source}`);
|
|
313
|
+
lines.push(`created_at: ${entry.created_at}`);
|
|
314
|
+
lines.push(`updated_at: ${entry.updated_at}`);
|
|
315
|
+
if (entry.tags?.length) {
|
|
316
|
+
lines.push(`tags: ${entry.tags.join(", ")}`);
|
|
317
|
+
}
|
|
318
|
+
lines.push("---");
|
|
319
|
+
lines.push(entry.content);
|
|
320
|
+
lines.push("");
|
|
321
|
+
lines.push("---");
|
|
322
|
+
lines.push("");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return lines.join("\n");
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Serialize user entries to markdown
|
|
330
|
+
*/
|
|
331
|
+
private serializeUser(): string {
|
|
332
|
+
const categories = ["preference", "style", "workflow", "context"];
|
|
333
|
+
const lines: string[] = ["# User Preferences", ""];
|
|
334
|
+
|
|
335
|
+
for (const category of categories) {
|
|
336
|
+
const entries = this.userCache.filter((e) => e.category === category);
|
|
337
|
+
if (entries.length === 0) continue;
|
|
338
|
+
|
|
339
|
+
lines.push(`## ${category.charAt(0).toUpperCase() + category.slice(1)}`, "");
|
|
340
|
+
|
|
341
|
+
for (const entry of entries) {
|
|
342
|
+
lines.push(`id: ${entry.id}`);
|
|
343
|
+
lines.push(`confidence: ${entry.confidence}`);
|
|
344
|
+
lines.push("---");
|
|
345
|
+
lines.push(entry.content);
|
|
346
|
+
lines.push("");
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
return lines.join("\n");
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* Add memory entry
|
|
355
|
+
*/
|
|
356
|
+
addMemory(content: string, source: MemoryEntry["source"] = "agent", tags?: string[]): MemoryEntry {
|
|
357
|
+
const now = new Date().toISOString();
|
|
358
|
+
const entry: MemoryEntry = {
|
|
359
|
+
id: `mem_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
360
|
+
content,
|
|
361
|
+
source,
|
|
362
|
+
created_at: now,
|
|
363
|
+
updated_at: now,
|
|
364
|
+
tags,
|
|
365
|
+
fingerprint: generateFingerprint(content),
|
|
366
|
+
};
|
|
367
|
+
|
|
368
|
+
// Check for duplicate
|
|
369
|
+
const existing = this.memoryCache.findIndex((e) => e.fingerprint === entry.fingerprint);
|
|
370
|
+
if (existing >= 0) {
|
|
371
|
+
// Update existing entry
|
|
372
|
+
this.memoryCache[existing].updated_at = now;
|
|
373
|
+
this.memoryCache[existing].content = content;
|
|
374
|
+
this.dirty = true;
|
|
375
|
+
this.save();
|
|
376
|
+
return this.memoryCache[existing];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
this.memoryCache.push(entry);
|
|
380
|
+
this.dirty = true;
|
|
381
|
+
this.save();
|
|
382
|
+
return entry;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
/**
|
|
386
|
+
* Replace memory entry
|
|
387
|
+
*/
|
|
388
|
+
replaceMemory(oldText: string, newContent: string): boolean {
|
|
389
|
+
const index = this.memoryCache.findIndex((e) =>
|
|
390
|
+
e.content.includes(oldText)
|
|
391
|
+
);
|
|
392
|
+
|
|
393
|
+
if (index < 0) {
|
|
394
|
+
return false;
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
this.memoryCache[index].content = newContent;
|
|
398
|
+
this.memoryCache[index].updated_at = new Date().toISOString();
|
|
399
|
+
this.memoryCache[index].fingerprint = generateFingerprint(newContent);
|
|
400
|
+
this.dirty = true;
|
|
401
|
+
this.save();
|
|
402
|
+
return true;
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
/**
|
|
406
|
+
* Remove memory entry
|
|
407
|
+
*/
|
|
408
|
+
removeMemory(oldText: string): boolean {
|
|
409
|
+
const index = this.memoryCache.findIndex((e) =>
|
|
410
|
+
e.content.includes(oldText)
|
|
411
|
+
);
|
|
412
|
+
|
|
413
|
+
if (index < 0) {
|
|
414
|
+
return false;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
this.memoryCache.splice(index, 1);
|
|
418
|
+
this.dirty = true;
|
|
419
|
+
this.save();
|
|
420
|
+
return true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
/**
|
|
424
|
+
* Add user profile entry
|
|
425
|
+
*/
|
|
426
|
+
addUserProfile(
|
|
427
|
+
content: string,
|
|
428
|
+
category: UserProfileEntry["category"] = "preference",
|
|
429
|
+
confidence: number = 0.8
|
|
430
|
+
): UserProfileEntry {
|
|
431
|
+
const now = new Date().toISOString();
|
|
432
|
+
const entry: UserProfileEntry = {
|
|
433
|
+
id: `user_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`,
|
|
434
|
+
content,
|
|
435
|
+
category,
|
|
436
|
+
created_at: now,
|
|
437
|
+
updated_at: now,
|
|
438
|
+
confidence,
|
|
439
|
+
};
|
|
440
|
+
|
|
441
|
+
this.userCache.push(entry);
|
|
442
|
+
this.dirty = true;
|
|
443
|
+
this.save();
|
|
444
|
+
return entry;
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Get all memory entries
|
|
449
|
+
*/
|
|
450
|
+
getMemory(): MemoryEntry[] {
|
|
451
|
+
return [...this.memoryCache];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/**
|
|
455
|
+
* Get all user profile entries
|
|
456
|
+
*/
|
|
457
|
+
getUserProfile(): UserProfileEntry[] {
|
|
458
|
+
return [...this.userCache];
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* Get memory as system prompt section
|
|
463
|
+
*/
|
|
464
|
+
getMemorySection(): string {
|
|
465
|
+
if (this.memoryCache.length === 0) {
|
|
466
|
+
return "";
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const lines = ["## Agent Memory", ""];
|
|
470
|
+
for (const entry of this.memoryCache) {
|
|
471
|
+
lines.push(`- ${entry.content}`);
|
|
472
|
+
if (entry.tags?.length) {
|
|
473
|
+
lines.push(` Tags: ${entry.tags.join(", ")}`);
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
lines.push("");
|
|
477
|
+
|
|
478
|
+
return lines.join("\n");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Get user preferences as system prompt section
|
|
483
|
+
*/
|
|
484
|
+
getUserSection(): string {
|
|
485
|
+
if (this.userCache.length === 0) {
|
|
486
|
+
return "";
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
const lines = ["## User Preferences", ""];
|
|
490
|
+
for (const entry of this.userCache) {
|
|
491
|
+
if (entry.confidence < 0.5) continue; // Skip low confidence
|
|
492
|
+
lines.push(`- [${entry.category}] ${entry.content}`);
|
|
493
|
+
}
|
|
494
|
+
lines.push("");
|
|
495
|
+
|
|
496
|
+
return lines.join("\n");
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* Search memory entries
|
|
501
|
+
*/
|
|
502
|
+
searchMemory(query: string): MemoryEntry[] {
|
|
503
|
+
const queryLower = query.toLowerCase();
|
|
504
|
+
return this.memoryCache.filter(
|
|
505
|
+
(e) =>
|
|
506
|
+
e.content.toLowerCase().includes(queryLower) ||
|
|
507
|
+
e.tags?.some((t) => t.toLowerCase().includes(queryLower))
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
/**
|
|
512
|
+
* List pending entries (for write approval)
|
|
513
|
+
*/
|
|
514
|
+
listPending(): { memory: MemoryEntry[]; user: UserProfileEntry[] } {
|
|
515
|
+
return {
|
|
516
|
+
memory: this.memoryCache.filter((e) => e.source === "agent"),
|
|
517
|
+
user: this.userCache,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
/**
|
|
522
|
+
* Get approximate token count
|
|
523
|
+
*/
|
|
524
|
+
getApproxTokenCount(): number {
|
|
525
|
+
const memory = this.serializeMemory();
|
|
526
|
+
const user = this.serializeUser();
|
|
527
|
+
// Rough estimate: 1 token ≈ 4 chars
|
|
528
|
+
return Math.ceil((memory.length + user.length) / 4);
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
/**
|
|
532
|
+
* Check if memory exceeds max tokens
|
|
533
|
+
*/
|
|
534
|
+
isOverTokenLimit(): boolean {
|
|
535
|
+
return this.getApproxTokenCount() > this.config.maxMemoryTokens;
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* Compact memory (keep most recent/important entries)
|
|
540
|
+
*/
|
|
541
|
+
compact(): void {
|
|
542
|
+
// Sort by updated_at descending, keep top entries
|
|
543
|
+
this.memoryCache.sort((a, b) =>
|
|
544
|
+
b.updated_at.localeCompare(a.updated_at)
|
|
545
|
+
);
|
|
546
|
+
|
|
547
|
+
// Keep entries until under token limit
|
|
548
|
+
const targetTokens = this.config.maxMemoryTokens * 0.8; // 80% of limit
|
|
549
|
+
const keptEntries: MemoryEntry[] = [];
|
|
550
|
+
let tokenCount = 0;
|
|
551
|
+
|
|
552
|
+
for (const entry of this.memoryCache) {
|
|
553
|
+
const entryTokens = Math.ceil(entry.content.length / 4);
|
|
554
|
+
if (tokenCount + entryTokens <= targetTokens) {
|
|
555
|
+
keptEntries.push(entry);
|
|
556
|
+
tokenCount += entryTokens;
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
this.memoryCache = keptEntries;
|
|
561
|
+
this.dirty = true;
|
|
562
|
+
this.save();
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
// Global memory store instance
|
|
567
|
+
let globalMemoryStore: MemoryStore | null = null;
|
|
568
|
+
|
|
569
|
+
export function getGlobalMemoryStore(): MemoryStore {
|
|
570
|
+
if (!globalMemoryStore) {
|
|
571
|
+
globalMemoryStore = new MemoryStore();
|
|
572
|
+
}
|
|
573
|
+
return globalMemoryStore;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export function createMemoryStore(config?: Partial<MemoryStoreConfig>): MemoryStore {
|
|
577
|
+
return new MemoryStore(config);
|
|
578
|
+
}
|