@memorax/memorax-code 0.1.7 → 0.1.8

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 (69) hide show
  1. package/README.md +36 -5
  2. package/bin/memorax-code-setup.mjs +0 -3
  3. package/docs/configuration.md +1 -3
  4. package/docs/troubleshooting.md +9 -8
  5. package/lib/memorax-code-backend/dist/app/backend-server.js +2 -29
  6. package/lib/memorax-code-backend/dist/app/memory-observability.js +1 -4
  7. package/lib/memorax-code-backend/dist/app/state.js +1 -12
  8. package/lib/memorax-code-backend/dist/clients/claude/transcript-turn.js +0 -182
  9. package/lib/memorax-code-backend/dist/clients/opencode/message-turn.js +68 -2
  10. package/lib/memorax-code-backend/dist/lifecycle/backend/service.js +0 -5
  11. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +1 -14
  12. package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +8 -5
  13. package/lib/memorax-code-backend/dist/memory/project.js +4 -26
  14. package/lib/memorax-code-backend/dist/memory/quota-notice.js +79 -19
  15. package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +1 -4
  16. package/lib/memorax-code-backend/dist/provider/memorax/http.js +0 -28
  17. package/lib/memorax-code-backend/dist/transport/http/request.js +1 -39
  18. package/lib/memorax-code-backend/package.json +2 -3
  19. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  20. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  21. package/lib/memorax-code-claude-adapter/package.json +1 -1
  22. package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
  23. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  24. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  25. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  26. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  27. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  28. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
  29. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  30. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  31. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  32. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  33. package/lib/memorax-code-codex-adapter/package.json +1 -1
  34. package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +0 -6
  35. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  36. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  37. package/lib/memorax-code-dsh-adapter/package.json +1 -1
  38. package/lib/memorax-code-dsh-adapter/skills/memorax-code/SKILL.md +0 -6
  39. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  40. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  41. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  42. package/lib/memorax-code-opencode-adapter/skills/memorax-code/SKILL.md +0 -6
  43. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  44. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  45. package/lib/memorax-code-opencode-adapter/src/plugin.mjs +121 -16
  46. package/lib/memorax-code-opencode-adapter/src/repo-memory-server-runner.mjs +2 -0
  47. package/package.json +1 -1
  48. package/lib/memorax-code-backend/dist/clients/codex/effective-prompt.js +0 -54
  49. package/lib/memorax-code-backend/dist/memory/writeback-reconciler.js +0 -244
  50. package/lib/memorax-code-backend/dist/memory/writeback-task-projection.js +0 -377
  51. package/lib/memorax-code-backend/dist/repository/readiness.js +0 -188
  52. package/lib/memorax-code-backend/dist/shared/incremental-jsonl-projection.js +0 -271
  53. package/lib/memorax-code-backend/dist/viewer/history/claude-transcript.js +0 -648
  54. package/lib/memorax-code-backend/dist/viewer/history/session-title.js +0 -93
  55. package/lib/memorax-code-backend/dist/viewer/http/public-routes.js +0 -146
  56. package/lib/memorax-code-backend/dist/viewer/model.js +0 -1
  57. package/lib/memorax-code-backend/dist/viewer/projection/activity.js +0 -113
  58. package/lib/memorax-code-backend/dist/viewer/projection/cache.js +0 -69
  59. package/lib/memorax-code-backend/dist/viewer/projection/event-identity.js +0 -3
  60. package/lib/memorax-code-backend/dist/viewer/projection/history.js +0 -79
  61. package/lib/memorax-code-backend/dist/viewer/projection/observability.js +0 -8
  62. package/lib/memorax-code-backend/dist/viewer/projection/redaction.js +0 -52
  63. package/lib/memorax-code-backend/dist/viewer/projection/turn-reference.js +0 -12
  64. package/lib/memorax-code-backend/dist/viewer/projection/user.js +0 -168
  65. package/lib/memorax-code-backend/dist/viewer/projection/writeback-status.js +0 -97
  66. package/lib/memorax-code-backend/dist/viewer/store.js +0 -991
  67. package/lib/memorax-code-backend/dist/viewer/ui/icon.js +0 -1
  68. package/lib/memorax-code-backend/dist/viewer/ui/logo.js +0 -1
  69. package/lib/memorax-code-backend/dist/viewer/ui/user-html.js +0 -117
@@ -1,648 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { open, readdir, stat } from "node:fs/promises";
3
- import { homedir } from "node:os";
4
- import { basename, join, resolve as resolvePath } from "node:path";
5
- import { memoryViewerEventKey } from "../projection/event-identity.js";
6
- import { claudeTranscriptMemoryActivitiesFromJsonLines } from "../../clients/claude/transcript-turn.js";
7
- import { resolveMemoryProject, } from "../../memory/project.js";
8
- const projections = new Map();
9
- const EMPTY_TRANSCRIPT_HISTORY = Object.freeze([]);
10
- const MAX_TRANSCRIPT_FILES = 250;
11
- const MAX_TRANSCRIPT_FILE_BYTES = 8 * 1024 * 1024;
12
- const MAX_TRANSCRIPT_REFRESH_BYTES = 64 * 1024 * 1024;
13
- const MAX_TRANSCRIPT_EVENTS_PER_FILE = 2_000;
14
- const MAX_TRANSCRIPT_RETAINED_EVENTS = 4_000;
15
- const MAX_TRANSCRIPT_RETAINED_BYTES = 32 * 1024 * 1024;
16
- export function claudeLocalProjectsRoot(env = process.env) {
17
- const claudeHome = env.CLAUDE_CONFIG_DIR?.trim()
18
- || env.CLAUDE_HOME?.trim()
19
- || join(homedir(), ".claude");
20
- return join(resolvePath(claudeHome), "projects");
21
- }
22
- export async function readClaudeLocalTranscriptHistory(projectsRoot, resolveProject = resolveMemoryProject) {
23
- if (typeof projectsRoot !== "string" || !projectsRoot.trim()) {
24
- return EMPTY_TRANSCRIPT_HISTORY;
25
- }
26
- const normalizedRoot = resolvePath(projectsRoot.trim());
27
- let projection = projections.get(normalizedRoot);
28
- if (projection && projection.identity !== resolveProject)
29
- projection = undefined;
30
- if (!projection) {
31
- projection = { identity: resolveProject, files: new Map(), values: [] };
32
- projections.set(normalizedRoot, projection);
33
- }
34
- if (projection.refresh)
35
- return projection.refresh;
36
- const refresh = refreshProjection(projection, normalizedRoot, resolveProject);
37
- projection.refresh = refresh;
38
- try {
39
- return await refresh;
40
- }
41
- finally {
42
- if (projection.refresh === refresh)
43
- projection.refresh = undefined;
44
- }
45
- }
46
- export function clearClaudeLocalTranscriptProjections() {
47
- projections.clear();
48
- }
49
- async function refreshProjection(projection, projectsRoot, resolveProject) {
50
- const files = await discoverTranscriptFiles(projectsRoot);
51
- const activePaths = new Set(files.map((file) => file.path));
52
- let changed = false;
53
- let allowBackfill = false;
54
- for (const path of projection.files.keys()) {
55
- if (activePaths.has(path))
56
- continue;
57
- projection.files.delete(path);
58
- changed = true;
59
- allowBackfill = true;
60
- }
61
- let remainingBytes = MAX_TRANSCRIPT_REFRESH_BYTES;
62
- for (const file of files) {
63
- const cached = projection.files.get(file.path);
64
- const unchanged = cached
65
- && cached.observedSize === file.size
66
- && cached.modifiedAtMs === file.modifiedAtMs
67
- && cached.changedAtMs === file.changedAtMs
68
- && cached.inode === file.inode;
69
- if (unchanged && (!allowBackfill || cached.projectionTruncated !== true)) {
70
- continue;
71
- }
72
- if (remainingBytes <= 0)
73
- continue;
74
- const bytesToRead = Math.min(file.size, MAX_TRANSCRIPT_FILE_BYTES);
75
- if (bytesToRead > remainingBytes)
76
- continue;
77
- const rangeStart = Math.max(0, file.size - bytesToRead);
78
- let buffer;
79
- try {
80
- buffer = await readFileRange(file.path, rangeStart, file.size);
81
- }
82
- catch {
83
- if (cached) {
84
- projection.files.delete(file.path);
85
- changed = true;
86
- allowBackfill = true;
87
- }
88
- continue;
89
- }
90
- remainingBytes -= buffer.byteLength;
91
- const parsed = projectTranscriptChunk(buffer, resolveProject, initialParserState(file.sessionId), rangeStart > 0);
92
- const events = parsed.events.slice(-MAX_TRANSCRIPT_EVENTS_PER_FILE);
93
- projection.files.set(file.path, {
94
- observedSize: file.size,
95
- modifiedAtMs: file.modifiedAtMs,
96
- changedAtMs: file.changedAtMs,
97
- inode: file.inode,
98
- events,
99
- projectionTruncated: false,
100
- });
101
- if (!sameTranscriptEvents(events, cached?.events)) {
102
- changed = true;
103
- allowBackfill = true;
104
- }
105
- }
106
- if (changed) {
107
- projection.values = retainTranscriptProjectionEvents(projection);
108
- }
109
- return projection.values;
110
- }
111
- function retainTranscriptProjectionEvents(projection) {
112
- const candidates = [...projection.files.values()]
113
- .flatMap((file) => file.events)
114
- .sort(compareEvents);
115
- const retained = [];
116
- let retainedBytes = 0;
117
- for (let index = candidates.length - 1; index >= 0; index -= 1) {
118
- const event = candidates[index];
119
- if (!event || retained.length >= MAX_TRANSCRIPT_RETAINED_EVENTS)
120
- break;
121
- const eventBytes = transcriptEventRetainedBytes(event);
122
- if (retainedBytes + eventBytes > MAX_TRANSCRIPT_RETAINED_BYTES)
123
- break;
124
- retained.push(event);
125
- retainedBytes += eventBytes;
126
- }
127
- const retainedEvents = new Set(retained);
128
- for (const [path, cached] of projection.files) {
129
- const events = cached.events.filter((event) => retainedEvents.has(event));
130
- const projectionTruncated = events.length !== cached.events.length;
131
- if (projectionTruncated || cached.projectionTruncated === true) {
132
- projection.files.set(path, { ...cached, events, projectionTruncated });
133
- }
134
- }
135
- return retained.reverse();
136
- }
137
- function transcriptEventRetainedBytes(event) {
138
- return 256 + Buffer.byteLength([
139
- event.eventKey,
140
- event.timestamp,
141
- event.projectId ?? "",
142
- event.projectLabel ?? "",
143
- event.content,
144
- ].join("\u0000"));
145
- }
146
- function sameTranscriptEvents(current, cached) {
147
- return cached !== undefined
148
- && current.length === cached.length
149
- && current.every((event, index) => {
150
- const previous = cached[index];
151
- return previous !== undefined
152
- && event.eventKey === previous.eventKey
153
- && event.timestamp === previous.timestamp
154
- && event.projectId === previous.projectId
155
- && event.projectLabel === previous.projectLabel
156
- && event.content === previous.content
157
- && event.ok === previous.ok
158
- && event.turnOutcome === previous.turnOutcome;
159
- });
160
- }
161
- async function discoverTranscriptFiles(projectsRoot) {
162
- let projectDirectories;
163
- try {
164
- projectDirectories = await readdir(projectsRoot, { withFileTypes: true });
165
- }
166
- catch {
167
- return [];
168
- }
169
- const candidates = [];
170
- for (const directory of projectDirectories) {
171
- if (!directory.isDirectory())
172
- continue;
173
- const directoryPath = join(projectsRoot, directory.name);
174
- let entries;
175
- try {
176
- entries = await readdir(directoryPath, { withFileTypes: true });
177
- }
178
- catch {
179
- continue;
180
- }
181
- for (const entry of entries) {
182
- if (!entry.isFile() || !entry.name.endsWith(".jsonl"))
183
- continue;
184
- const sessionId = safeIdentifier(basename(entry.name, ".jsonl"));
185
- if (sessionId)
186
- candidates.push({ path: join(directoryPath, entry.name), sessionId });
187
- }
188
- }
189
- const files = [];
190
- for (const candidate of candidates) {
191
- try {
192
- const metadata = await stat(candidate.path);
193
- if (!metadata.isFile())
194
- continue;
195
- files.push({
196
- ...candidate,
197
- size: metadata.size,
198
- modifiedAtMs: metadata.mtimeMs,
199
- changedAtMs: metadata.ctimeMs,
200
- inode: metadata.ino,
201
- });
202
- }
203
- catch {
204
- // A live Claude session may rotate a file between directory listing and stat.
205
- }
206
- }
207
- return files
208
- .sort((left, right) => right.modifiedAtMs - left.modifiedAtMs || left.path.localeCompare(right.path))
209
- .slice(0, MAX_TRANSCRIPT_FILES);
210
- }
211
- function projectTranscriptChunk(buffer, resolveProject, initialState, skipLeadingPartial) {
212
- const events = [];
213
- const recordTurns = new Map();
214
- const recordParents = new Map();
215
- const terminalRecords = new Map();
216
- const ambiguousTurns = new Set();
217
- const partialAnswers = new Map();
218
- let state = initialState;
219
- let lineStart = 0;
220
- if (skipLeadingPartial) {
221
- const newline = buffer.indexOf(0x0a);
222
- if (newline < 0)
223
- return { consumedByteLength: buffer.byteLength, events, state };
224
- lineStart = newline + 1;
225
- }
226
- const completeChunkStart = lineStart;
227
- let consumedByteLength = lineStart;
228
- while (lineStart < buffer.byteLength) {
229
- const newline = buffer.indexOf(0x0a, lineStart);
230
- if (newline < 0)
231
- break;
232
- const lineEnd = newline > lineStart && buffer[newline - 1] === 0x0d ? newline - 1 : newline;
233
- const raw = parseRecord(buffer.subarray(lineStart, lineEnd).toString("utf8"));
234
- consumedByteLength = newline + 1;
235
- lineStart = newline + 1;
236
- if (!raw)
237
- continue;
238
- const type = stringValue(raw.type);
239
- const message = record(raw.message);
240
- const recordId = safeIdentifier(raw.uuid);
241
- const messageId = safeIdentifier(message?.id);
242
- const parentId = safeIdentifier(raw.parentUuid ?? raw.parent_uuid);
243
- if (parentId) {
244
- if (recordId)
245
- recordParents.set(recordId, parentId);
246
- if (messageId)
247
- recordParents.set(messageId, parentId);
248
- }
249
- const inheritedLineage = parentId ? recordTurns.get(parentId) : undefined;
250
- const declaredSessionId = stringValue(raw.sessionId);
251
- const sessionId = declaredSessionId
252
- ? safeIdentifier(declaredSessionId)
253
- : state.activeSessionId;
254
- const cwd = stringValue(raw.cwd);
255
- const project = cwd ? resolveProject(cwd) : state.activeProject;
256
- const compatibleInheritedLineage = inheritedLineage
257
- && transcriptLineageMatchesRecord(inheritedLineage, declaredSessionId, sessionId, cwd, project)
258
- ? inheritedLineage
259
- : undefined;
260
- if (isHiddenRecord(raw)) {
261
- if (raw.isSidechain !== true && compatibleInheritedLineage) {
262
- if (recordId)
263
- recordTurns.set(recordId, compatibleInheritedLineage);
264
- if (messageId)
265
- recordTurns.set(messageId, compatibleInheritedLineage);
266
- }
267
- continue;
268
- }
269
- if (type !== "user" && type !== "assistant")
270
- continue;
271
- const timestamp = safeTimestamp(raw.timestamp);
272
- if (!sessionId || !timestamp)
273
- continue;
274
- state = {
275
- ...state,
276
- activeSessionId: sessionId,
277
- ...(project ? { activeProject: project } : {}),
278
- };
279
- if (type === "user") {
280
- const interruptedMessageId = safeIdentifier(raw.interruptedMessageId ?? raw.interrupted_message_id);
281
- if (interruptedMessageId) {
282
- const interruptedCandidate = recordTurns.get(interruptedMessageId) ?? inheritedLineage;
283
- const interruptedLineage = interruptedCandidate
284
- && transcriptLineageMatchesRecord(interruptedCandidate, declaredSessionId, sessionId, cwd, project)
285
- ? interruptedCandidate
286
- : undefined;
287
- if (!interruptedLineage)
288
- continue;
289
- if (recordId)
290
- recordTurns.set(recordId, interruptedLineage);
291
- if (messageId)
292
- recordTurns.set(messageId, interruptedLineage);
293
- const interruptedTurnId = interruptedLineage.turnId;
294
- if (ambiguousTurns.has(interruptedTurnId))
295
- continue;
296
- const terminalRecord = terminalRecords.get(interruptedTurnId);
297
- if (terminalRecord) {
298
- ambiguousTurns.add(interruptedTurnId);
299
- removeTurnEndEvent(events, interruptedLineage.sessionId, interruptedTurnId);
300
- continue;
301
- }
302
- terminalRecords.set(interruptedTurnId, recordId || interruptedMessageId);
303
- events.push(turnEndEvent({
304
- sessionId: interruptedLineage.sessionId,
305
- turnId: interruptedTurnId,
306
- timestamp,
307
- answer: partialAnswers.get(interruptedTurnId) ?? "",
308
- interrupted: true,
309
- error: "Claude Code turn was interrupted.",
310
- project: interruptedLineage.project,
311
- }));
312
- if (state.activeTurnId === interruptedTurnId
313
- && state.activeSessionId === interruptedLineage.sessionId) {
314
- state = { ...state, activeTurnId: "", activeTurnLineage: undefined };
315
- }
316
- continue;
317
- }
318
- if (!isInteractiveUserRecord(raw, message) || hasOnlyToolResults(message?.content)) {
319
- if (recordId && compatibleInheritedLineage)
320
- recordTurns.set(recordId, compatibleInheritedLineage);
321
- if (messageId && compatibleInheritedLineage)
322
- recordTurns.set(messageId, compatibleInheritedLineage);
323
- continue;
324
- }
325
- const prompt = visibleText(message?.content);
326
- const turnId = safeIdentifier(raw.promptId ?? raw.prompt_id)
327
- || safeIdentifier(raw.uuid);
328
- if (!prompt || !turnId)
329
- continue;
330
- const lineage = {
331
- turnId,
332
- sessionId,
333
- ...(project ? { project } : {}),
334
- };
335
- state = { ...state, activeTurnId: turnId, activeTurnLineage: lineage };
336
- if (recordId)
337
- recordTurns.set(recordId, lineage);
338
- if (messageId)
339
- recordTurns.set(messageId, lineage);
340
- events.push(turnStartEvent({ sessionId, turnId, timestamp, prompt, project }));
341
- continue;
342
- }
343
- const assistantLineage = parentId
344
- ? compatibleInheritedLineage
345
- : state.activeTurnLineage
346
- && transcriptLineageMatchesRecord(state.activeTurnLineage, declaredSessionId, sessionId, cwd, project)
347
- ? state.activeTurnLineage
348
- : undefined;
349
- if (!assistantLineage)
350
- continue;
351
- const assistantTurnId = assistantLineage.turnId;
352
- if (ambiguousTurns.has(assistantTurnId))
353
- continue;
354
- if (recordId)
355
- recordTurns.set(recordId, assistantLineage);
356
- if (messageId)
357
- recordTurns.set(messageId, assistantLineage);
358
- const answer = visibleText(message?.content);
359
- if (answer)
360
- partialAnswers.set(assistantTurnId, answer);
361
- if (!isTerminalAssistantRecord(raw, message))
362
- continue;
363
- const terminalRecord = recordId || `${parentId}\u0000${timestamp}`;
364
- const previousTerminalRecord = terminalRecords.get(assistantTurnId);
365
- if (previousTerminalRecord && previousTerminalRecord !== terminalRecord) {
366
- if (!recordId || !recordDescendsFrom(recordId, previousTerminalRecord, recordParents)) {
367
- ambiguousTurns.add(assistantTurnId);
368
- removeTurnEndEvent(events, assistantLineage.sessionId, assistantTurnId);
369
- continue;
370
- }
371
- removeTurnEndEvent(events, assistantLineage.sessionId, assistantTurnId);
372
- }
373
- if (previousTerminalRecord === terminalRecord)
374
- continue;
375
- terminalRecords.set(assistantTurnId, terminalRecord);
376
- const reachedTokenLimit = stringValue(message?.stop_reason) === "max_tokens";
377
- const interrupted = raw.isApiErrorMessage === true || Boolean(raw.error) || reachedTokenLimit;
378
- events.push(turnEndEvent({
379
- sessionId: assistantLineage.sessionId,
380
- turnId: assistantTurnId,
381
- timestamp,
382
- answer,
383
- interrupted,
384
- ...(interrupted ? {
385
- error: reachedTokenLimit
386
- ? "Claude Code response reached its token limit."
387
- : "Claude Code recorded an API error.",
388
- } : {}),
389
- project: assistantLineage.project,
390
- }));
391
- }
392
- const activityEvents = projectTranscriptMemoryActivities(buffer.subarray(completeChunkStart, consumedByteLength).toString("utf8"), initialState.activeSessionId, events, terminalRecords);
393
- return {
394
- consumedByteLength,
395
- events: mergeTranscriptEvents([], [...events, ...activityEvents]),
396
- state,
397
- };
398
- }
399
- function projectTranscriptMemoryActivities(transcript, sessionId, lifecycleEvents, acceptedTerminalRecords) {
400
- const finalizedTurnScopes = new Set(lifecycleEvents
401
- .filter((event) => event.type === "turn_end" && event.sessionId === sessionId && event.turnId)
402
- .map((event) => JSON.stringify([event.sessionId, event.turnId, event.projectId ?? null])));
403
- const starts = new Map(lifecycleEvents
404
- .filter((event) => event.type === "turn_start" && event.sessionId === sessionId && event.turnId)
405
- .map((event) => [event.turnId, event]));
406
- return claudeTranscriptMemoryActivitiesFromJsonLines(transcript, {
407
- sessionId,
408
- includeAuthority: true,
409
- })
410
- .flatMap((activity) => {
411
- const turnId = safeIdentifier(activity.promptId);
412
- const toolUseId = safeIdentifier(activity.toolUseId);
413
- const terminalRecordId = safeIdentifier(activity.terminalRecordId);
414
- const start = turnId ? starts.get(turnId) : undefined;
415
- if (!start
416
- || !turnId
417
- || !toolUseId
418
- || !terminalRecordId
419
- || acceptedTerminalRecords.get(turnId) !== terminalRecordId
420
- || activity.ok === undefined) {
421
- return [];
422
- }
423
- const turnScope = JSON.stringify([sessionId, turnId, start.projectId ?? null]);
424
- if (!finalizedTurnScopes.has(turnScope))
425
- return [];
426
- const timestamp = safeTimestamp(activity.timestamp)
427
- || syntheticActivityTimestamp(start.timestamp, activity.index, activity.occurrence);
428
- if (!timestamp)
429
- return [];
430
- const identity = createHash("sha256")
431
- .update(JSON.stringify([
432
- "claude-native-memory-activity-v1",
433
- sessionId,
434
- turnId,
435
- toolUseId,
436
- activity.occurrence,
437
- activity.type,
438
- ]))
439
- .digest("hex")
440
- .slice(0, 32);
441
- const id = `claude-local:${sessionId}:${activity.type}:${identity}`;
442
- const itemCount = activity.type === "memory_cli_search"
443
- && activity.ok
444
- && Number.isSafeInteger(activity.itemCount)
445
- && (activity.itemCount ?? -1) >= 0
446
- && (activity.itemCount ?? 101) <= 100
447
- ? activity.itemCount
448
- : undefined;
449
- return [{
450
- id,
451
- eventKey: memoryViewerEventKey({ id, client: "claude", sessionId }),
452
- client: "claude",
453
- type: activity.type,
454
- timestamp,
455
- source: "memory_cli",
456
- operation: activity.type === "memory_cli_add" ? "writeback" : "query",
457
- ok: activity.ok,
458
- ...(start.projectId ? { projectId: start.projectId } : {}),
459
- ...(start.projectLabel ? { projectLabel: start.projectLabel } : {}),
460
- ...(start.project ? { project: start.project } : {}),
461
- sessionId,
462
- turnId,
463
- content: activity.type === "memory_cli_add"
464
- ? "Claude Code invoked memory add."
465
- : "Claude Code invoked memory search.",
466
- ...(itemCount === undefined ? {} : { itemCount }),
467
- ...(activity.ok === false ? { error: "Claude Code recorded a failed memory command." } : {}),
468
- }];
469
- });
470
- }
471
- function syntheticActivityTimestamp(turnTimestamp, index, occurrence) {
472
- const parsed = Date.parse(turnTimestamp);
473
- return Number.isFinite(parsed)
474
- ? new Date(parsed + index * 100 + occurrence).toISOString()
475
- : "";
476
- }
477
- function transcriptLineageMatchesRecord(lineage, declaredSessionId, sessionId, cwd, project) {
478
- if (declaredSessionId && sessionId !== lineage.sessionId)
479
- return false;
480
- if (!cwd)
481
- return true;
482
- return project?.projectId === lineage.project?.projectId;
483
- }
484
- function turnStartEvent(input) {
485
- const id = `claude-local:${input.sessionId}:turn_start:${input.turnId}`;
486
- return {
487
- id,
488
- eventKey: memoryViewerEventKey({ id, client: "claude", sessionId: input.sessionId }),
489
- client: "claude",
490
- type: "turn_start",
491
- timestamp: input.timestamp,
492
- source: "unknown",
493
- operation: "query",
494
- ok: true,
495
- ...projectFields(input.project),
496
- sessionId: input.sessionId,
497
- turnId: input.turnId,
498
- content: input.prompt,
499
- prompt: input.prompt,
500
- };
501
- }
502
- function turnEndEvent(input) {
503
- const id = `claude-local:${input.sessionId}:turn_end:${input.turnId}`;
504
- return {
505
- id,
506
- eventKey: memoryViewerEventKey({ id, client: "claude", sessionId: input.sessionId }),
507
- client: "claude",
508
- type: "turn_end",
509
- timestamp: input.timestamp,
510
- source: "unknown",
511
- operation: "reply",
512
- ok: !input.interrupted,
513
- ...projectFields(input.project),
514
- sessionId: input.sessionId,
515
- turnId: input.turnId,
516
- content: input.answer,
517
- ...(input.answer ? { answer: input.answer } : {}),
518
- turnOutcome: input.interrupted ? "interrupted" : "completed",
519
- ...(input.error ? { error: input.error } : {}),
520
- };
521
- }
522
- function removeTurnEndEvent(events, sessionId, turnId) {
523
- const index = events.findIndex((event) => (event.type === "turn_end"
524
- && event.sessionId === sessionId
525
- && event.turnId === turnId));
526
- if (index >= 0)
527
- events.splice(index, 1);
528
- }
529
- function recordDescendsFrom(recordId, ancestorId, parents) {
530
- const visited = new Set();
531
- let current = recordId;
532
- while (current && !visited.has(current)) {
533
- if (current === ancestorId)
534
- return true;
535
- visited.add(current);
536
- current = parents.get(current) ?? "";
537
- }
538
- return false;
539
- }
540
- function initialParserState(sessionId) {
541
- return { activeTurnId: "", activeSessionId: sessionId };
542
- }
543
- function mergeTranscriptEvents(current, appended) {
544
- if (appended.length === 0)
545
- return current;
546
- const events = new Map(current.map((event) => [event.eventKey, event]));
547
- for (const event of appended)
548
- events.set(event.eventKey, event);
549
- return [...events.values()].sort(compareEvents).slice(-MAX_TRANSCRIPT_EVENTS_PER_FILE);
550
- }
551
- async function readFileRange(path, start, end) {
552
- const handle = await open(path, "r");
553
- try {
554
- const buffer = Buffer.allocUnsafe(Math.max(0, end - start));
555
- let offset = 0;
556
- while (offset < buffer.byteLength) {
557
- const { bytesRead } = await handle.read(buffer, offset, buffer.byteLength - offset, start + offset);
558
- if (bytesRead === 0)
559
- break;
560
- offset += bytesRead;
561
- }
562
- return buffer.subarray(0, offset);
563
- }
564
- finally {
565
- await handle.close();
566
- }
567
- }
568
- function isHiddenRecord(raw) {
569
- return raw.isMeta === true
570
- || raw.isSidechain === true
571
- || raw.isCompactSummary === true
572
- || raw.isVisibleInTranscriptOnly === true;
573
- }
574
- function isInteractiveUserRecord(raw, message) {
575
- if (raw.userType !== "external" || message?.role !== "user")
576
- return false;
577
- const origin = record(raw.origin);
578
- return stringValue(origin?.kind).toLowerCase() !== "task-notification"
579
- && stringValue(raw.promptSource ?? raw.prompt_source).toLowerCase() !== "system"
580
- && !stringValue(raw.interruptedMessageId ?? raw.interrupted_message_id);
581
- }
582
- function isTerminalAssistantRecord(raw, message) {
583
- if (message?.role !== "assistant")
584
- return false;
585
- if (raw.isApiErrorMessage === true || raw.error)
586
- return true;
587
- const stopReason = stringValue(message.stop_reason);
588
- return Boolean(stopReason && stopReason !== "tool_use");
589
- }
590
- function hasOnlyToolResults(value) {
591
- return Array.isArray(value)
592
- && value.length > 0
593
- && value.every((part) => stringValue(record(part)?.type) === "tool_result");
594
- }
595
- function visibleText(value) {
596
- if (typeof value === "string")
597
- return value.trim();
598
- if (!Array.isArray(value))
599
- return "";
600
- return value
601
- .map((part) => {
602
- const block = record(part);
603
- return stringValue(block?.type) === "text" ? stringValue(block?.text) : "";
604
- })
605
- .filter(Boolean)
606
- .join("\n\n");
607
- }
608
- function projectFields(identity) {
609
- if (!identity)
610
- return {};
611
- return {
612
- projectId: identity.projectId,
613
- projectLabel: identity.projectLabel,
614
- project: identity.projectLabel,
615
- };
616
- }
617
- function safeIdentifier(value) {
618
- const candidate = stringValue(value);
619
- if (!candidate || candidate.length > 512 || /[\u0000-\u001f\u007f\\/]/.test(candidate))
620
- return "";
621
- return candidate;
622
- }
623
- function safeTimestamp(value) {
624
- const candidate = stringValue(value);
625
- const parsed = Date.parse(candidate);
626
- return Number.isFinite(parsed) ? new Date(parsed).toISOString() : "";
627
- }
628
- function parseRecord(line) {
629
- if (!line.trim())
630
- return undefined;
631
- try {
632
- return record(JSON.parse(line));
633
- }
634
- catch {
635
- return undefined;
636
- }
637
- }
638
- function record(value) {
639
- return value && typeof value === "object" && !Array.isArray(value)
640
- ? value
641
- : undefined;
642
- }
643
- function stringValue(value) {
644
- return typeof value === "string" ? value.trim() : "";
645
- }
646
- function compareEvents(left, right) {
647
- return Date.parse(left.timestamp) - Date.parse(right.timestamp) || left.eventKey.localeCompare(right.eventKey);
648
- }