@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,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
- }