@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.
- package/README.md +36 -5
- package/bin/memorax-code-setup.mjs +0 -3
- package/docs/configuration.md +1 -3
- package/docs/troubleshooting.md +9 -8
- package/lib/memorax-code-backend/dist/app/backend-server.js +2 -29
- package/lib/memorax-code-backend/dist/app/memory-observability.js +1 -4
- package/lib/memorax-code-backend/dist/app/state.js +1 -12
- package/lib/memorax-code-backend/dist/clients/claude/transcript-turn.js +0 -182
- package/lib/memorax-code-backend/dist/clients/opencode/message-turn.js +68 -2
- package/lib/memorax-code-backend/dist/lifecycle/backend/service.js +0 -5
- package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +1 -14
- package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +8 -5
- package/lib/memorax-code-backend/dist/memory/project.js +4 -26
- package/lib/memorax-code-backend/dist/memory/quota-notice.js +79 -19
- package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +1 -4
- package/lib/memorax-code-backend/dist/provider/memorax/http.js +0 -28
- package/lib/memorax-code-backend/dist/transport/http/request.js +1 -39
- package/lib/memorax-code-backend/package.json +2 -3
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-dsh-adapter/package.json +1 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-opencode-adapter/package.json +1 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/SKILL.md +0 -6
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +1 -1
- package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +1 -1
- package/lib/memorax-code-opencode-adapter/src/plugin.mjs +121 -16
- package/lib/memorax-code-opencode-adapter/src/repo-memory-server-runner.mjs +2 -0
- package/package.json +1 -1
- package/lib/memorax-code-backend/dist/clients/codex/effective-prompt.js +0 -54
- package/lib/memorax-code-backend/dist/memory/writeback-reconciler.js +0 -244
- package/lib/memorax-code-backend/dist/memory/writeback-task-projection.js +0 -377
- package/lib/memorax-code-backend/dist/repository/readiness.js +0 -188
- package/lib/memorax-code-backend/dist/shared/incremental-jsonl-projection.js +0 -271
- package/lib/memorax-code-backend/dist/viewer/history/claude-transcript.js +0 -648
- package/lib/memorax-code-backend/dist/viewer/history/session-title.js +0 -93
- package/lib/memorax-code-backend/dist/viewer/http/public-routes.js +0 -146
- package/lib/memorax-code-backend/dist/viewer/model.js +0 -1
- package/lib/memorax-code-backend/dist/viewer/projection/activity.js +0 -113
- package/lib/memorax-code-backend/dist/viewer/projection/cache.js +0 -69
- package/lib/memorax-code-backend/dist/viewer/projection/event-identity.js +0 -3
- package/lib/memorax-code-backend/dist/viewer/projection/history.js +0 -79
- package/lib/memorax-code-backend/dist/viewer/projection/observability.js +0 -8
- package/lib/memorax-code-backend/dist/viewer/projection/redaction.js +0 -52
- package/lib/memorax-code-backend/dist/viewer/projection/turn-reference.js +0 -12
- package/lib/memorax-code-backend/dist/viewer/projection/user.js +0 -168
- package/lib/memorax-code-backend/dist/viewer/projection/writeback-status.js +0 -97
- package/lib/memorax-code-backend/dist/viewer/store.js +0 -991
- package/lib/memorax-code-backend/dist/viewer/ui/icon.js +0 -1
- package/lib/memorax-code-backend/dist/viewer/ui/logo.js +0 -1
- package/lib/memorax-code-backend/dist/viewer/ui/user-html.js +0 -117
|
@@ -1,991 +0,0 @@
|
|
|
1
|
-
import { randomUUID } from "node:crypto";
|
|
2
|
-
import { resolve } from "node:path";
|
|
3
|
-
import { clearClaudeLocalTranscriptProjections, readClaudeLocalTranscriptHistory, } from "./history/claude-transcript.js";
|
|
4
|
-
import { stripCodexPreambleSegments } from "../clients/codex/effective-prompt.js";
|
|
5
|
-
import { clearIncrementalJsonlProjections, readIncrementalJsonlProjectionSnapshot, } from "../shared/incremental-jsonl-projection.js";
|
|
6
|
-
import { clearMemoryViewerDataProjectionCache, clearMemoryViewerDataProjections, memoryViewerDataProjectionCacheSize, prepareMemoryViewerDataProjectionHistory, readMemoryViewerDataProjection, shareMemoryViewerDataProjectionValue, shouldCacheMemoryViewerDataProjection, writeMemoryViewerDataProjection, } from "./projection/cache.js";
|
|
7
|
-
import { memoryViewerActivityProjection, memoryViewerActivitySummary, } from "./projection/activity.js";
|
|
8
|
-
import { memoryViewerEventKey } from "./projection/event-identity.js";
|
|
9
|
-
import { memoryViewerEventTurnIds } from "./projection/turn-reference.js";
|
|
10
|
-
import { foldMemoryViewerSupersededTurnStarts, foldMemoryViewerTurnMaterializations, } from "./projection/history.js";
|
|
11
|
-
import { memoryProjectFromUnknown, resolveMemoryProject, UNCLASSIFIED_PROJECT_ID, } from "../memory/project.js";
|
|
12
|
-
import { sanitizeMemoryViewerDetails } from "./projection/redaction.js";
|
|
13
|
-
import { earliestMemoryViewerSessionTitleCandidates, } from "./history/session-title.js";
|
|
14
|
-
import { projectMemoryViewerUserData } from "./projection/user.js";
|
|
15
|
-
import { completeMemoryViewerWriteback, foldMemoryViewerWritebackStatuses, memoryText, memoryViewerWritebackOutcome, nonNegativeInteger, pendingMemoryViewerWritebacks, stringArray, writebackOutcomeForStatus, } from "./projection/writeback-status.js";
|
|
16
|
-
import { clientTracePaths, tracePaths } from "../trace/config.js";
|
|
17
|
-
export { completeMemoryViewerWriteback, memoryViewerEventTurnIds, pendingMemoryViewerWritebacks, };
|
|
18
|
-
const CAPACITY = 200;
|
|
19
|
-
const TURN_CAPACITY = 50;
|
|
20
|
-
export const MEMORY_VIEWER_UNSCOPED_SESSION_FILTER = "__unscoped__";
|
|
21
|
-
const events = [];
|
|
22
|
-
let liveEventsVersion = 0;
|
|
23
|
-
const combinedTraceHistories = new Map();
|
|
24
|
-
const EMPTY_MEMORY_VIEWER_HISTORY = Object.freeze([]);
|
|
25
|
-
const claudeTranscriptSources = new Map();
|
|
26
|
-
export function recordMemoryViewerEvent(input) {
|
|
27
|
-
const client = memoryViewerClient(input);
|
|
28
|
-
if (!client)
|
|
29
|
-
return undefined;
|
|
30
|
-
const response = record(input.response);
|
|
31
|
-
const request = record(input.request);
|
|
32
|
-
const items = arrayValue(response?.items);
|
|
33
|
-
const query = searchQuery(request);
|
|
34
|
-
const results = searchResults(items);
|
|
35
|
-
const traceSessionId = sessionId(input.traceContext?.sessionId);
|
|
36
|
-
const traceTurnId = safeMemoryViewerTurnId(input.traceContext?.turnId);
|
|
37
|
-
const turnReferences = client === "claude"
|
|
38
|
-
? memoryViewerTurnReferences(input.relatedTurns, input.traceContext)
|
|
39
|
-
: [];
|
|
40
|
-
const projectIdentity = memoryProjectFromUnknown(input.traceContext?.memoryProject)
|
|
41
|
-
?? resolveMemoryProject(input.traceContext?.cwd);
|
|
42
|
-
const writeback = writebackState(input.operation, response);
|
|
43
|
-
const conversation = writebackConversation(input.operation, request);
|
|
44
|
-
const id = memoryViewerEventId(client, input.eventId);
|
|
45
|
-
const event = {
|
|
46
|
-
id,
|
|
47
|
-
eventKey: memoryViewerEventKey({
|
|
48
|
-
id,
|
|
49
|
-
client,
|
|
50
|
-
...(traceSessionId ? { sessionId: traceSessionId } : {}),
|
|
51
|
-
}),
|
|
52
|
-
client,
|
|
53
|
-
type: memoryEventType(input.source, input.operation),
|
|
54
|
-
timestamp: new Date().toISOString(),
|
|
55
|
-
source: input.source,
|
|
56
|
-
operation: input.operation,
|
|
57
|
-
ok: input.ok,
|
|
58
|
-
...viewerProjectFields(projectIdentity),
|
|
59
|
-
...(traceSessionId ? { sessionId: traceSessionId } : {}),
|
|
60
|
-
...(traceTurnId ? { turnId: traceTurnId } : {}),
|
|
61
|
-
...(turnReferences.length > 0 ? { turnReferences } : {}),
|
|
62
|
-
content: eventContent(input.operation, request, response, items, input.error),
|
|
63
|
-
...(conversation.prompt ? { prompt: conversation.prompt } : {}),
|
|
64
|
-
...(conversation.answer ? { answer: conversation.answer } : {}),
|
|
65
|
-
...(query ? { query } : {}),
|
|
66
|
-
...(results.length > 0 ? { results } : {}),
|
|
67
|
-
...(items.length > 0 ? { itemCount: items.length } : {}),
|
|
68
|
-
...(stringValue(response?.receiptId) ? { receiptId: stringValue(response?.receiptId) } : {}),
|
|
69
|
-
...(writeback.taskId ? { taskId: writeback.taskId } : {}),
|
|
70
|
-
...(writeback.status ? { writebackStatus: writeback.status } : {}),
|
|
71
|
-
...(writeback.status ? { writebackOutcome: writebackOutcomeForStatus(writeback.status) } : {}),
|
|
72
|
-
...(input.error ? { error: input.error.slice(0, 2000) } : {}),
|
|
73
|
-
details: sanitizeMemoryViewerDetails({ request: input.request, response: input.response }),
|
|
74
|
-
};
|
|
75
|
-
events.push(event);
|
|
76
|
-
while (events.length > CAPACITY)
|
|
77
|
-
events.shift();
|
|
78
|
-
liveEventsVersion += 1;
|
|
79
|
-
clearMemoryViewerDataProjections();
|
|
80
|
-
return event;
|
|
81
|
-
}
|
|
82
|
-
export function listMemoryViewerEvents() {
|
|
83
|
-
return events.map((event) => structuredClone(event));
|
|
84
|
-
}
|
|
85
|
-
export async function listMemoryViewerEventsWithHistory(memoraxCodeHome) {
|
|
86
|
-
return (await listMemoryViewerDataWithHistory(memoraxCodeHome)).events;
|
|
87
|
-
}
|
|
88
|
-
export async function listMemoryViewerDataWithHistory(memoraxCodeHome, filter = {}, dependencies = {}) {
|
|
89
|
-
const claudeProjectsRoot = claudeTranscriptRoot(dependencies.claudeProjectsRoot);
|
|
90
|
-
observeClaudeTranscriptSource(memoraxCodeHome, claudeProjectsRoot);
|
|
91
|
-
const historicalSnapshot = await readTraceHistory(memoraxCodeHome, dependencies.resolveProject ?? resolveMemoryProject, filter.client, claudeProjectsRoot);
|
|
92
|
-
const historical = historicalSnapshot.values;
|
|
93
|
-
const projectionKey = memoryViewerProjectionKey(memoraxCodeHome, filter.client);
|
|
94
|
-
prepareMemoryViewerDataProjectionHistory(projectionKey, historical);
|
|
95
|
-
const filterKey = memoryViewerHistoryFilterKey(filter);
|
|
96
|
-
const cached = readMemoryViewerDataProjection(projectionKey, filterKey, historical, liveEventsVersion);
|
|
97
|
-
if (cached)
|
|
98
|
-
return structuredClone(cached);
|
|
99
|
-
const merged = new Map();
|
|
100
|
-
const liveEvents = filter.client
|
|
101
|
-
? events.filter((event) => event.client === filter.client)
|
|
102
|
-
: events;
|
|
103
|
-
for (const event of [...historical, ...liveEvents])
|
|
104
|
-
merged.set(event.eventKey, event);
|
|
105
|
-
const traceEvents = [...merged.values()];
|
|
106
|
-
const claudeTranscriptAdmission = admitClaudeTranscriptEvents(traceEvents, historicalSnapshot.claudeLocal);
|
|
107
|
-
const associatedEvents = associateClaudeEventsWithTranscriptTurns(coalesceClaudeTranscriptTurns(traceEvents, claudeTranscriptAdmission.events), historicalSnapshot.claudeLocal, claudeTranscriptAdmission.turnScopes);
|
|
108
|
-
const coalescedEvents = coalesceClaudeTranscriptActivities(associatedEvents);
|
|
109
|
-
const allEvents = foldMemoryViewerSupersededTurnStarts(foldMemoryViewerTurnMaterializations(foldMemoryViewerWritebackStatuses(coalescedEvents.sort(compareMemoryViewerEvents))));
|
|
110
|
-
const selectionCatalogEvents = allEvents.filter(memoryViewerEventVisible);
|
|
111
|
-
const catalogEvents = selectionCatalogEvents;
|
|
112
|
-
const catalogWindow = catalogEvents.slice(-CAPACITY);
|
|
113
|
-
const projects = projectCatalog(catalogWindow);
|
|
114
|
-
const projectSessions = projectSessionCatalog(catalogWindow);
|
|
115
|
-
const hasUnclassified = catalogWindow.some((event) => !event.projectId);
|
|
116
|
-
const selectableEvents = filter.includePendingWritebacks
|
|
117
|
-
? allEvents.filter((event) => memoryViewerEventVisible(event)
|
|
118
|
-
|| (event.operation === "writeback" && event.writebackOutcome === "pending"))
|
|
119
|
-
: catalogEvents;
|
|
120
|
-
const activitySelectionProjects = filter.includePendingWritebacks
|
|
121
|
-
? projectCatalog(selectableEvents)
|
|
122
|
-
: projectCatalog(selectionCatalogEvents);
|
|
123
|
-
const activitySelectionHasUnclassified = filter.includePendingWritebacks
|
|
124
|
-
? selectableEvents.some((event) => !event.projectId)
|
|
125
|
-
: selectionCatalogEvents.some((event) => !event.projectId);
|
|
126
|
-
const activityProjects = activitySelectionProjects;
|
|
127
|
-
const activityProjection = memoryViewerActivityProjection(selectableEvents);
|
|
128
|
-
const activityProjectSessions = activityProjection.projectSessions;
|
|
129
|
-
const selection = resolveProjectSelection(activitySelectionProjects, activitySelectionHasUnclassified, filter.projectId, filter.rejectProjectFilter === true);
|
|
130
|
-
const activeSessionSince = filter.activeSessionSince;
|
|
131
|
-
const scopedEvents = (selection.blockAll ? [] : selectableEvents)
|
|
132
|
-
.filter((event) => !filter.eventKey || event.eventKey === filter.eventKey)
|
|
133
|
-
.filter((event) => !filter.unscopedSession && !filter.sessionId
|
|
134
|
-
? true
|
|
135
|
-
: filter.unscopedSession
|
|
136
|
-
? !event.sessionId
|
|
137
|
-
: event.sessionId === filter.sessionId)
|
|
138
|
-
.filter((event) => !selection.projectId
|
|
139
|
-
? true
|
|
140
|
-
: selection.projectId === UNCLASSIFIED_PROJECT_ID
|
|
141
|
-
? !event.projectId
|
|
142
|
-
: event.projectId === selection.projectId);
|
|
143
|
-
const explicitSession = Boolean(filter.sessionId || filter.unscopedSession);
|
|
144
|
-
const explicitSessionHasRecentActivity = activeSessionSince !== undefined
|
|
145
|
-
&& explicitSession
|
|
146
|
-
&& scopedEvents.some((event) => ((activityProjection.eventActivityOrder.get(event.eventKey) ?? Number.NEGATIVE_INFINITY) >= activeSessionSince));
|
|
147
|
-
// Active scopes contain complete Turns started inside the window plus recent standalone events.
|
|
148
|
-
// Explicit stale sessions retain historical access.
|
|
149
|
-
const filteredEvents = activeSessionSince === undefined || (explicitSession && !explicitSessionHasRecentActivity)
|
|
150
|
-
? scopedEvents
|
|
151
|
-
: scopedEvents.filter((event) => ((activityProjection.eventActivityOrder.get(event.eventKey) ?? Number.NEGATIVE_INFINITY) >= activeSessionSince));
|
|
152
|
-
const eventWindow = filteredEvents.slice(-CAPACITY);
|
|
153
|
-
const data = {
|
|
154
|
-
events: eventWindow.map((event) => structuredClone(event)),
|
|
155
|
-
turnEvents: completeTurnEventWindow(filteredEvents, TURN_CAPACITY, eventWindow)
|
|
156
|
-
.map((event) => structuredClone(event)),
|
|
157
|
-
activitySummary: memoryViewerActivitySummary(filteredEvents),
|
|
158
|
-
catalogSourceEvents: selectableEvents.slice(-CAPACITY).map((event) => structuredClone(event)),
|
|
159
|
-
sessionTitleCandidates: shareMemoryViewerDataProjectionValue(projectionKey, `session-titles:${filter.includePendingWritebacks === true}`, historical, liveEventsVersion, () => earliestMemoryViewerSessionTitleCandidates(selectableEvents)),
|
|
160
|
-
projects,
|
|
161
|
-
projectSessions,
|
|
162
|
-
hasUnclassified,
|
|
163
|
-
activityProjects,
|
|
164
|
-
activityProjectSessions,
|
|
165
|
-
...(filter.includeUserProjection
|
|
166
|
-
? { userProjection: projectMemoryViewerUserData(filteredEvents) }
|
|
167
|
-
: {}),
|
|
168
|
-
...(selection.projectId ? { selectedProjectId: selection.projectId } : {}),
|
|
169
|
-
...(selection.status ? { projectFilterStatus: selection.status } : {}),
|
|
170
|
-
};
|
|
171
|
-
if (shouldCacheMemoryViewerDataProjection(filter, data)) {
|
|
172
|
-
writeMemoryViewerDataProjection(projectionKey, filterKey, historical, liveEventsVersion, data);
|
|
173
|
-
}
|
|
174
|
-
return structuredClone(data);
|
|
175
|
-
}
|
|
176
|
-
function completeTurnEventWindow(allEvents, capacity, requiredEvents = []) {
|
|
177
|
-
const turns = new Map();
|
|
178
|
-
for (const event of allEvents) {
|
|
179
|
-
if (!event.sessionId)
|
|
180
|
-
continue;
|
|
181
|
-
const timestamp = Date.parse(event.timestamp);
|
|
182
|
-
const order = Number.isFinite(timestamp) ? timestamp : 0;
|
|
183
|
-
for (const turnId of memoryViewerEventTurnIds(event)) {
|
|
184
|
-
const key = `${event.client}\u0000${event.sessionId}\u0000${turnId}`;
|
|
185
|
-
const current = turns.get(key);
|
|
186
|
-
if (!current) {
|
|
187
|
-
turns.set(key, {
|
|
188
|
-
startedAt: event.type === "turn_start" ? order : Number.NEGATIVE_INFINITY,
|
|
189
|
-
firstSeenAt: order,
|
|
190
|
-
});
|
|
191
|
-
continue;
|
|
192
|
-
}
|
|
193
|
-
current.firstSeenAt = Math.min(current.firstSeenAt, order);
|
|
194
|
-
if (event.type === "turn_start")
|
|
195
|
-
current.startedAt = order;
|
|
196
|
-
}
|
|
197
|
-
}
|
|
198
|
-
const selected = new Set([...turns.entries()]
|
|
199
|
-
.sort((left, right) => {
|
|
200
|
-
const leftOrder = Number.isFinite(left[1].startedAt) ? left[1].startedAt : left[1].firstSeenAt;
|
|
201
|
-
const rightOrder = Number.isFinite(right[1].startedAt) ? right[1].startedAt : right[1].firstSeenAt;
|
|
202
|
-
return leftOrder - rightOrder || left[0].localeCompare(right[0]);
|
|
203
|
-
})
|
|
204
|
-
.slice(-capacity)
|
|
205
|
-
.map(([key]) => key));
|
|
206
|
-
for (const event of requiredEvents) {
|
|
207
|
-
if (!event.sessionId)
|
|
208
|
-
continue;
|
|
209
|
-
for (const turnId of memoryViewerEventTurnIds(event)) {
|
|
210
|
-
selected.add(`${event.client}\u0000${event.sessionId}\u0000${turnId}`);
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return allEvents.filter((event) => (Boolean(event.sessionId)
|
|
214
|
-
&& memoryViewerEventTurnIds(event).some((turnId) => (selected.has(`${event.client}\u0000${event.sessionId}\u0000${turnId}`)))));
|
|
215
|
-
}
|
|
216
|
-
export function clearMemoryViewerEvents() {
|
|
217
|
-
events.length = 0;
|
|
218
|
-
liveEventsVersion += 1;
|
|
219
|
-
combinedTraceHistories.clear();
|
|
220
|
-
claudeTranscriptSources.clear();
|
|
221
|
-
clearClaudeLocalTranscriptProjections();
|
|
222
|
-
clearIncrementalJsonlProjections();
|
|
223
|
-
clearMemoryViewerDataProjectionCache();
|
|
224
|
-
}
|
|
225
|
-
function claudeTranscriptRoot(configuredRoot) {
|
|
226
|
-
return typeof configuredRoot === "string" && configuredRoot.trim()
|
|
227
|
-
? resolve(configuredRoot.trim())
|
|
228
|
-
: false;
|
|
229
|
-
}
|
|
230
|
-
function observeClaudeTranscriptSource(memoraxCodeHome, source) {
|
|
231
|
-
const key = resolve(memoraxCodeHome);
|
|
232
|
-
const previous = claudeTranscriptSources.get(key);
|
|
233
|
-
claudeTranscriptSources.set(key, source);
|
|
234
|
-
if (previous === undefined || previous === source)
|
|
235
|
-
return;
|
|
236
|
-
clearClaudeLocalTranscriptProjections();
|
|
237
|
-
for (const cacheKey of combinedTraceHistories.keys()) {
|
|
238
|
-
if (cacheKey === key || cacheKey.startsWith(`${key}\u0000`)) {
|
|
239
|
-
combinedTraceHistories.delete(cacheKey);
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
clearMemoryViewerDataProjections();
|
|
243
|
-
}
|
|
244
|
-
export function memoryViewerDataProjectionCount(memoraxCodeHome) {
|
|
245
|
-
return memoryViewerDataProjectionCacheSize(tracePaths(memoraxCodeHome).sessionsRoot);
|
|
246
|
-
}
|
|
247
|
-
function memoryViewerHistoryFilterKey(filter) {
|
|
248
|
-
return JSON.stringify([
|
|
249
|
-
filter.client ?? "",
|
|
250
|
-
filter.includePendingWritebacks === true,
|
|
251
|
-
filter.eventKey ?? "",
|
|
252
|
-
filter.sessionId ?? "",
|
|
253
|
-
filter.unscopedSession === true,
|
|
254
|
-
filter.projectId ?? "",
|
|
255
|
-
filter.rejectProjectFilter === true,
|
|
256
|
-
filter.activeSessionSince ?? null,
|
|
257
|
-
filter.includeUserProjection === true,
|
|
258
|
-
]);
|
|
259
|
-
}
|
|
260
|
-
export function memoryViewerEventVisible(event) {
|
|
261
|
-
return isMemoryViewerEventType(event.type) && event.type !== "memory_writeback_status"
|
|
262
|
-
&& event.type !== "turn_materialized";
|
|
263
|
-
}
|
|
264
|
-
function eventContent(operation, request, response, items, error) {
|
|
265
|
-
if (error)
|
|
266
|
-
return error;
|
|
267
|
-
if (operation === "writeback") {
|
|
268
|
-
const payload = record(request?.payload);
|
|
269
|
-
const messages = arrayValue(payload?.messages);
|
|
270
|
-
const contents = messages.map((message) => stringValue(record(message)?.content)).filter(Boolean);
|
|
271
|
-
return contents.join("\n\n") || "Memory writeback accepted.";
|
|
272
|
-
}
|
|
273
|
-
const memories = items.map(memoryText).filter(Boolean);
|
|
274
|
-
if (memories.length > 0)
|
|
275
|
-
return memories.join("\n\n");
|
|
276
|
-
const payload = record(request?.payload);
|
|
277
|
-
return stringValue(payload?.query) || "Memory retrieval completed.";
|
|
278
|
-
}
|
|
279
|
-
async function readTraceHistory(memoraxCodeHome, resolveProject, client, claudeProjectsRoot = false) {
|
|
280
|
-
if (client !== undefined && !isMemoryViewerClient(client)) {
|
|
281
|
-
return {
|
|
282
|
-
values: [],
|
|
283
|
-
complete: true,
|
|
284
|
-
claudeLocal: EMPTY_MEMORY_VIEWER_HISTORY,
|
|
285
|
-
};
|
|
286
|
-
}
|
|
287
|
-
const historyCacheRoot = resolve(memoraxCodeHome);
|
|
288
|
-
const projectsByCwd = new Map();
|
|
289
|
-
const resolveProjectOnce = (cwd) => {
|
|
290
|
-
const key = cwd?.trim() ?? "";
|
|
291
|
-
if (!key)
|
|
292
|
-
return undefined;
|
|
293
|
-
if (projectsByCwd.has(key))
|
|
294
|
-
return projectsByCwd.get(key);
|
|
295
|
-
const identity = resolveProject(key);
|
|
296
|
-
projectsByCwd.set(key, identity);
|
|
297
|
-
return identity;
|
|
298
|
-
};
|
|
299
|
-
const readClient = (traceClient) => readIncrementalJsonlProjectionSnapshot({
|
|
300
|
-
root: clientTracePaths(traceClient, memoraxCodeHome).sessionsRoot,
|
|
301
|
-
filename: "events.jsonl",
|
|
302
|
-
identity: resolveProject,
|
|
303
|
-
project(value, sessionDir) {
|
|
304
|
-
const event = traceEventToViewerEvent(value, sessionDir, resolveProjectOnce, traceClient);
|
|
305
|
-
if (!event)
|
|
306
|
-
return undefined;
|
|
307
|
-
const retryKey = event.projectId ? "" : traceEventProjectInput(value);
|
|
308
|
-
return { value: event, ...(retryKey ? { retryKey } : {}) };
|
|
309
|
-
},
|
|
310
|
-
refreshEntry(entry) {
|
|
311
|
-
if (!entry.retryKey)
|
|
312
|
-
return false;
|
|
313
|
-
const identity = resolveProjectOnce(entry.retryKey);
|
|
314
|
-
if (!identity)
|
|
315
|
-
return false;
|
|
316
|
-
entry.value = { ...entry.value, ...viewerProjectFields(identity) };
|
|
317
|
-
entry.retryKey = undefined;
|
|
318
|
-
return true;
|
|
319
|
-
},
|
|
320
|
-
compare: compareMemoryViewerEvents,
|
|
321
|
-
});
|
|
322
|
-
if (client === "codex" || client === "dsh" || client === "opencode") {
|
|
323
|
-
const selected = await readClient(client);
|
|
324
|
-
return { ...selected, claudeLocal: EMPTY_MEMORY_VIEWER_HISTORY };
|
|
325
|
-
}
|
|
326
|
-
const claudeLocalPromise = readClaudeLocalTranscriptHistory(claudeProjectsRoot, resolveProject);
|
|
327
|
-
if (client === "claude") {
|
|
328
|
-
const [claude, claudeLocal] = await Promise.all([
|
|
329
|
-
readClient("claude"),
|
|
330
|
-
claudeLocalPromise,
|
|
331
|
-
]);
|
|
332
|
-
return {
|
|
333
|
-
values: combinedTraceHistory(`${historyCacheRoot}\u0000client=claude\u0000source=${claudeProjectsRoot || "disabled"}`, EMPTY_MEMORY_VIEWER_HISTORY, claude.values, EMPTY_MEMORY_VIEWER_HISTORY, EMPTY_MEMORY_VIEWER_HISTORY, claudeLocal),
|
|
334
|
-
complete: claude.complete,
|
|
335
|
-
claudeLocal,
|
|
336
|
-
};
|
|
337
|
-
}
|
|
338
|
-
const [codex, claude, dsh, opencode, claudeLocal] = await Promise.all([
|
|
339
|
-
readClient("codex"),
|
|
340
|
-
readClient("claude"),
|
|
341
|
-
readClient("dsh"),
|
|
342
|
-
readClient("opencode"),
|
|
343
|
-
claudeLocalPromise,
|
|
344
|
-
]);
|
|
345
|
-
return {
|
|
346
|
-
values: combinedTraceHistory(`${historyCacheRoot}\u0000client=all\u0000source=${claudeProjectsRoot || "disabled"}`, codex.values, claude.values, dsh.values, opencode.values, claudeLocal),
|
|
347
|
-
complete: codex.complete && claude.complete && dsh.complete && opencode.complete,
|
|
348
|
-
claudeLocal,
|
|
349
|
-
};
|
|
350
|
-
}
|
|
351
|
-
function combinedTraceHistory(cacheKey, codex, claude, dsh, opencode, claudeLocal) {
|
|
352
|
-
const cached = combinedTraceHistories.get(cacheKey);
|
|
353
|
-
if (cached?.codex === codex
|
|
354
|
-
&& cached.claude === claude
|
|
355
|
-
&& cached.dsh === dsh
|
|
356
|
-
&& cached.opencode === opencode
|
|
357
|
-
&& cached.claudeLocal === claudeLocal) {
|
|
358
|
-
return cached.values;
|
|
359
|
-
}
|
|
360
|
-
// Native Claude transcripts invalidate this cached identity, but they are
|
|
361
|
-
// admitted only after retained and live Hook trace events are merged.
|
|
362
|
-
const values = [...codex, ...claude, ...dsh, ...opencode].sort(compareMemoryViewerEvents);
|
|
363
|
-
combinedTraceHistories.set(cacheKey, { codex, claude, dsh, opencode, claudeLocal, values });
|
|
364
|
-
return values;
|
|
365
|
-
}
|
|
366
|
-
function admitClaudeTranscriptEvents(traceEvents, localEvents) {
|
|
367
|
-
const hookProjectsByTurn = new Map();
|
|
368
|
-
for (const event of traceEvents) {
|
|
369
|
-
if (event.client !== "claude"
|
|
370
|
-
|| event.type !== "turn_start"
|
|
371
|
-
|| event.id.startsWith("claude-local:")
|
|
372
|
-
|| !event.sessionId
|
|
373
|
-
|| !event.turnId) {
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
const key = viewerTurnKey(event.client, event.sessionId, event.turnId);
|
|
377
|
-
const projects = hookProjectsByTurn.get(key) ?? new Set();
|
|
378
|
-
projects.add(event.projectId ?? "");
|
|
379
|
-
hookProjectsByTurn.set(key, projects);
|
|
380
|
-
}
|
|
381
|
-
const turnScopes = new Map();
|
|
382
|
-
for (const [key, projects] of hookProjectsByTurn) {
|
|
383
|
-
if (projects.size !== 1)
|
|
384
|
-
continue;
|
|
385
|
-
const projectId = projects.values().next().value;
|
|
386
|
-
turnScopes.set(key, projectId || undefined);
|
|
387
|
-
}
|
|
388
|
-
if (turnScopes.size === 0 || localEvents.length === 0) {
|
|
389
|
-
return { turnScopes, events: EMPTY_MEMORY_VIEWER_HISTORY };
|
|
390
|
-
}
|
|
391
|
-
return { turnScopes, events: localEvents.filter((event) => (claudeTranscriptEventAdmitted(event, turnScopes))) };
|
|
392
|
-
}
|
|
393
|
-
function claudeTranscriptEventAdmitted(event, turnScopes) {
|
|
394
|
-
if (event.client !== "claude" || !event.sessionId || !event.turnId)
|
|
395
|
-
return false;
|
|
396
|
-
const key = viewerTurnKey(event.client, event.sessionId, event.turnId);
|
|
397
|
-
if (!turnScopes.has(key))
|
|
398
|
-
return false;
|
|
399
|
-
const projectId = turnScopes.get(key);
|
|
400
|
-
return event.projectId === projectId;
|
|
401
|
-
}
|
|
402
|
-
function coalesceClaudeTranscriptTurns(traceEvents, localEvents) {
|
|
403
|
-
if (localEvents.length === 0)
|
|
404
|
-
return traceEvents;
|
|
405
|
-
const localByTurn = new Map();
|
|
406
|
-
for (const event of localEvents) {
|
|
407
|
-
const key = claudeTurnLifecycleKey(event);
|
|
408
|
-
if (!key)
|
|
409
|
-
continue;
|
|
410
|
-
const candidates = localByTurn.get(key) ?? [];
|
|
411
|
-
candidates.push(event);
|
|
412
|
-
localByTurn.set(key, candidates);
|
|
413
|
-
}
|
|
414
|
-
const suppressed = new Set();
|
|
415
|
-
const coalesced = traceEvents.map((event) => {
|
|
416
|
-
const key = claudeTurnLifecycleKey(event);
|
|
417
|
-
const candidates = key ? localByTurn.get(key) : undefined;
|
|
418
|
-
if (!key || !candidates)
|
|
419
|
-
return event;
|
|
420
|
-
for (const candidate of candidates)
|
|
421
|
-
suppressed.add(candidate);
|
|
422
|
-
const matching = event.projectId
|
|
423
|
-
? candidates.filter((candidate) => candidate.projectId === event.projectId)
|
|
424
|
-
: candidates;
|
|
425
|
-
const local = matching.length === 1 ? matching[0] : undefined;
|
|
426
|
-
if (!local)
|
|
427
|
-
return event;
|
|
428
|
-
const project = event.projectId ? {} : viewerProjectFields(memoryProjectFromViewerEvent(local));
|
|
429
|
-
if (event.type === "turn_start") {
|
|
430
|
-
const prompt = local.prompt || event.prompt;
|
|
431
|
-
return {
|
|
432
|
-
...event,
|
|
433
|
-
...project,
|
|
434
|
-
...(prompt ? { prompt, content: prompt } : {}),
|
|
435
|
-
};
|
|
436
|
-
}
|
|
437
|
-
const answer = local.answer || event.answer;
|
|
438
|
-
return {
|
|
439
|
-
...event,
|
|
440
|
-
...project,
|
|
441
|
-
...(answer ? { answer, content: answer } : {}),
|
|
442
|
-
...((local.turnOutcome === "interrupted" || !event.turnOutcome) && local.turnOutcome
|
|
443
|
-
? { turnOutcome: local.turnOutcome }
|
|
444
|
-
: {}),
|
|
445
|
-
};
|
|
446
|
-
});
|
|
447
|
-
for (const event of localEvents) {
|
|
448
|
-
if (!suppressed.has(event))
|
|
449
|
-
coalesced.push(event);
|
|
450
|
-
}
|
|
451
|
-
return coalesced;
|
|
452
|
-
}
|
|
453
|
-
function claudeTurnLifecycleKey(event) {
|
|
454
|
-
if (event.client !== "claude"
|
|
455
|
-
|| !event.sessionId
|
|
456
|
-
|| !event.turnId
|
|
457
|
-
|| (event.type !== "turn_start" && event.type !== "turn_end")) {
|
|
458
|
-
return "";
|
|
459
|
-
}
|
|
460
|
-
return `${event.sessionId}\u0000${event.turnId}\u0000${event.type}`;
|
|
461
|
-
}
|
|
462
|
-
function memoryProjectFromViewerEvent(event) {
|
|
463
|
-
return event.projectId && event.projectLabel
|
|
464
|
-
? { projectId: event.projectId, projectLabel: event.projectLabel }
|
|
465
|
-
: undefined;
|
|
466
|
-
}
|
|
467
|
-
function associateClaudeEventsWithTranscriptTurns(traceEvents, transcriptEvents, admittedTurnScopes) {
|
|
468
|
-
if (transcriptEvents.length === 0)
|
|
469
|
-
return traceEvents;
|
|
470
|
-
const startsBySession = new Map();
|
|
471
|
-
const endsByTurn = new Map();
|
|
472
|
-
for (const event of transcriptEvents) {
|
|
473
|
-
if (event.client !== "claude" || !event.sessionId || !event.turnId)
|
|
474
|
-
continue;
|
|
475
|
-
if (event.type === "turn_start") {
|
|
476
|
-
const starts = startsBySession.get(event.sessionId) ?? [];
|
|
477
|
-
starts.push(event);
|
|
478
|
-
startsBySession.set(event.sessionId, starts);
|
|
479
|
-
}
|
|
480
|
-
else if (event.type === "turn_end") {
|
|
481
|
-
endsByTurn.set(viewerTurnProjectKey(event.client, event.sessionId, event.turnId, event.projectId), event);
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
for (const starts of startsBySession.values())
|
|
485
|
-
starts.sort(compareMemoryViewerEvents);
|
|
486
|
-
let changed = false;
|
|
487
|
-
const associated = traceEvents.map((event) => {
|
|
488
|
-
if (event.client !== "claude"
|
|
489
|
-
|| !event.sessionId
|
|
490
|
-
|| !event.projectId
|
|
491
|
-
|| (event.turnId
|
|
492
|
-
&& event.type !== "memory_cli_search"
|
|
493
|
-
&& event.type !== "memory_cli_add")) {
|
|
494
|
-
return event;
|
|
495
|
-
}
|
|
496
|
-
const starts = startsBySession.get(event.sessionId);
|
|
497
|
-
if (!starts)
|
|
498
|
-
return event;
|
|
499
|
-
const references = event.turnReferences ?? [];
|
|
500
|
-
if (references.length === 0)
|
|
501
|
-
return event;
|
|
502
|
-
const resolvedReferences = references.map((reference) => {
|
|
503
|
-
const owner = transcriptTurnForReference(event, reference, starts, endsByTurn, admittedTurnScopes);
|
|
504
|
-
if (owner?.turnId)
|
|
505
|
-
return { ...reference, turnId: owner.turnId };
|
|
506
|
-
const { turnId: _turnId, ...unassociated } = reference;
|
|
507
|
-
return unassociated;
|
|
508
|
-
});
|
|
509
|
-
const turnIds = [...new Set(resolvedReferences
|
|
510
|
-
.map((reference) => safeMemoryViewerTurnId(reference.turnId))
|
|
511
|
-
.filter(Boolean))];
|
|
512
|
-
changed = true;
|
|
513
|
-
const { turnId: _turnId, ...unassociated } = event;
|
|
514
|
-
if (turnIds.length === 0
|
|
515
|
-
|| resolvedReferences.some((reference) => !safeMemoryViewerTurnId(reference.turnId))) {
|
|
516
|
-
return { ...unassociated, turnReferences: resolvedReferences };
|
|
517
|
-
}
|
|
518
|
-
return turnIds.length === 1
|
|
519
|
-
? { ...unassociated, turnId: turnIds[0], turnReferences: resolvedReferences }
|
|
520
|
-
: { ...unassociated, turnReferences: resolvedReferences };
|
|
521
|
-
});
|
|
522
|
-
return changed ? associated : traceEvents;
|
|
523
|
-
}
|
|
524
|
-
const CLAUDE_NATIVE_ACTIVITY_MATCH_WINDOW_MS = 10 * 60 * 1_000;
|
|
525
|
-
function coalesceClaudeTranscriptActivities(allEvents) {
|
|
526
|
-
const nativeByTurn = new Map();
|
|
527
|
-
for (const event of allEvents) {
|
|
528
|
-
if (!isClaudeNativeMemoryActivity(event) || !event.sessionId)
|
|
529
|
-
continue;
|
|
530
|
-
for (const turnId of memoryViewerEventTurnIds(event)) {
|
|
531
|
-
const key = claudeActivityTurnKey(event.sessionId, turnId, event.type);
|
|
532
|
-
const candidates = nativeByTurn.get(key) ?? [];
|
|
533
|
-
candidates.push(event);
|
|
534
|
-
nativeByTurn.set(key, candidates);
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
if (nativeByTurn.size === 0)
|
|
538
|
-
return allEvents;
|
|
539
|
-
for (const candidates of nativeByTurn.values())
|
|
540
|
-
candidates.sort(compareMemoryViewerEvents);
|
|
541
|
-
const suppressed = new Set();
|
|
542
|
-
for (const traceEvent of allEvents) {
|
|
543
|
-
if (!isClaudeTraceMemoryActivity(traceEvent) || !traceEvent.sessionId)
|
|
544
|
-
continue;
|
|
545
|
-
const traceTimes = claudeTraceActivityTimes(traceEvent);
|
|
546
|
-
if (traceTimes.length === 0)
|
|
547
|
-
continue;
|
|
548
|
-
const candidates = memoryViewerEventTurnIds(traceEvent)
|
|
549
|
-
.flatMap((turnId) => nativeByTurn.get(claudeActivityTurnKey(traceEvent.sessionId, turnId, traceEvent.type)) ?? [])
|
|
550
|
-
.filter((candidate) => !suppressed.has(candidate));
|
|
551
|
-
const projectCandidates = traceEvent.projectId
|
|
552
|
-
? candidates.filter((candidate) => candidate.projectId === traceEvent.projectId)
|
|
553
|
-
: candidates;
|
|
554
|
-
if (!traceEvent.projectId
|
|
555
|
-
&& new Set(projectCandidates.map((candidate) => candidate.projectId ?? "")).size > 1) {
|
|
556
|
-
continue;
|
|
557
|
-
}
|
|
558
|
-
const ranked = projectCandidates
|
|
559
|
-
.map((candidate) => ({
|
|
560
|
-
candidate,
|
|
561
|
-
distance: Math.min(...traceTimes.map((traceTime) => (Math.abs(Date.parse(candidate.timestamp) - traceTime)))),
|
|
562
|
-
}))
|
|
563
|
-
.filter((entry) => Number.isFinite(entry.distance))
|
|
564
|
-
.sort((left, right) => (left.distance - right.distance
|
|
565
|
-
|| compareMemoryViewerEvents(left.candidate, right.candidate)));
|
|
566
|
-
const nearest = ranked[0];
|
|
567
|
-
if (!nearest
|
|
568
|
-
|| nearest.distance > CLAUDE_NATIVE_ACTIVITY_MATCH_WINDOW_MS) {
|
|
569
|
-
continue;
|
|
570
|
-
}
|
|
571
|
-
suppressed.add(nearest.candidate);
|
|
572
|
-
}
|
|
573
|
-
return suppressed.size === 0
|
|
574
|
-
? allEvents
|
|
575
|
-
: allEvents.filter((event) => !suppressed.has(event));
|
|
576
|
-
}
|
|
577
|
-
function claudeTraceActivityTimes(event) {
|
|
578
|
-
return [event.timestamp, ...(event.turnReferences ?? []).map((reference) => reference.capturedAt)]
|
|
579
|
-
.map((timestamp) => Date.parse(timestamp))
|
|
580
|
-
.filter(Number.isFinite);
|
|
581
|
-
}
|
|
582
|
-
function isClaudeNativeMemoryActivity(event) {
|
|
583
|
-
return event.client === "claude"
|
|
584
|
-
&& event.id.startsWith("claude-local:")
|
|
585
|
-
&& (event.type === "memory_cli_search" || event.type === "memory_cli_add");
|
|
586
|
-
}
|
|
587
|
-
function isClaudeTraceMemoryActivity(event) {
|
|
588
|
-
return event.client === "claude"
|
|
589
|
-
&& !event.id.startsWith("claude-local:")
|
|
590
|
-
&& (event.type === "memory_cli_search" || event.type === "memory_cli_add");
|
|
591
|
-
}
|
|
592
|
-
function claudeActivityTurnKey(sessionId, turnId, type) {
|
|
593
|
-
return `${sessionId}\u0000${turnId}\u0000${type}`;
|
|
594
|
-
}
|
|
595
|
-
function transcriptTurnForReference(event, reference, starts, endsByTurn, admittedTurnScopes) {
|
|
596
|
-
const capturedAt = Date.parse(reference.capturedAt);
|
|
597
|
-
if (!Number.isFinite(capturedAt) || !event.sessionId || !event.projectId)
|
|
598
|
-
return undefined;
|
|
599
|
-
const explicitTurnId = safeMemoryViewerTurnId(reference.turnId);
|
|
600
|
-
const candidates = [];
|
|
601
|
-
for (const [index, start] of starts.entries()) {
|
|
602
|
-
if (!start.turnId || start.projectId !== event.projectId)
|
|
603
|
-
continue;
|
|
604
|
-
if (!claudeTranscriptEventAdmitted(start, admittedTurnScopes))
|
|
605
|
-
continue;
|
|
606
|
-
if (explicitTurnId && start.turnId !== explicitTurnId)
|
|
607
|
-
continue;
|
|
608
|
-
const startedAt = Date.parse(start.timestamp);
|
|
609
|
-
if (!Number.isFinite(startedAt) || capturedAt < startedAt)
|
|
610
|
-
continue;
|
|
611
|
-
const end = endsByTurn.get(viewerTurnProjectKey(event.client, event.sessionId, start.turnId, start.projectId));
|
|
612
|
-
const endedAt = end ? Date.parse(end.timestamp) : Number.NaN;
|
|
613
|
-
const nextStart = starts.slice(index + 1).find((candidate) => (candidate.projectId === event.projectId));
|
|
614
|
-
const nextStartedAt = Date.parse(nextStart?.timestamp ?? "");
|
|
615
|
-
if (Number.isFinite(endedAt) && capturedAt > endedAt)
|
|
616
|
-
continue;
|
|
617
|
-
if (!Number.isFinite(endedAt) && Number.isFinite(nextStartedAt) && capturedAt >= nextStartedAt)
|
|
618
|
-
continue;
|
|
619
|
-
candidates.push(start);
|
|
620
|
-
}
|
|
621
|
-
return candidates.length === 1 ? candidates[0] : undefined;
|
|
622
|
-
}
|
|
623
|
-
function memoryViewerTurnReferences(relatedTurns, traceContext) {
|
|
624
|
-
const collect = (values) => {
|
|
625
|
-
const output = [];
|
|
626
|
-
const seen = new Set();
|
|
627
|
-
for (const value of values) {
|
|
628
|
-
const raw = record(value);
|
|
629
|
-
const capturedAt = canonicalOptionalMemoryViewerTimestamp(raw?.captured_at ?? raw?.capturedAt);
|
|
630
|
-
if (!capturedAt)
|
|
631
|
-
continue;
|
|
632
|
-
const turnId = safeMemoryViewerTurnId(raw?.turn_id ?? raw?.turnId);
|
|
633
|
-
const key = `${turnId}\u0000${capturedAt}`;
|
|
634
|
-
if (seen.has(key))
|
|
635
|
-
continue;
|
|
636
|
-
seen.add(key);
|
|
637
|
-
output.push({ ...(turnId ? { turnId } : {}), capturedAt });
|
|
638
|
-
}
|
|
639
|
-
return output;
|
|
640
|
-
};
|
|
641
|
-
const related = collect(arrayValue(relatedTurns));
|
|
642
|
-
return related.length > 0 ? related : collect(traceContext ? [traceContext] : []);
|
|
643
|
-
}
|
|
644
|
-
function viewerTurnKey(client, sessionId, turnId) {
|
|
645
|
-
return `${client}\u0000${sessionId}\u0000${turnId}`;
|
|
646
|
-
}
|
|
647
|
-
function viewerTurnProjectKey(client, sessionId, turnId, projectId) {
|
|
648
|
-
return `${viewerTurnKey(client, sessionId, turnId)}\u0000${projectId ?? ""}`;
|
|
649
|
-
}
|
|
650
|
-
function traceEventProjectInput(value) {
|
|
651
|
-
const raw = record(value);
|
|
652
|
-
const trace = record(raw?.trace);
|
|
653
|
-
if (memoryProjectFromUnknown(trace?.memory_project) || memoryProjectFromUnknown(trace?.memoryProject))
|
|
654
|
-
return "";
|
|
655
|
-
return stringValue(trace?.cwd) || stringValue(record(raw?.request)?.cwd);
|
|
656
|
-
}
|
|
657
|
-
function compareMemoryViewerEvents(left, right) {
|
|
658
|
-
const leftTime = Date.parse(left.timestamp);
|
|
659
|
-
const rightTime = Date.parse(right.timestamp);
|
|
660
|
-
const leftOrder = Number.isFinite(leftTime) ? leftTime : 0;
|
|
661
|
-
const rightOrder = Number.isFinite(rightTime) ? rightTime : 0;
|
|
662
|
-
return leftOrder - rightOrder || left.eventKey.localeCompare(right.eventKey);
|
|
663
|
-
}
|
|
664
|
-
function traceEventToViewerEvent(value, sessionDir, resolveProject, client) {
|
|
665
|
-
const raw = record(value);
|
|
666
|
-
const type = memoryViewerEventType(raw?.type);
|
|
667
|
-
if (!raw || !type)
|
|
668
|
-
return undefined;
|
|
669
|
-
const operation = type === "turn_start"
|
|
670
|
-
? "query"
|
|
671
|
-
: type === "turn_end" || type === "turn_materialized"
|
|
672
|
-
? "reply"
|
|
673
|
-
: raw.operation === "writeback"
|
|
674
|
-
? "writeback"
|
|
675
|
-
: raw.operation === "query"
|
|
676
|
-
? "query"
|
|
677
|
-
: raw.operation === "retrieve"
|
|
678
|
-
? "retrieve"
|
|
679
|
-
: undefined;
|
|
680
|
-
if (!operation)
|
|
681
|
-
return undefined;
|
|
682
|
-
const source = typeof raw.source === "string" ? raw.source : "unknown";
|
|
683
|
-
const request = record(raw.request);
|
|
684
|
-
const response = record(raw.response);
|
|
685
|
-
const items = arrayValue(response?.items);
|
|
686
|
-
const query = searchQuery(request);
|
|
687
|
-
const results = searchResults(items);
|
|
688
|
-
const timestamp = canonicalMemoryViewerTimestamp(raw.timestamp);
|
|
689
|
-
const error = stringValue(raw.error);
|
|
690
|
-
const trace = record(raw.trace);
|
|
691
|
-
const turnId = safeMemoryViewerTurnId(stringValue(trace?.turn_id) || stringValue(trace?.turnId));
|
|
692
|
-
const turnReferences = client === "claude"
|
|
693
|
-
? memoryViewerTurnReferences(raw.related_turns ?? raw.relatedTurns, trace)
|
|
694
|
-
: [];
|
|
695
|
-
const persistedProjectIdentity = memoryProjectFromUnknown(trace?.memory_project)
|
|
696
|
-
?? memoryProjectFromUnknown(trace?.memoryProject);
|
|
697
|
-
const projectCwd = stringValue(trace?.cwd) || stringValue(request?.cwd);
|
|
698
|
-
// Resolving the persisted cwd rebuilds the process-local project-root index on
|
|
699
|
-
// cold start and verifies any persisted identity before readiness uses it.
|
|
700
|
-
const resolvedProjectIdentity = resolveProject(projectCwd);
|
|
701
|
-
const projectIdentity = resolvedProjectIdentity ?? persistedProjectIdentity;
|
|
702
|
-
const rawTraceSessionId = stringValue(trace?.session_id) || stringValue(trace?.sessionId);
|
|
703
|
-
const traceSessionId = rawTraceSessionId ? sessionId(rawTraceSessionId) : sessionId(sessionDir);
|
|
704
|
-
const writeback = writebackState(operation === "reply" ? "query" : operation, response);
|
|
705
|
-
const persistedOutcome = memoryViewerWritebackOutcome(response?.outcome);
|
|
706
|
-
const savedMemoryCount = nonNegativeInteger(response?.savedMemoryCount ?? response?.saved_memory_count);
|
|
707
|
-
const savedMemoryIds = stringArray(response?.savedMemoryIds ?? response?.saved_memory_ids);
|
|
708
|
-
const savedMemories = stringArray(response?.savedMemories ?? response?.saved_memories);
|
|
709
|
-
const turnOutcome = type === "turn_end" || type === "turn_materialized"
|
|
710
|
-
? memoryViewerTurnOutcome(raw.outcome) ?? "completed"
|
|
711
|
-
: undefined;
|
|
712
|
-
const writebackMessages = operation === "writeback"
|
|
713
|
-
? writebackConversation(operation, request)
|
|
714
|
-
: { prompt: "", answer: "" };
|
|
715
|
-
const rawPrompt = stringValue(request?.prompt)
|
|
716
|
-
|| stringValue(request?.userPrompt)
|
|
717
|
-
|| stringValue(request?.user_prompt);
|
|
718
|
-
const prompt = type === "turn_start" || type === "turn_materialized"
|
|
719
|
-
? (client === "codex" ? stripCodexPreambleSegments(rawPrompt) : rawPrompt)
|
|
720
|
-
: writebackMessages.prompt;
|
|
721
|
-
const answer = type === "turn_end" || type === "turn_materialized"
|
|
722
|
-
? stringValue(response?.assistantMessage) || stringValue(response?.assistant_message) || stringValue(response?.answer)
|
|
723
|
-
: writebackMessages.answer;
|
|
724
|
-
const content = type === "turn_start"
|
|
725
|
-
? prompt || "Turn started."
|
|
726
|
-
: type === "turn_end" || type === "turn_materialized"
|
|
727
|
-
? answer || (turnOutcome === "interrupted" ? "" : "Turn ended.")
|
|
728
|
-
: eventContent(operation === "reply" ? "query" : operation, request, response, items, error || undefined);
|
|
729
|
-
const eventId = stringValue(raw.event_id) || `${traceSessionId || "unscoped"}:${type}:${timestamp}`;
|
|
730
|
-
const id = persistedMemoryViewerEventId(client, eventId);
|
|
731
|
-
const originalEventId = memoryViewerOriginalEventId(request?.original_event_id ?? request?.originalEventId, client);
|
|
732
|
-
return {
|
|
733
|
-
id,
|
|
734
|
-
eventKey: memoryViewerEventKey({
|
|
735
|
-
id,
|
|
736
|
-
client,
|
|
737
|
-
...(traceSessionId ? { sessionId: traceSessionId } : {}),
|
|
738
|
-
}),
|
|
739
|
-
client,
|
|
740
|
-
type,
|
|
741
|
-
timestamp,
|
|
742
|
-
source,
|
|
743
|
-
operation,
|
|
744
|
-
ok: raw.ok !== false,
|
|
745
|
-
...viewerProjectFields(projectIdentity),
|
|
746
|
-
...(traceSessionId ? { sessionId: traceSessionId } : {}),
|
|
747
|
-
...(turnId ? { turnId } : {}),
|
|
748
|
-
...(turnReferences.length > 0 ? { turnReferences } : {}),
|
|
749
|
-
content,
|
|
750
|
-
...(turnOutcome ? { turnOutcome } : {}),
|
|
751
|
-
...(prompt ? { prompt } : {}),
|
|
752
|
-
...(answer ? { answer } : {}),
|
|
753
|
-
...(query ? { query } : {}),
|
|
754
|
-
...(results.length > 0 ? { results } : {}),
|
|
755
|
-
...(items.length > 0 ? { itemCount: items.length } : {}),
|
|
756
|
-
...(stringValue(response?.receiptId) ? { receiptId: stringValue(response?.receiptId) } : {}),
|
|
757
|
-
...(writeback.taskId ? { taskId: writeback.taskId } : {}),
|
|
758
|
-
...(originalEventId ? { originalEventId } : {}),
|
|
759
|
-
...(writeback.status ? { writebackStatus: writeback.status } : {}),
|
|
760
|
-
...(persistedOutcome
|
|
761
|
-
? { writebackOutcome: persistedOutcome }
|
|
762
|
-
: writeback.status ? { writebackOutcome: writebackOutcomeForStatus(writeback.status) } : {}),
|
|
763
|
-
...(savedMemoryCount === undefined ? {} : { savedMemoryCount }),
|
|
764
|
-
...(savedMemoryIds.length > 0 ? { savedMemoryIds } : {}),
|
|
765
|
-
...(savedMemories.length > 0 ? { savedMemories } : {}),
|
|
766
|
-
...(error ? { error: error.slice(0, 2000) } : {}),
|
|
767
|
-
details: sanitizeMemoryViewerDetails({ request: raw.request, response: raw.response }),
|
|
768
|
-
};
|
|
769
|
-
}
|
|
770
|
-
function memoryViewerTurnOutcome(value) {
|
|
771
|
-
return value === "completed" || value === "interrupted" ? value : undefined;
|
|
772
|
-
}
|
|
773
|
-
function memoryViewerOriginalEventId(value, client) {
|
|
774
|
-
const eventId = stringValue(value);
|
|
775
|
-
if (!eventId)
|
|
776
|
-
return "";
|
|
777
|
-
return persistedMemoryViewerEventId(client, eventId);
|
|
778
|
-
}
|
|
779
|
-
function viewerProjectFields(identity) {
|
|
780
|
-
if (!identity)
|
|
781
|
-
return {};
|
|
782
|
-
return {
|
|
783
|
-
projectId: identity.projectId,
|
|
784
|
-
projectLabel: identity.projectLabel,
|
|
785
|
-
project: identity.projectLabel,
|
|
786
|
-
};
|
|
787
|
-
}
|
|
788
|
-
function projectCatalog(allEvents) {
|
|
789
|
-
const projects = new Map();
|
|
790
|
-
for (const event of allEvents) {
|
|
791
|
-
if (!event.projectId || !event.projectLabel)
|
|
792
|
-
continue;
|
|
793
|
-
const current = projects.get(event.projectId);
|
|
794
|
-
projects.set(event.projectId, {
|
|
795
|
-
projectId: event.projectId,
|
|
796
|
-
projectLabel: event.projectLabel,
|
|
797
|
-
eventCount: (current?.eventCount ?? 0) + 1,
|
|
798
|
-
lastSeenAt: !current || Date.parse(event.timestamp) >= Date.parse(current.lastSeenAt)
|
|
799
|
-
? event.timestamp
|
|
800
|
-
: current.lastSeenAt,
|
|
801
|
-
});
|
|
802
|
-
}
|
|
803
|
-
return [...projects.values()].sort((left, right) => (left.projectLabel.localeCompare(right.projectLabel) || left.projectId.localeCompare(right.projectId)));
|
|
804
|
-
}
|
|
805
|
-
function projectSessionCatalog(allEvents) {
|
|
806
|
-
const entries = new Map();
|
|
807
|
-
for (const event of allEvents) {
|
|
808
|
-
const projectId = event.projectId ?? UNCLASSIFIED_PROJECT_ID;
|
|
809
|
-
const key = `${event.client}\u0000${projectId}\u0000${event.sessionId ?? ""}`;
|
|
810
|
-
const current = entries.get(key);
|
|
811
|
-
entries.set(key, {
|
|
812
|
-
client: event.client,
|
|
813
|
-
projectId,
|
|
814
|
-
...(event.sessionId ? { sessionId: event.sessionId } : { unscoped: true }),
|
|
815
|
-
eventCount: (current?.eventCount ?? 0) + 1,
|
|
816
|
-
lastSeenAt: !current || Date.parse(event.timestamp) >= Date.parse(current.lastSeenAt)
|
|
817
|
-
? event.timestamp
|
|
818
|
-
: current.lastSeenAt,
|
|
819
|
-
});
|
|
820
|
-
}
|
|
821
|
-
return [...entries.values()].sort((left, right) => (left.projectId.localeCompare(right.projectId)
|
|
822
|
-
|| left.client.localeCompare(right.client)
|
|
823
|
-
|| Date.parse(right.lastSeenAt) - Date.parse(left.lastSeenAt)
|
|
824
|
-
|| (left.sessionId ?? "").localeCompare(right.sessionId ?? "")));
|
|
825
|
-
}
|
|
826
|
-
function resolveProjectSelection(projects, hasUnclassified, projectId, rejectProjectFilter) {
|
|
827
|
-
if (rejectProjectFilter)
|
|
828
|
-
return { status: "invalid", blockAll: true };
|
|
829
|
-
if (projectId) {
|
|
830
|
-
const exists = projectId === UNCLASSIFIED_PROJECT_ID
|
|
831
|
-
? hasUnclassified
|
|
832
|
-
: projects.some((project) => project.projectId === projectId);
|
|
833
|
-
return { projectId, status: exists ? "resolved" : "missing", blockAll: !exists };
|
|
834
|
-
}
|
|
835
|
-
return {};
|
|
836
|
-
}
|
|
837
|
-
function memoryViewerEventType(value) {
|
|
838
|
-
return isMemoryViewerEventType(value) ? value : undefined;
|
|
839
|
-
}
|
|
840
|
-
function isMemoryViewerEventType(value) {
|
|
841
|
-
return value === "turn_start"
|
|
842
|
-
|| value === "turn_materialized"
|
|
843
|
-
|| value === "memory_retrieve"
|
|
844
|
-
|| value === "memory_cli_search"
|
|
845
|
-
|| value === "memory_cli_add"
|
|
846
|
-
|| value === "memory_writeback"
|
|
847
|
-
|| value === "memory_writeback_status"
|
|
848
|
-
|| value === "turn_end";
|
|
849
|
-
}
|
|
850
|
-
function memoryEventType(source, operation) {
|
|
851
|
-
if (source === "memory_cli")
|
|
852
|
-
return operation === "writeback" ? "memory_cli_add" : "memory_cli_search";
|
|
853
|
-
return operation === "writeback" ? "memory_writeback" : "memory_retrieve";
|
|
854
|
-
}
|
|
855
|
-
function searchQuery(request) {
|
|
856
|
-
return stringValue(record(request?.payload)?.query) || stringValue(request?.query);
|
|
857
|
-
}
|
|
858
|
-
function searchResults(items) {
|
|
859
|
-
return items
|
|
860
|
-
.map((item) => {
|
|
861
|
-
const value = record(item);
|
|
862
|
-
const content = memoryText(item);
|
|
863
|
-
if (!value || !content)
|
|
864
|
-
return undefined;
|
|
865
|
-
const score = typeof value.score === "number" && Number.isFinite(value.score) ? value.score : undefined;
|
|
866
|
-
const scoreDetails = record(value.score_details);
|
|
867
|
-
const semanticSimilarity = scoreDetails?.semantic_similarity ?? value.semantic_similarity;
|
|
868
|
-
const confidence = typeof semanticSimilarity === "number"
|
|
869
|
-
&& Number.isFinite(semanticSimilarity)
|
|
870
|
-
&& semanticSimilarity >= 0
|
|
871
|
-
&& semanticSimilarity <= 1
|
|
872
|
-
? semanticSimilarity
|
|
873
|
-
: undefined;
|
|
874
|
-
return {
|
|
875
|
-
content,
|
|
876
|
-
...(score === undefined ? {} : { score }),
|
|
877
|
-
...(confidence === undefined ? {} : { confidence }),
|
|
878
|
-
};
|
|
879
|
-
})
|
|
880
|
-
.filter((item) => Boolean(item));
|
|
881
|
-
}
|
|
882
|
-
function writebackConversation(operation, request) {
|
|
883
|
-
if (operation !== "writeback")
|
|
884
|
-
return { prompt: "", answer: "" };
|
|
885
|
-
const messages = arrayValue(record(request?.payload)?.messages);
|
|
886
|
-
const contentsForRole = (role) => messages
|
|
887
|
-
.map(record)
|
|
888
|
-
.filter((message) => stringValue(message?.role).toLowerCase() === role)
|
|
889
|
-
.map((message) => stringValue(message?.content))
|
|
890
|
-
.filter(Boolean)
|
|
891
|
-
.join("\n\n");
|
|
892
|
-
return {
|
|
893
|
-
prompt: contentsForRole("user"),
|
|
894
|
-
answer: contentsForRole("assistant"),
|
|
895
|
-
};
|
|
896
|
-
}
|
|
897
|
-
function writebackState(operation, response) {
|
|
898
|
-
if (operation !== "writeback")
|
|
899
|
-
return { taskId: "", status: "" };
|
|
900
|
-
const raw = record(response?.raw) ?? response;
|
|
901
|
-
const data = record(raw?.data);
|
|
902
|
-
return {
|
|
903
|
-
taskId: stringValue(data?.task_id) || stringValue(response?.taskId),
|
|
904
|
-
status: (stringValue(data?.status)
|
|
905
|
-
|| stringValue(response?.status)
|
|
906
|
-
|| stringValue(response?.addStatus)).toLowerCase(),
|
|
907
|
-
};
|
|
908
|
-
}
|
|
909
|
-
function record(value) {
|
|
910
|
-
return value && typeof value === "object" && !Array.isArray(value) ? value : undefined;
|
|
911
|
-
}
|
|
912
|
-
function arrayValue(value) {
|
|
913
|
-
return Array.isArray(value) ? value : [];
|
|
914
|
-
}
|
|
915
|
-
function stringValue(value) {
|
|
916
|
-
return typeof value === "string" ? value.trim() : "";
|
|
917
|
-
}
|
|
918
|
-
function canonicalMemoryViewerTimestamp(value) {
|
|
919
|
-
if (value === undefined || value === null || value === "")
|
|
920
|
-
return new Date().toISOString();
|
|
921
|
-
return canonicalOptionalMemoryViewerTimestamp(value) ?? new Date(0).toISOString();
|
|
922
|
-
}
|
|
923
|
-
function canonicalOptionalMemoryViewerTimestamp(value) {
|
|
924
|
-
if (typeof value !== "string" || !value.trim() || value.length > 512)
|
|
925
|
-
return "";
|
|
926
|
-
const parsed = Date.parse(value.trim());
|
|
927
|
-
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : "";
|
|
928
|
-
}
|
|
929
|
-
export function safeMemoryViewerSessionId(value) {
|
|
930
|
-
const candidate = typeof value === "string" ? value.trim() : "";
|
|
931
|
-
if (candidate.length > 512)
|
|
932
|
-
return "";
|
|
933
|
-
// This value is reserved by the viewer protocol to select events without a session.
|
|
934
|
-
if (!candidate || candidate === MEMORY_VIEWER_UNSCOPED_SESSION_FILTER)
|
|
935
|
-
return "";
|
|
936
|
-
if (/[\u0000-\u001f\u007f]/.test(candidate))
|
|
937
|
-
return "";
|
|
938
|
-
if (/[\\/]/.test(candidate))
|
|
939
|
-
return "";
|
|
940
|
-
return candidate;
|
|
941
|
-
}
|
|
942
|
-
export function safeMemoryViewerTurnId(value) {
|
|
943
|
-
const candidate = typeof value === "string" ? value.trim() : "";
|
|
944
|
-
if (!candidate || candidate.length > 512)
|
|
945
|
-
return "";
|
|
946
|
-
if (/[\u0000-\u001f\u007f\\/]/.test(candidate))
|
|
947
|
-
return "";
|
|
948
|
-
return candidate;
|
|
949
|
-
}
|
|
950
|
-
function sessionId(value) {
|
|
951
|
-
return safeMemoryViewerSessionId(value);
|
|
952
|
-
}
|
|
953
|
-
function memoryViewerClient(input) {
|
|
954
|
-
if (input.traceContext?.client !== undefined) {
|
|
955
|
-
return isMemoryViewerClient(input.traceContext.client)
|
|
956
|
-
? input.traceContext.client
|
|
957
|
-
: undefined;
|
|
958
|
-
}
|
|
959
|
-
if (input.source === "dsh_native_retrieval" || input.source === "dsh_native_writeback") {
|
|
960
|
-
return undefined;
|
|
961
|
-
}
|
|
962
|
-
if (input.source === "claude_hook_retrieval" || input.source === "claude_hook_writeback")
|
|
963
|
-
return "claude";
|
|
964
|
-
if (input.source === "opencode_plugin_retrieval" || input.source === "opencode_plugin_writeback")
|
|
965
|
-
return "opencode";
|
|
966
|
-
return "codex";
|
|
967
|
-
}
|
|
968
|
-
function isMemoryViewerClient(client) {
|
|
969
|
-
return client === "codex" || client === "claude" || client === "dsh" || client === "opencode";
|
|
970
|
-
}
|
|
971
|
-
function memoryViewerEventId(client, eventId) {
|
|
972
|
-
if (eventId)
|
|
973
|
-
return persistedMemoryViewerEventId(client, eventId);
|
|
974
|
-
return `${client === "codex" ? "" : `${client}-`}memory-viewer:${randomUUID()}`;
|
|
975
|
-
}
|
|
976
|
-
function persistedMemoryViewerEventId(client, eventId) {
|
|
977
|
-
return client === "codex" ? `trace:${eventId}` : `${client}-trace:${eventId}`;
|
|
978
|
-
}
|
|
979
|
-
function memoryViewerProjectionKey(memoraxCodeHome, client) {
|
|
980
|
-
if (client !== undefined && !isMemoryViewerClient(client)) {
|
|
981
|
-
return `${resolve(memoraxCodeHome)}\u0000client=unsupported`;
|
|
982
|
-
}
|
|
983
|
-
if (client === "claude")
|
|
984
|
-
return clientTracePaths("claude", memoraxCodeHome).sessionsRoot;
|
|
985
|
-
if (client === "dsh")
|
|
986
|
-
return clientTracePaths("dsh", memoraxCodeHome).sessionsRoot;
|
|
987
|
-
if (client === "opencode")
|
|
988
|
-
return clientTracePaths("opencode", memoraxCodeHome).sessionsRoot;
|
|
989
|
-
const codexSessionsRoot = tracePaths(memoraxCodeHome).sessionsRoot;
|
|
990
|
-
return client === "codex" ? `${codexSessionsRoot}\u0000client=codex` : codexSessionsRoot;
|
|
991
|
-
}
|