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.
Files changed (226) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/harness/loop-runtime.ts +3 -0
  3. package/index.ts +130 -29
  4. package/package.json +2 -1
  5. package/packages/a2a-adapter/package.json +1 -1
  6. package/packages/architecture-generator/package.json +1 -1
  7. package/packages/auth/package.json +1 -1
  8. package/packages/autonomous-refactor/package.json +1 -1
  9. package/packages/autonomous-runtime/package.json +1 -1
  10. package/packages/cache-strategy/package.json +1 -1
  11. package/packages/capability-registry/package.json +1 -1
  12. package/packages/checkpoint/package.json +1 -1
  13. package/packages/cli-plugin-sdk/package.json +1 -1
  14. package/packages/clipboard/package.json +1 -1
  15. package/packages/clipboard-plugin/package.json +1 -1
  16. package/packages/code-generation/package.json +1 -1
  17. package/packages/code-review/package.json +1 -1
  18. package/packages/codex-adapter/package.json +1 -1
  19. package/packages/config-capture/package.json +1 -1
  20. package/packages/context-compiler/package.json +1 -1
  21. package/packages/context-discovery/package.json +1 -1
  22. package/packages/context-manager/package.json +1 -1
  23. package/packages/cookie-sanitizer/package.json +1 -1
  24. package/packages/cost-optimizer/package.json +1 -1
  25. package/packages/dependency-analyzer/package.json +1 -1
  26. package/packages/django-plugin/package.json +1 -1
  27. package/packages/doc-generator/package.json +1 -1
  28. package/packages/evaluation-engine/package.json +1 -1
  29. package/packages/evaluation-runner/package.json +1 -1
  30. package/packages/event-bus/package.json +1 -1
  31. package/packages/event-store/package.json +1 -1
  32. package/packages/experience-replay/package.json +1 -1
  33. package/packages/feedback-collector/package.json +1 -1
  34. package/packages/file-copy-helper/package.json +1 -1
  35. package/packages/framework-detector/package.json +1 -1
  36. package/packages/framework-plugin-sdk/package.json +1 -1
  37. package/packages/frappe-plugin/package.json +1 -1
  38. package/packages/generic-web-plugin/package.json +1 -1
  39. package/packages/health-monitor/package.json +1 -1
  40. package/packages/intent-analyzer/package.json +1 -1
  41. package/packages/jev-judge/package.json +1 -1
  42. package/packages/knowledge-graph/package.json +1 -1
  43. package/packages/knowledge-retrieval/package.json +1 -1
  44. package/packages/laravel-plugin/package.json +1 -1
  45. package/packages/learning-engine/package.json +1 -1
  46. package/packages/mcp-adapter/package.json +1 -1
  47. package/packages/memory-engine/package.json +1 -1
  48. package/packages/milestone-manager/package.json +1 -1
  49. package/packages/model-registry/package.json +1 -1
  50. package/packages/nextjs-plugin/package.json +1 -1
  51. package/packages/notification/package.json +1 -1
  52. package/packages/observability/package.json +1 -1
  53. package/packages/okf-indexer/package.json +1 -1
  54. package/packages/performance-optimizer/package.json +1 -1
  55. package/packages/privilege-broker/package.json +1 -1
  56. package/packages/project-analyzer/package.json +1 -1
  57. package/packages/project-bootstrap/package.json +1 -1
  58. package/packages/projection-engine/package.json +1 -1
  59. package/packages/prompt-compiler/package.json +1 -1
  60. package/packages/prompt-versioning/package.json +1 -1
  61. package/packages/provider-adapter-sdk/package.json +1 -1
  62. package/packages/provider-router/package.json +1 -1
  63. package/packages/provider-selector/package.json +1 -1
  64. package/packages/providers/package.json +1 -1
  65. package/packages/qdrant-skills/dist/client.d.ts +51 -0
  66. package/packages/qdrant-skills/dist/client.d.ts.map +1 -0
  67. package/packages/qdrant-skills/dist/client.js +105 -0
  68. package/packages/qdrant-skills/dist/client.js.map +1 -0
  69. package/packages/qdrant-skills/dist/embedder.d.ts +39 -0
  70. package/packages/qdrant-skills/dist/embedder.d.ts.map +1 -0
  71. package/packages/qdrant-skills/dist/embedder.js +101 -0
  72. package/packages/qdrant-skills/dist/embedder.js.map +1 -0
  73. package/packages/qdrant-skills/dist/index.d.ts +25 -0
  74. package/packages/qdrant-skills/dist/index.d.ts.map +1 -0
  75. package/packages/qdrant-skills/dist/index.js +25 -0
  76. package/packages/qdrant-skills/dist/index.js.map +1 -0
  77. package/packages/qdrant-skills/package.json +27 -0
  78. package/packages/qdrant-skills/src/client.ts +178 -0
  79. package/packages/qdrant-skills/src/embedder.ts +137 -0
  80. package/packages/qdrant-skills/src/index.ts +42 -0
  81. package/packages/quota-manager/package.json +1 -1
  82. package/packages/rate-limiter/package.json +1 -1
  83. package/packages/react-vite-plugin/package.json +1 -1
  84. package/packages/release-manager/package.json +1 -1
  85. package/packages/requirement-compiler/package.json +1 -1
  86. package/packages/runtime/package.json +1 -1
  87. package/packages/scheduler/package.json +1 -1
  88. package/packages/scheduler-adapter/package.json +1 -1
  89. package/packages/session/package.json +1 -1
  90. package/packages/session-api/package.json +1 -1
  91. package/packages/session-export/package.json +1 -1
  92. package/packages/shared-context/package.json +1 -1
  93. package/packages/skill-mcp-client/package.json +1 -1
  94. package/packages/skill-registry/package.json +1 -1
  95. package/packages/skills/dist/index.d.ts +40 -0
  96. package/packages/skills/dist/index.d.ts.map +1 -0
  97. package/packages/skills/dist/index.js +84 -0
  98. package/packages/skills/dist/index.js.map +1 -0
  99. package/packages/skills/dist/linter.d.ts +38 -0
  100. package/packages/skills/dist/linter.d.ts.map +1 -0
  101. package/packages/skills/dist/linter.js +323 -0
  102. package/packages/skills/dist/linter.js.map +1 -0
  103. package/packages/skills/dist/loader.d.ts +74 -0
  104. package/packages/skills/dist/loader.d.ts.map +1 -0
  105. package/packages/skills/dist/loader.js +280 -0
  106. package/packages/skills/dist/loader.js.map +1 -0
  107. package/packages/skills/dist/parser.d.ts +32 -0
  108. package/packages/skills/dist/parser.d.ts.map +1 -0
  109. package/packages/skills/dist/parser.js +459 -0
  110. package/packages/skills/dist/parser.js.map +1 -0
  111. package/packages/skills/dist/scanner.d.ts +33 -0
  112. package/packages/skills/dist/scanner.d.ts.map +1 -0
  113. package/packages/skills/dist/scanner.js +257 -0
  114. package/packages/skills/dist/scanner.js.map +1 -0
  115. package/packages/skills/dist/skill-commands.d.ts +33 -0
  116. package/packages/skills/dist/skill-commands.d.ts.map +1 -0
  117. package/packages/skills/dist/skill-commands.js +239 -0
  118. package/packages/skills/dist/skill-commands.js.map +1 -0
  119. package/packages/skills/dist/skill-tool.d.ts +120 -0
  120. package/packages/skills/dist/skill-tool.d.ts.map +1 -0
  121. package/packages/skills/dist/skill-tool.js +239 -0
  122. package/packages/skills/dist/skill-tool.js.map +1 -0
  123. package/packages/skills/dist/skill-versioning.d.ts +123 -0
  124. package/packages/skills/dist/skill-versioning.d.ts.map +1 -0
  125. package/packages/skills/dist/skill-versioning.js +240 -0
  126. package/packages/skills/dist/skill-versioning.js.map +1 -0
  127. package/packages/skills/dist/types.d.ts +226 -0
  128. package/packages/skills/dist/types.d.ts.map +1 -0
  129. package/packages/skills/dist/types.js +21 -0
  130. package/packages/skills/dist/types.js.map +1 -0
  131. package/packages/skills/package.json +46 -0
  132. package/packages/skills/src/index.ts +95 -0
  133. package/packages/skills/src/linter.ts +371 -0
  134. package/packages/skills/src/loader.ts +335 -0
  135. package/packages/skills/src/parser.ts +548 -0
  136. package/packages/skills/src/scanner.ts +322 -0
  137. package/packages/skills/src/skill-commands.ts +318 -0
  138. package/packages/skills/src/skill-tool.ts +324 -0
  139. package/packages/skills/src/skill-versioning.ts +345 -0
  140. package/packages/skills/src/types.ts +259 -0
  141. package/packages/skills/test/init-skills.test.ts +118 -0
  142. package/packages/sprint-planner/package.json +1 -1
  143. package/packages/subscription-engine/package.json +1 -1
  144. package/packages/task-compiler/package.json +1 -1
  145. package/packages/tencentdb-memory/package.json +1 -1
  146. package/packages/tencentdb-sync/package.json +1 -1
  147. package/packages/test-data-generator/package.json +1 -1
  148. package/packages/test-generator/package.json +1 -1
  149. package/packages/todo-bd-sync/package.json +1 -1
  150. package/packages/token-estimation/package.json +1 -1
  151. package/packages/token-optimizer/package.json +1 -1
  152. package/packages/trajectory/dist/src/curator.d.ts +189 -0
  153. package/packages/trajectory/dist/src/curator.d.ts.map +1 -0
  154. package/packages/trajectory/dist/src/curator.js +340 -0
  155. package/packages/trajectory/dist/src/curator.js.map +1 -0
  156. package/packages/trajectory/dist/src/journey-renderer.d.ts +53 -0
  157. package/packages/trajectory/dist/src/journey-renderer.d.ts.map +1 -0
  158. package/packages/trajectory/dist/src/journey-renderer.js +324 -0
  159. package/packages/trajectory/dist/src/journey-renderer.js.map +1 -0
  160. package/packages/trajectory/dist/src/learning-graph.d.ts +159 -0
  161. package/packages/trajectory/dist/src/learning-graph.d.ts.map +1 -0
  162. package/packages/trajectory/dist/src/learning-graph.js +258 -0
  163. package/packages/trajectory/dist/src/learning-graph.js.map +1 -0
  164. package/packages/trajectory/dist/src/memory-store.d.ts +146 -0
  165. package/packages/trajectory/dist/src/memory-store.d.ts.map +1 -0
  166. package/packages/trajectory/dist/src/memory-store.js +472 -0
  167. package/packages/trajectory/dist/src/memory-store.js.map +1 -0
  168. package/packages/trajectory/dist/src/review-fork.d.ts +164 -0
  169. package/packages/trajectory/dist/src/review-fork.d.ts.map +1 -0
  170. package/packages/trajectory/dist/src/review-fork.js +332 -0
  171. package/packages/trajectory/dist/src/review-fork.js.map +1 -0
  172. package/packages/trajectory/dist/src/write-approval.d.ts +136 -0
  173. package/packages/trajectory/dist/src/write-approval.d.ts.map +1 -0
  174. package/packages/trajectory/dist/src/write-approval.js +281 -0
  175. package/packages/trajectory/dist/src/write-approval.js.map +1 -0
  176. package/packages/trajectory/package.json +1 -1
  177. package/packages/trajectory/src/curator.ts +485 -0
  178. package/packages/trajectory/src/journey-renderer.ts +399 -0
  179. package/packages/trajectory/src/learning-graph.ts +363 -0
  180. package/packages/trajectory/src/memory-store.ts +578 -0
  181. package/packages/trajectory/src/review-fork.ts +450 -0
  182. package/packages/trajectory/src/write-approval.ts +372 -0
  183. package/packages/tui/package.json +1 -1
  184. package/packages/types/package.json +1 -1
  185. package/packages/workflow-events/package.json +1 -1
  186. package/packages/workspace-scanner/package.json +1 -1
  187. package/packages/worktree/package.json +1 -1
  188. package/packages/write-review/dist/blackboard.d.ts +98 -0
  189. package/packages/write-review/dist/blackboard.d.ts.map +1 -0
  190. package/packages/write-review/dist/blackboard.js +289 -0
  191. package/packages/write-review/dist/blackboard.js.map +1 -0
  192. package/packages/write-review/dist/event-bus.d.ts +106 -0
  193. package/packages/write-review/dist/event-bus.d.ts.map +1 -0
  194. package/packages/write-review/dist/event-bus.js +294 -0
  195. package/packages/write-review/dist/event-bus.js.map +1 -0
  196. package/packages/write-review/dist/gate.d.ts +37 -0
  197. package/packages/write-review/dist/gate.d.ts.map +1 -0
  198. package/packages/write-review/dist/gate.js +138 -0
  199. package/packages/write-review/dist/gate.js.map +1 -0
  200. package/packages/write-review/dist/index.d.ts +22 -0
  201. package/packages/write-review/dist/index.d.ts.map +1 -0
  202. package/packages/write-review/dist/index.js +60 -0
  203. package/packages/write-review/dist/index.js.map +1 -0
  204. package/packages/write-review/dist/injection.d.ts +40 -0
  205. package/packages/write-review/dist/injection.d.ts.map +1 -0
  206. package/packages/write-review/dist/injection.js +179 -0
  207. package/packages/write-review/dist/injection.js.map +1 -0
  208. package/packages/write-review/dist/review.d.ts +54 -0
  209. package/packages/write-review/dist/review.d.ts.map +1 -0
  210. package/packages/write-review/dist/review.js +154 -0
  211. package/packages/write-review/dist/review.js.map +1 -0
  212. package/packages/write-review/dist/trigger.d.ts +39 -0
  213. package/packages/write-review/dist/trigger.d.ts.map +1 -0
  214. package/packages/write-review/dist/trigger.js +107 -0
  215. package/packages/write-review/dist/trigger.js.map +1 -0
  216. package/packages/write-review/dist/types.d.ts +78 -0
  217. package/packages/write-review/dist/types.d.ts.map +1 -0
  218. package/packages/write-review/dist/types.js +26 -0
  219. package/packages/write-review/dist/types.js.map +1 -0
  220. package/packages/write-review/package.json +1 -1
  221. package/packages/write-review/src/event-bus.ts +351 -0
  222. package/packages/write-review/src/index.ts +1 -0
  223. package/packages/write-review/src/injection.ts +25 -3
  224. package/proactive-compact.ts +51 -7
  225. package/scripts/sync-skills.ts +275 -0
  226. 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
+ }