psychmem 1.0.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 +632 -0
- package/dist/adapters/claude-code/index.d.ts +125 -0
- package/dist/adapters/claude-code/index.d.ts.map +1 -0
- package/dist/adapters/claude-code/index.js +398 -0
- package/dist/adapters/claude-code/index.js.map +1 -0
- package/dist/adapters/opencode/index.d.ts +50 -0
- package/dist/adapters/opencode/index.d.ts.map +1 -0
- package/dist/adapters/opencode/index.js +793 -0
- package/dist/adapters/opencode/index.js.map +1 -0
- package/dist/adapters/types.d.ts +226 -0
- package/dist/adapters/types.d.ts.map +1 -0
- package/dist/adapters/types.js +6 -0
- package/dist/adapters/types.js.map +1 -0
- package/dist/cli.d.ts +19 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +461 -0
- package/dist/cli.js.map +1 -0
- package/dist/hooks/index.d.ts +92 -0
- package/dist/hooks/index.d.ts.map +1 -0
- package/dist/hooks/index.js +304 -0
- package/dist/hooks/index.js.map +1 -0
- package/dist/hooks/post-tool-use.d.ts +26 -0
- package/dist/hooks/post-tool-use.d.ts.map +1 -0
- package/dist/hooks/post-tool-use.js +69 -0
- package/dist/hooks/post-tool-use.js.map +1 -0
- package/dist/hooks/session-end.d.ts +32 -0
- package/dist/hooks/session-end.d.ts.map +1 -0
- package/dist/hooks/session-end.js +66 -0
- package/dist/hooks/session-end.js.map +1 -0
- package/dist/hooks/session-start.d.ts +55 -0
- package/dist/hooks/session-start.d.ts.map +1 -0
- package/dist/hooks/session-start.js +173 -0
- package/dist/hooks/session-start.js.map +1 -0
- package/dist/hooks/stop.d.ts +72 -0
- package/dist/hooks/stop.d.ts.map +1 -0
- package/dist/hooks/stop.js +273 -0
- package/dist/hooks/stop.js.map +1 -0
- package/dist/index.d.ts +114 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +191 -0
- package/dist/index.js.map +1 -0
- package/dist/memory/context-sweep.d.ts +107 -0
- package/dist/memory/context-sweep.d.ts.map +1 -0
- package/dist/memory/context-sweep.js +557 -0
- package/dist/memory/context-sweep.js.map +1 -0
- package/dist/memory/patterns.d.ts +106 -0
- package/dist/memory/patterns.d.ts.map +1 -0
- package/dist/memory/patterns.js +613 -0
- package/dist/memory/patterns.js.map +1 -0
- package/dist/memory/selective-memory.d.ts +78 -0
- package/dist/memory/selective-memory.d.ts.map +1 -0
- package/dist/memory/selective-memory.js +227 -0
- package/dist/memory/selective-memory.js.map +1 -0
- package/dist/memory/structural-analyzer.d.ts +75 -0
- package/dist/memory/structural-analyzer.d.ts.map +1 -0
- package/dist/memory/structural-analyzer.js +359 -0
- package/dist/memory/structural-analyzer.js.map +1 -0
- package/dist/retrieval/index.d.ts +106 -0
- package/dist/retrieval/index.d.ts.map +1 -0
- package/dist/retrieval/index.js +291 -0
- package/dist/retrieval/index.js.map +1 -0
- package/dist/storage/database.d.ts +138 -0
- package/dist/storage/database.d.ts.map +1 -0
- package/dist/storage/database.js +748 -0
- package/dist/storage/database.js.map +1 -0
- package/dist/storage/sqlite-adapter.d.ts +35 -0
- package/dist/storage/sqlite-adapter.d.ts.map +1 -0
- package/dist/storage/sqlite-adapter.js +103 -0
- package/dist/storage/sqlite-adapter.js.map +1 -0
- package/dist/transcript/index.d.ts +8 -0
- package/dist/transcript/index.d.ts.map +1 -0
- package/dist/transcript/index.js +6 -0
- package/dist/transcript/index.js.map +1 -0
- package/dist/transcript/parser.d.ts +93 -0
- package/dist/transcript/parser.d.ts.map +1 -0
- package/dist/transcript/parser.js +373 -0
- package/dist/transcript/parser.js.map +1 -0
- package/dist/transcript/sweep.d.ts +75 -0
- package/dist/transcript/sweep.d.ts.map +1 -0
- package/dist/transcript/sweep.js +202 -0
- package/dist/transcript/sweep.js.map +1 -0
- package/dist/types/index.d.ts +328 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +80 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/paths.d.ts +19 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +43 -0
- package/dist/utils/paths.js.map +1 -0
- package/hooks/hooks.json +54 -0
- package/package.json +83 -0
- package/plugin.js +45 -0
- package/plugin.json +19 -0
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript Parser for Claude Code
|
|
3
|
+
*
|
|
4
|
+
* Parses Claude Code's JSONL transcript files incrementally using watermark tracking.
|
|
5
|
+
* The watermark is a byte offset to avoid re-processing already-seen content.
|
|
6
|
+
*
|
|
7
|
+
* Claude Code transcript format (JSONL):
|
|
8
|
+
* Each line is a JSON object with various message types. Schema is not fully documented,
|
|
9
|
+
* so we use flexible parsing with fallback handling.
|
|
10
|
+
*/
|
|
11
|
+
import * as fs from 'fs';
|
|
12
|
+
import * as path from 'path';
|
|
13
|
+
import * as readline from 'readline';
|
|
14
|
+
// =============================================================================
|
|
15
|
+
// Parser Implementation
|
|
16
|
+
// =============================================================================
|
|
17
|
+
export class TranscriptParser {
|
|
18
|
+
/**
|
|
19
|
+
* Parse transcript file from a given byte offset
|
|
20
|
+
* Returns entries from that point forward and the new watermark
|
|
21
|
+
*/
|
|
22
|
+
async parseFromWatermark(transcriptPath, watermark = 0) {
|
|
23
|
+
// Resolve path if needed
|
|
24
|
+
const resolvedPath = this.resolvePath(transcriptPath);
|
|
25
|
+
// Check if file exists
|
|
26
|
+
if (!fs.existsSync(resolvedPath)) {
|
|
27
|
+
return {
|
|
28
|
+
entries: [],
|
|
29
|
+
newWatermark: watermark,
|
|
30
|
+
linesProcessed: 0,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
const entries = [];
|
|
34
|
+
let newWatermark = watermark;
|
|
35
|
+
let linesProcessed = 0;
|
|
36
|
+
// Open file and seek to watermark position
|
|
37
|
+
const fileHandle = await fs.promises.open(resolvedPath, 'r');
|
|
38
|
+
try {
|
|
39
|
+
const stats = await fileHandle.stat();
|
|
40
|
+
// If watermark is beyond file size, no new content
|
|
41
|
+
if (watermark >= stats.size) {
|
|
42
|
+
return {
|
|
43
|
+
entries: [],
|
|
44
|
+
newWatermark: watermark,
|
|
45
|
+
linesProcessed: 0,
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
// Create read stream from watermark position
|
|
49
|
+
const stream = fs.createReadStream(resolvedPath, {
|
|
50
|
+
start: watermark,
|
|
51
|
+
encoding: 'utf-8',
|
|
52
|
+
});
|
|
53
|
+
// Read raw bytes separately to detect CRLF vs LF line endings
|
|
54
|
+
const rawStream = fs.createReadStream(resolvedPath, {
|
|
55
|
+
start: watermark,
|
|
56
|
+
});
|
|
57
|
+
// Detect line endings by scanning a small buffer
|
|
58
|
+
let crlfDetected = false;
|
|
59
|
+
let crlfSampleDone = false;
|
|
60
|
+
const sampleChunks = [];
|
|
61
|
+
rawStream.on('data', (chunk) => {
|
|
62
|
+
if (!crlfSampleDone) {
|
|
63
|
+
sampleChunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
64
|
+
const combined = Buffer.concat(sampleChunks);
|
|
65
|
+
if (combined.includes(0x0d /* \r */)) {
|
|
66
|
+
crlfDetected = true;
|
|
67
|
+
crlfSampleDone = true;
|
|
68
|
+
}
|
|
69
|
+
else if (combined.length >= 4096) {
|
|
70
|
+
crlfSampleDone = true;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
});
|
|
74
|
+
rawStream.on('error', () => { crlfSampleDone = true; });
|
|
75
|
+
await new Promise((resolve) => rawStream.on('close', resolve));
|
|
76
|
+
const rl = readline.createInterface({
|
|
77
|
+
input: stream,
|
|
78
|
+
crlfDelay: Infinity,
|
|
79
|
+
});
|
|
80
|
+
let bytesRead = watermark;
|
|
81
|
+
// readline with crlfDelay:Infinity strips \r, so each line's byte length
|
|
82
|
+
// needs +1 for \n (LF files) or +2 for \r\n (CRLF files)
|
|
83
|
+
const newlineBytes = crlfDetected ? 2 : 1;
|
|
84
|
+
for await (const line of rl) {
|
|
85
|
+
// Track byte position (line content + newline byte(s))
|
|
86
|
+
bytesRead += Buffer.byteLength(line, 'utf-8') + newlineBytes;
|
|
87
|
+
if (line.trim().length === 0)
|
|
88
|
+
continue;
|
|
89
|
+
try {
|
|
90
|
+
const entry = this.parseLine(line);
|
|
91
|
+
if (entry) {
|
|
92
|
+
entries.push(entry);
|
|
93
|
+
}
|
|
94
|
+
linesProcessed++;
|
|
95
|
+
}
|
|
96
|
+
catch (e) {
|
|
97
|
+
// Skip malformed lines but continue processing
|
|
98
|
+
console.error(`[TranscriptParser] Skipping malformed line: ${e}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
newWatermark = bytesRead;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await fileHandle.close();
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
entries,
|
|
108
|
+
newWatermark,
|
|
109
|
+
linesProcessed,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Parse a single JSONL line into a transcript entry
|
|
114
|
+
* Handles various Claude Code message formats flexibly
|
|
115
|
+
*/
|
|
116
|
+
parseLine(line) {
|
|
117
|
+
const json = JSON.parse(line);
|
|
118
|
+
// Detect message type and extract content
|
|
119
|
+
const type = this.detectEntryType(json);
|
|
120
|
+
const content = this.extractContent(json);
|
|
121
|
+
if (!content || content.trim().length === 0) {
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
// Build entry with only defined optional properties
|
|
125
|
+
const timestamp = this.extractTimestamp(json);
|
|
126
|
+
const role = this.extractRole(json);
|
|
127
|
+
const toolName = this.extractToolName(json);
|
|
128
|
+
const toolInput = this.extractToolInput(json);
|
|
129
|
+
const toolOutput = this.extractToolOutput(json);
|
|
130
|
+
const entry = {
|
|
131
|
+
type,
|
|
132
|
+
content,
|
|
133
|
+
rawJson: json,
|
|
134
|
+
};
|
|
135
|
+
// Only add optional properties if defined
|
|
136
|
+
if (timestamp !== undefined)
|
|
137
|
+
entry.timestamp = timestamp;
|
|
138
|
+
if (role !== undefined)
|
|
139
|
+
entry.role = role;
|
|
140
|
+
if (toolName !== undefined)
|
|
141
|
+
entry.toolName = toolName;
|
|
142
|
+
if (toolInput !== undefined)
|
|
143
|
+
entry.toolInput = toolInput;
|
|
144
|
+
if (toolOutput !== undefined)
|
|
145
|
+
entry.toolOutput = toolOutput;
|
|
146
|
+
return entry;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Detect the type of transcript entry
|
|
150
|
+
*/
|
|
151
|
+
detectEntryType(json) {
|
|
152
|
+
// Check for explicit type field
|
|
153
|
+
const type = json.type;
|
|
154
|
+
if (type === 'user' || json.role === 'user') {
|
|
155
|
+
return 'user_message';
|
|
156
|
+
}
|
|
157
|
+
if (type === 'assistant' || json.role === 'assistant') {
|
|
158
|
+
// Check if it's a tool use
|
|
159
|
+
if (json.tool_use || json.toolName || this.hasToolUseContent(json)) {
|
|
160
|
+
return 'tool_use';
|
|
161
|
+
}
|
|
162
|
+
return 'assistant_message';
|
|
163
|
+
}
|
|
164
|
+
if (type === 'tool_result' || json.tool_result || json.toolOutput !== undefined) {
|
|
165
|
+
return 'tool_result';
|
|
166
|
+
}
|
|
167
|
+
if (type === 'system' || json.role === 'system') {
|
|
168
|
+
return 'system';
|
|
169
|
+
}
|
|
170
|
+
// Check content structure for tool patterns
|
|
171
|
+
if (json.tool_use || json.name || json.input) {
|
|
172
|
+
return 'tool_use';
|
|
173
|
+
}
|
|
174
|
+
// Default based on role
|
|
175
|
+
const role = json.role;
|
|
176
|
+
if (role === 'user')
|
|
177
|
+
return 'user_message';
|
|
178
|
+
if (role === 'assistant')
|
|
179
|
+
return 'assistant_message';
|
|
180
|
+
return 'unknown';
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Check if json contains tool use content
|
|
184
|
+
*/
|
|
185
|
+
hasToolUseContent(json) {
|
|
186
|
+
// Check various possible locations for tool use
|
|
187
|
+
if (json.content && Array.isArray(json.content)) {
|
|
188
|
+
return json.content.some((c) => typeof c === 'object' && c !== null && 'type' in c && c.type === 'tool_use');
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Extract text content from various formats
|
|
194
|
+
*/
|
|
195
|
+
extractContent(json) {
|
|
196
|
+
// Direct content string
|
|
197
|
+
if (typeof json.content === 'string') {
|
|
198
|
+
return json.content;
|
|
199
|
+
}
|
|
200
|
+
// Content array (Claude API format)
|
|
201
|
+
if (Array.isArray(json.content)) {
|
|
202
|
+
const textParts = [];
|
|
203
|
+
for (const part of json.content) {
|
|
204
|
+
if (typeof part === 'string') {
|
|
205
|
+
textParts.push(part);
|
|
206
|
+
}
|
|
207
|
+
else if (typeof part === 'object' && part !== null) {
|
|
208
|
+
const obj = part;
|
|
209
|
+
// Text block
|
|
210
|
+
if (obj.type === 'text' && typeof obj.text === 'string') {
|
|
211
|
+
textParts.push(obj.text);
|
|
212
|
+
}
|
|
213
|
+
// Tool use block
|
|
214
|
+
if (obj.type === 'tool_use') {
|
|
215
|
+
const toolName = obj.name || 'unknown';
|
|
216
|
+
const input = obj.input ? JSON.stringify(obj.input) : '';
|
|
217
|
+
textParts.push(`[Tool: ${toolName}] ${input}`);
|
|
218
|
+
}
|
|
219
|
+
// Tool result block
|
|
220
|
+
if (obj.type === 'tool_result') {
|
|
221
|
+
const content = typeof obj.content === 'string'
|
|
222
|
+
? obj.content
|
|
223
|
+
: JSON.stringify(obj.content);
|
|
224
|
+
textParts.push(`[Tool Result] ${content}`);
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return textParts.join('\n');
|
|
229
|
+
}
|
|
230
|
+
// Message field
|
|
231
|
+
if (typeof json.message === 'string') {
|
|
232
|
+
return json.message;
|
|
233
|
+
}
|
|
234
|
+
// Text field
|
|
235
|
+
if (typeof json.text === 'string') {
|
|
236
|
+
return json.text;
|
|
237
|
+
}
|
|
238
|
+
// Tool output
|
|
239
|
+
if (json.toolOutput !== undefined) {
|
|
240
|
+
return typeof json.toolOutput === 'string'
|
|
241
|
+
? json.toolOutput
|
|
242
|
+
: JSON.stringify(json.toolOutput);
|
|
243
|
+
}
|
|
244
|
+
// Fallback: stringify the whole object
|
|
245
|
+
return '';
|
|
246
|
+
}
|
|
247
|
+
/**
|
|
248
|
+
* Extract timestamp from entry
|
|
249
|
+
*/
|
|
250
|
+
extractTimestamp(json) {
|
|
251
|
+
if (typeof json.timestamp === 'string')
|
|
252
|
+
return json.timestamp;
|
|
253
|
+
if (typeof json.created_at === 'string')
|
|
254
|
+
return json.created_at;
|
|
255
|
+
if (typeof json.time === 'string')
|
|
256
|
+
return json.time;
|
|
257
|
+
return undefined;
|
|
258
|
+
}
|
|
259
|
+
/**
|
|
260
|
+
* Extract role from entry
|
|
261
|
+
*/
|
|
262
|
+
extractRole(json) {
|
|
263
|
+
const role = json.role;
|
|
264
|
+
if (role === 'user' || role === 'assistant' || role === 'system' || role === 'tool') {
|
|
265
|
+
return role;
|
|
266
|
+
}
|
|
267
|
+
return undefined;
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Extract tool name
|
|
271
|
+
*/
|
|
272
|
+
extractToolName(json) {
|
|
273
|
+
if (typeof json.name === 'string')
|
|
274
|
+
return json.name;
|
|
275
|
+
if (typeof json.toolName === 'string')
|
|
276
|
+
return json.toolName;
|
|
277
|
+
if (typeof json.tool_name === 'string')
|
|
278
|
+
return json.tool_name;
|
|
279
|
+
// Check in content array
|
|
280
|
+
if (Array.isArray(json.content)) {
|
|
281
|
+
for (const part of json.content) {
|
|
282
|
+
if (typeof part === 'object' && part !== null) {
|
|
283
|
+
const obj = part;
|
|
284
|
+
if (obj.type === 'tool_use' && typeof obj.name === 'string') {
|
|
285
|
+
return obj.name;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return undefined;
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Extract tool input
|
|
294
|
+
*/
|
|
295
|
+
extractToolInput(json) {
|
|
296
|
+
if (json.input !== undefined)
|
|
297
|
+
return json.input;
|
|
298
|
+
if (json.toolInput !== undefined)
|
|
299
|
+
return json.toolInput;
|
|
300
|
+
if (json.tool_input !== undefined)
|
|
301
|
+
return json.tool_input;
|
|
302
|
+
// Check in content array
|
|
303
|
+
if (Array.isArray(json.content)) {
|
|
304
|
+
for (const part of json.content) {
|
|
305
|
+
if (typeof part === 'object' && part !== null) {
|
|
306
|
+
const obj = part;
|
|
307
|
+
if (obj.type === 'tool_use' && obj.input !== undefined) {
|
|
308
|
+
return obj.input;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
return undefined;
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Extract tool output
|
|
317
|
+
*/
|
|
318
|
+
extractToolOutput(json) {
|
|
319
|
+
if (json.output !== undefined)
|
|
320
|
+
return json.output;
|
|
321
|
+
if (json.toolOutput !== undefined)
|
|
322
|
+
return json.toolOutput;
|
|
323
|
+
if (json.tool_output !== undefined)
|
|
324
|
+
return json.tool_output;
|
|
325
|
+
// Check in content array for tool_result
|
|
326
|
+
if (Array.isArray(json.content)) {
|
|
327
|
+
for (const part of json.content) {
|
|
328
|
+
if (typeof part === 'object' && part !== null) {
|
|
329
|
+
const obj = part;
|
|
330
|
+
if (obj.type === 'tool_result') {
|
|
331
|
+
return obj.content;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Resolve path, expanding ~ to home directory
|
|
340
|
+
*/
|
|
341
|
+
resolvePath(filePath) {
|
|
342
|
+
if (filePath.startsWith('~')) {
|
|
343
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
344
|
+
return path.join(home, filePath.slice(1));
|
|
345
|
+
}
|
|
346
|
+
return path.resolve(filePath);
|
|
347
|
+
}
|
|
348
|
+
/**
|
|
349
|
+
* Get conversation text from entries (for context sweep)
|
|
350
|
+
*/
|
|
351
|
+
static entriesToConversationText(entries) {
|
|
352
|
+
return entries
|
|
353
|
+
.map(entry => {
|
|
354
|
+
const rolePrefix = entry.role ? `[${entry.role}] ` : '';
|
|
355
|
+
return `${rolePrefix}${entry.content}`;
|
|
356
|
+
})
|
|
357
|
+
.join('\n\n');
|
|
358
|
+
}
|
|
359
|
+
/**
|
|
360
|
+
* Filter entries to only user and assistant messages (no tool details)
|
|
361
|
+
*/
|
|
362
|
+
static filterConversation(entries) {
|
|
363
|
+
return entries.filter(e => e.type === 'user_message' || e.type === 'assistant_message');
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Get only new entries since last check (stateless helper)
|
|
367
|
+
*/
|
|
368
|
+
static async getNewEntries(transcriptPath, lastWatermark) {
|
|
369
|
+
const parser = new TranscriptParser();
|
|
370
|
+
return parser.parseFromWatermark(transcriptPath, lastWatermark);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
//# sourceMappingURL=parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"parser.js","sourceRoot":"","sources":["../../src/transcript/parser.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript Sweep - Memory extraction from transcript with deduplication
|
|
3
|
+
*
|
|
4
|
+
* This module coordinates:
|
|
5
|
+
* 1. Parsing new transcript entries (using watermark)
|
|
6
|
+
* 2. Running context sweep on new content
|
|
7
|
+
* 3. Deduplicating against existing session memories (70% keyword overlap)
|
|
8
|
+
* 4. Limiting to maxMemoriesPerStop (based on Cowan's working memory: 4±1)
|
|
9
|
+
*/
|
|
10
|
+
import type { MemoryCandidate, MemoryUnit, PsychMemConfig } from '../types/index.js';
|
|
11
|
+
export interface TranscriptSweepResult {
|
|
12
|
+
/** Memory candidates after deduplication */
|
|
13
|
+
candidates: MemoryCandidate[];
|
|
14
|
+
/** New watermark to save */
|
|
15
|
+
newWatermark: number;
|
|
16
|
+
/** Number of candidates before deduplication */
|
|
17
|
+
rawCandidateCount: number;
|
|
18
|
+
/** Number filtered by deduplication */
|
|
19
|
+
deduplicatedCount: number;
|
|
20
|
+
/** Number filtered by limit */
|
|
21
|
+
limitedCount: number;
|
|
22
|
+
}
|
|
23
|
+
export interface TranscriptSweepOptions {
|
|
24
|
+
/** Session ID for deduplication lookup */
|
|
25
|
+
sessionId: string;
|
|
26
|
+
/** Existing memories for this session */
|
|
27
|
+
existingMemories: MemoryUnit[];
|
|
28
|
+
/** Configuration */
|
|
29
|
+
config?: Partial<PsychMemConfig>;
|
|
30
|
+
}
|
|
31
|
+
export declare class TranscriptSweep {
|
|
32
|
+
private parser;
|
|
33
|
+
private contextSweep;
|
|
34
|
+
private config;
|
|
35
|
+
constructor(config?: Partial<PsychMemConfig>);
|
|
36
|
+
/**
|
|
37
|
+
* Main entry point: extract memories from transcript since last watermark
|
|
38
|
+
* with deduplication and limits
|
|
39
|
+
*/
|
|
40
|
+
sweepTranscript(transcriptPath: string, watermark: number, options: TranscriptSweepOptions): Promise<TranscriptSweepResult>;
|
|
41
|
+
/**
|
|
42
|
+
* Extract memory candidates from conversation text
|
|
43
|
+
* Creates synthetic events for context sweep compatibility
|
|
44
|
+
*/
|
|
45
|
+
private extractFromText;
|
|
46
|
+
/**
|
|
47
|
+
* Map transcript entry type to hook type for context sweep
|
|
48
|
+
*/
|
|
49
|
+
private entryTypeToHookType;
|
|
50
|
+
/**
|
|
51
|
+
* Deduplicate candidates against existing memories using keyword overlap
|
|
52
|
+
*/
|
|
53
|
+
private deduplicateCandidates;
|
|
54
|
+
/**
|
|
55
|
+
* Apply the max memories per stop limit
|
|
56
|
+
* Sort by importance and take top N
|
|
57
|
+
*/
|
|
58
|
+
private applyLimit;
|
|
59
|
+
/**
|
|
60
|
+
* Extract keywords from text for overlap calculation
|
|
61
|
+
* Returns a Set of normalized keywords
|
|
62
|
+
*/
|
|
63
|
+
private extractKeywords;
|
|
64
|
+
/**
|
|
65
|
+
* Calculate Jaccard similarity (overlap) between two keyword sets
|
|
66
|
+
* Returns a value between 0 and 1
|
|
67
|
+
*/
|
|
68
|
+
private calculateKeywordOverlap;
|
|
69
|
+
/**
|
|
70
|
+
* Static helper: quick check if transcript has new content
|
|
71
|
+
*/
|
|
72
|
+
static hasNewContent(transcriptPath: string, watermark: number): Promise<boolean>;
|
|
73
|
+
}
|
|
74
|
+
export { TranscriptParser } from './parser.js';
|
|
75
|
+
//# sourceMappingURL=sweep.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sweep.d.ts","sourceRoot":"","sources":["../../src/transcript/sweep.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,KAAK,EACV,eAAe,EACf,UAAU,EACV,cAAc,EACf,MAAM,mBAAmB,CAAC;AAS3B,MAAM,WAAW,qBAAqB;IACpC,4CAA4C;IAC5C,UAAU,EAAE,eAAe,EAAE,CAAC;IAC9B,4BAA4B;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,gDAAgD;IAChD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,uCAAuC;IACvC,iBAAiB,EAAE,MAAM,CAAC;IAC1B,+BAA+B;IAC/B,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,sBAAsB;IACrC,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;IAClB,yCAAyC;IACzC,gBAAgB,EAAE,UAAU,EAAE,CAAC;IAC/B,oBAAoB;IACpB,MAAM,CAAC,EAAE,OAAO,CAAC,cAAc,CAAC,CAAC;CAClC;AAMD,qBAAa,eAAe;IAC1B,OAAO,CAAC,MAAM,CAAmB;IACjC,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,MAAM,CAAiB;gBAEnB,MAAM,GAAE,OAAO,CAAC,cAAc,CAAM;IAMhD;;;OAGG;IACG,eAAe,CACnB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,sBAAsB,GAC9B,OAAO,CAAC,qBAAqB,CAAC;IAoCjC;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgCvB;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAY3B;;OAEG;IACH,OAAO,CAAC,qBAAqB;IA0C7B;;;OAGG;IACH,OAAO,CAAC,UAAU;IAkBlB;;;OAGG;IACH,OAAO,CAAC,eAAe;IAsBvB;;;OAGG;IACH,OAAO,CAAC,uBAAuB;IAmB/B;;OAEG;WACU,aAAa,CACxB,cAAc,EAAE,MAAM,EACtB,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,OAAO,CAAC;CAIpB;AAMD,OAAO,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript Sweep - Memory extraction from transcript with deduplication
|
|
3
|
+
*
|
|
4
|
+
* This module coordinates:
|
|
5
|
+
* 1. Parsing new transcript entries (using watermark)
|
|
6
|
+
* 2. Running context sweep on new content
|
|
7
|
+
* 3. Deduplicating against existing session memories (70% keyword overlap)
|
|
8
|
+
* 4. Limiting to maxMemoriesPerStop (based on Cowan's working memory: 4±1)
|
|
9
|
+
*/
|
|
10
|
+
import { TranscriptParser } from './parser.js';
|
|
11
|
+
import { ContextSweep } from '../memory/context-sweep.js';
|
|
12
|
+
import { DEFAULT_CONFIG } from '../types/index.js';
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// Transcript Sweep Implementation
|
|
15
|
+
// =============================================================================
|
|
16
|
+
export class TranscriptSweep {
|
|
17
|
+
parser;
|
|
18
|
+
contextSweep;
|
|
19
|
+
config;
|
|
20
|
+
constructor(config = {}) {
|
|
21
|
+
this.config = { ...DEFAULT_CONFIG, ...config };
|
|
22
|
+
this.parser = new TranscriptParser();
|
|
23
|
+
this.contextSweep = new ContextSweep(this.config.sweep);
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Main entry point: extract memories from transcript since last watermark
|
|
27
|
+
* with deduplication and limits
|
|
28
|
+
*/
|
|
29
|
+
async sweepTranscript(transcriptPath, watermark, options) {
|
|
30
|
+
// 1. Parse new transcript entries
|
|
31
|
+
const parseResult = await this.parser.parseFromWatermark(transcriptPath, watermark);
|
|
32
|
+
if (parseResult.entries.length === 0) {
|
|
33
|
+
return {
|
|
34
|
+
candidates: [],
|
|
35
|
+
newWatermark: parseResult.newWatermark,
|
|
36
|
+
rawCandidateCount: 0,
|
|
37
|
+
deduplicatedCount: 0,
|
|
38
|
+
limitedCount: 0,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
// 2. Convert entries to conversation text and run context sweep
|
|
42
|
+
const conversationText = TranscriptParser.entriesToConversationText(parseResult.entries);
|
|
43
|
+
const rawCandidates = this.extractFromText(conversationText, parseResult.entries);
|
|
44
|
+
// 3. Deduplicate against existing session memories
|
|
45
|
+
const dedupResult = this.deduplicateCandidates(rawCandidates, options.existingMemories);
|
|
46
|
+
// 4. Apply limit (maxMemoriesPerStop)
|
|
47
|
+
const limited = this.applyLimit(dedupResult.candidates);
|
|
48
|
+
return {
|
|
49
|
+
candidates: limited.candidates,
|
|
50
|
+
newWatermark: parseResult.newWatermark,
|
|
51
|
+
rawCandidateCount: rawCandidates.length,
|
|
52
|
+
deduplicatedCount: dedupResult.removedCount,
|
|
53
|
+
limitedCount: limited.removedCount,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Extract memory candidates from conversation text
|
|
58
|
+
* Creates synthetic events for context sweep compatibility
|
|
59
|
+
*/
|
|
60
|
+
extractFromText(text, entries) {
|
|
61
|
+
// Create synthetic events for the context sweep
|
|
62
|
+
const syntheticEvents = entries.map((entry, index) => ({
|
|
63
|
+
id: `transcript-${index}`,
|
|
64
|
+
sessionId: 'transcript',
|
|
65
|
+
hookType: this.entryTypeToHookType(entry.type),
|
|
66
|
+
timestamp: entry.timestamp ? new Date(entry.timestamp) : new Date(),
|
|
67
|
+
content: entry.content,
|
|
68
|
+
toolName: entry.toolName,
|
|
69
|
+
toolInput: entry.toolInput ? JSON.stringify(entry.toolInput) : undefined,
|
|
70
|
+
toolOutput: entry.toolOutput ? JSON.stringify(entry.toolOutput) : undefined,
|
|
71
|
+
}));
|
|
72
|
+
// Also add a Stop event with full conversation for comprehensive analysis
|
|
73
|
+
syntheticEvents.push({
|
|
74
|
+
id: 'transcript-stop',
|
|
75
|
+
sessionId: 'transcript',
|
|
76
|
+
hookType: 'Stop',
|
|
77
|
+
timestamp: new Date(),
|
|
78
|
+
content: text,
|
|
79
|
+
toolName: undefined,
|
|
80
|
+
toolInput: undefined,
|
|
81
|
+
toolOutput: undefined,
|
|
82
|
+
});
|
|
83
|
+
// Run context sweep
|
|
84
|
+
return this.contextSweep.extractCandidates(syntheticEvents);
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Map transcript entry type to hook type for context sweep
|
|
88
|
+
*/
|
|
89
|
+
entryTypeToHookType(type) {
|
|
90
|
+
switch (type) {
|
|
91
|
+
case 'user_message':
|
|
92
|
+
return 'UserPromptSubmit';
|
|
93
|
+
case 'tool_use':
|
|
94
|
+
case 'tool_result':
|
|
95
|
+
return 'PostToolUse';
|
|
96
|
+
default:
|
|
97
|
+
return 'Stop';
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Deduplicate candidates against existing memories using keyword overlap
|
|
102
|
+
*/
|
|
103
|
+
deduplicateCandidates(candidates, existingMemories) {
|
|
104
|
+
if (existingMemories.length === 0) {
|
|
105
|
+
return { candidates, removedCount: 0 };
|
|
106
|
+
}
|
|
107
|
+
// Pre-compute keywords for existing memories
|
|
108
|
+
const existingKeywordSets = existingMemories.map(mem => ({
|
|
109
|
+
memory: mem,
|
|
110
|
+
keywords: this.extractKeywords(mem.summary),
|
|
111
|
+
}));
|
|
112
|
+
const filtered = [];
|
|
113
|
+
let removedCount = 0;
|
|
114
|
+
for (const candidate of candidates) {
|
|
115
|
+
const candidateKeywords = this.extractKeywords(candidate.summary);
|
|
116
|
+
// Check overlap with each existing memory
|
|
117
|
+
let isDuplicate = false;
|
|
118
|
+
for (const { keywords: existingKeywords } of existingKeywordSets) {
|
|
119
|
+
const overlap = this.calculateKeywordOverlap(candidateKeywords, existingKeywords);
|
|
120
|
+
if (overlap >= this.config.deduplicationThreshold) {
|
|
121
|
+
isDuplicate = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (isDuplicate) {
|
|
126
|
+
removedCount++;
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
filtered.push(candidate);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return { candidates: filtered, removedCount };
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* Apply the max memories per stop limit
|
|
136
|
+
* Sort by importance and take top N
|
|
137
|
+
*/
|
|
138
|
+
applyLimit(candidates) {
|
|
139
|
+
if (candidates.length <= this.config.maxMemoriesPerStop) {
|
|
140
|
+
return { candidates, removedCount: 0 };
|
|
141
|
+
}
|
|
142
|
+
// Sort by preliminary importance (descending)
|
|
143
|
+
const sorted = [...candidates].sort((a, b) => b.preliminaryImportance - a.preliminaryImportance);
|
|
144
|
+
const limited = sorted.slice(0, this.config.maxMemoriesPerStop);
|
|
145
|
+
const removedCount = candidates.length - limited.length;
|
|
146
|
+
return { candidates: limited, removedCount };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Extract keywords from text for overlap calculation
|
|
150
|
+
* Returns a Set of normalized keywords
|
|
151
|
+
*/
|
|
152
|
+
extractKeywords(text) {
|
|
153
|
+
// Stopwords to filter out
|
|
154
|
+
const stopwords = new Set([
|
|
155
|
+
'the', 'a', 'an', 'is', 'are', 'was', 'were', 'be', 'been', 'being',
|
|
156
|
+
'have', 'has', 'had', 'do', 'does', 'did', 'will', 'would', 'could',
|
|
157
|
+
'should', 'may', 'might', 'can', 'to', 'of', 'in', 'for', 'on', 'with',
|
|
158
|
+
'at', 'by', 'from', 'as', 'into', 'through', 'during', 'before', 'after',
|
|
159
|
+
'this', 'that', 'these', 'those', 'it', 'its', 'they', 'them', 'their',
|
|
160
|
+
'and', 'or', 'but', 'if', 'so', 'because', 'while', 'when', 'where',
|
|
161
|
+
'what', 'which', 'who', 'how', 'why', 'all', 'each', 'some', 'any',
|
|
162
|
+
'i', 'me', 'my', 'we', 'our', 'you', 'your', 'he', 'she', 'him', 'her',
|
|
163
|
+
]);
|
|
164
|
+
const words = text
|
|
165
|
+
.toLowerCase()
|
|
166
|
+
.replace(/[^a-z0-9\s]/g, ' ')
|
|
167
|
+
.split(/\s+/)
|
|
168
|
+
.filter(word => word.length > 2 && !stopwords.has(word));
|
|
169
|
+
return new Set(words);
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* Calculate Jaccard similarity (overlap) between two keyword sets
|
|
173
|
+
* Returns a value between 0 and 1
|
|
174
|
+
*/
|
|
175
|
+
calculateKeywordOverlap(setA, setB) {
|
|
176
|
+
if (setA.size === 0 || setB.size === 0) {
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
let intersection = 0;
|
|
180
|
+
for (const word of setA) {
|
|
181
|
+
if (setB.has(word)) {
|
|
182
|
+
intersection++;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
const union = setA.size + setB.size - intersection;
|
|
186
|
+
if (union === 0)
|
|
187
|
+
return 0;
|
|
188
|
+
return intersection / union;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Static helper: quick check if transcript has new content
|
|
192
|
+
*/
|
|
193
|
+
static async hasNewContent(transcriptPath, watermark) {
|
|
194
|
+
const result = await TranscriptParser.getNewEntries(transcriptPath, watermark);
|
|
195
|
+
return result.entries.length > 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
// =============================================================================
|
|
199
|
+
// Export index for module
|
|
200
|
+
// =============================================================================
|
|
201
|
+
export { TranscriptParser } from './parser.js';
|
|
202
|
+
//# sourceMappingURL=sweep.js.map
|