@memorax/memorax-code 0.1.7 → 0.1.9
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/codex/rollout-turn.js +52 -2
- 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,93 +0,0 @@
|
|
|
1
|
-
import { redactViewerPaths } from "../projection/redaction.js";
|
|
2
|
-
const MAX_SESSION_TITLE_LENGTH = 120;
|
|
3
|
-
const COMMON_RELATIVE_PATH_ROOTS = new Set([
|
|
4
|
-
"app",
|
|
5
|
-
"apps",
|
|
6
|
-
"assets",
|
|
7
|
-
"build",
|
|
8
|
-
"config",
|
|
9
|
-
"configs",
|
|
10
|
-
"crates",
|
|
11
|
-
"dist",
|
|
12
|
-
"docs",
|
|
13
|
-
"lib",
|
|
14
|
-
"node_modules",
|
|
15
|
-
"packages",
|
|
16
|
-
"private",
|
|
17
|
-
"public",
|
|
18
|
-
"resources",
|
|
19
|
-
"scripts",
|
|
20
|
-
"src",
|
|
21
|
-
"test",
|
|
22
|
-
"tests",
|
|
23
|
-
"vendor",
|
|
24
|
-
]);
|
|
25
|
-
export function earliestMemoryViewerSessionTitleCandidates(allEvents) {
|
|
26
|
-
const candidates = new Map();
|
|
27
|
-
for (const event of allEvents) {
|
|
28
|
-
if (event.type !== "turn_start" || !event.sessionId)
|
|
29
|
-
continue;
|
|
30
|
-
const title = normalizedMemoryViewerSessionTitle(event.prompt);
|
|
31
|
-
if (!title)
|
|
32
|
-
continue;
|
|
33
|
-
const candidate = {
|
|
34
|
-
id: event.id,
|
|
35
|
-
client: event.client,
|
|
36
|
-
timestamp: event.timestamp,
|
|
37
|
-
sessionId: event.sessionId,
|
|
38
|
-
title,
|
|
39
|
-
};
|
|
40
|
-
const key = `${event.client}\u0000${event.sessionId}`;
|
|
41
|
-
const current = candidates.get(key);
|
|
42
|
-
if (!current || compareSessionTitleCandidates(candidate, current) < 0) {
|
|
43
|
-
candidates.set(key, candidate);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
return [...candidates.values()];
|
|
47
|
-
}
|
|
48
|
-
export function normalizedMemoryViewerSessionTitle(value) {
|
|
49
|
-
if (typeof value !== "string")
|
|
50
|
-
return undefined;
|
|
51
|
-
const title = value
|
|
52
|
-
.replace(/[\u0000-\u001f\u007f-\u009f]+/g, " ")
|
|
53
|
-
.replace(/\s+/g, " ")
|
|
54
|
-
.trim();
|
|
55
|
-
if (!title)
|
|
56
|
-
return undefined;
|
|
57
|
-
if (/^(?:[a-z]:[\\/]|[~.]?[\\/]|file:\/\/)/i.test(title))
|
|
58
|
-
return undefined;
|
|
59
|
-
const redacted = redactSessionTitlePaths(title).replace(/\s+/g, " ").trim();
|
|
60
|
-
if (!redacted || redacted === "[REDACTED]")
|
|
61
|
-
return undefined;
|
|
62
|
-
const characters = Array.from(redacted);
|
|
63
|
-
return characters.length <= MAX_SESSION_TITLE_LENGTH
|
|
64
|
-
? redacted
|
|
65
|
-
: `${characters.slice(0, MAX_SESSION_TITLE_LENGTH - 1).join("")}…`;
|
|
66
|
-
}
|
|
67
|
-
function compareSessionTitleCandidates(left, right) {
|
|
68
|
-
const leftTime = Date.parse(left.timestamp);
|
|
69
|
-
const rightTime = Date.parse(right.timestamp);
|
|
70
|
-
const leftOrder = Number.isFinite(leftTime) ? leftTime : 0;
|
|
71
|
-
const rightOrder = Number.isFinite(rightTime) ? rightTime : 0;
|
|
72
|
-
return leftOrder - rightOrder || left.id.localeCompare(right.id);
|
|
73
|
-
}
|
|
74
|
-
function redactSessionTitlePaths(value) {
|
|
75
|
-
return redactViewerPaths(value)
|
|
76
|
-
.replace(/(^|[\s('"`])(?:~|\.\.?)?[\\/](?![\\/])[^\s)'"`]+/g, "$1[REDACTED]")
|
|
77
|
-
.replace(/(^|[\s('"`])\\\\[^\\\s]+\\[^\s)'"`]+/g, "$1[REDACTED]")
|
|
78
|
-
.replace(/(^|[\s('"`])([^\s)'"`]*[\\/][^\s)'"`]*)/g, (match, prefix, token) => (likelyPathToken(token) ? `${prefix}[REDACTED]` : match));
|
|
79
|
-
}
|
|
80
|
-
function likelyPathToken(token) {
|
|
81
|
-
if (/^[a-z][a-z0-9+.-]*:\/\//i.test(token))
|
|
82
|
-
return false;
|
|
83
|
-
if (/^[a-z]:[\\/]/i.test(token))
|
|
84
|
-
return true;
|
|
85
|
-
const segments = token.split(/[\\/]/).filter(Boolean);
|
|
86
|
-
if (segments.length >= 3)
|
|
87
|
-
return true;
|
|
88
|
-
if (segments.length < 2)
|
|
89
|
-
return false;
|
|
90
|
-
if (/\.[a-z0-9]{1,16}(?:[?#].*)?$/i.test(segments.at(-1) ?? ""))
|
|
91
|
-
return true;
|
|
92
|
-
return COMMON_RELATIVE_PATH_ROOTS.has(segments[0]?.toLowerCase() ?? "");
|
|
93
|
-
}
|
|
@@ -1,146 +0,0 @@
|
|
|
1
|
-
import { createHash } from "node:crypto";
|
|
2
|
-
import { MEMORAX_ICON_DATA_URL } from "../ui/icon.js";
|
|
3
|
-
import { isMemoryProjectId, UNCLASSIFIED_PROJECT_ID } from "../../memory/project.js";
|
|
4
|
-
import { MEMORAX_LOGO_DATA_URL } from "../ui/logo.js";
|
|
5
|
-
import { memoryViewerUserHtml } from "../ui/user-html.js";
|
|
6
|
-
import { projectMemoryViewerUserData } from "../projection/user.js";
|
|
7
|
-
import { listMemoryViewerDataWithHistory } from "../store.js";
|
|
8
|
-
import { repoMemoryReadinessForProject, } from "../../repository/readiness.js";
|
|
9
|
-
import { json } from "../../transport/http/json.js";
|
|
10
|
-
import { memoraxCodeHomeForTrace } from "../../trace/config.js";
|
|
11
|
-
const MEMORY_VIEWER_SESSION_ACTIVITY_WINDOW_MS = 72 * 60 * 60 * 1_000;
|
|
12
|
-
const MEMORY_VIEWER_ACTIVITY_CUTOFF_GRANULARITY_MS = 60_000;
|
|
13
|
-
const MEMORY_VIEWER_LOGO = Buffer.from(MEMORAX_LOGO_DATA_URL.slice(MEMORAX_LOGO_DATA_URL.indexOf(",") + 1), "base64");
|
|
14
|
-
const MEMORY_VIEWER_ICON = Buffer.from(MEMORAX_ICON_DATA_URL.slice(MEMORAX_ICON_DATA_URL.indexOf(",") + 1), "base64");
|
|
15
|
-
export async function handleMemoryViewerRequest(url, req, res, options = {}) {
|
|
16
|
-
if (req.method === "GET" && url.pathname === "/memory-viewer") {
|
|
17
|
-
if (!memoryViewerUserClient(url)) {
|
|
18
|
-
json(res, 400, { ok: false, error: "client must be codex, claude-code, dsh, or opencode" });
|
|
19
|
-
return true;
|
|
20
|
-
}
|
|
21
|
-
res.writeHead(200, {
|
|
22
|
-
"content-type": "text/html; charset=utf-8",
|
|
23
|
-
"cache-control": "no-store",
|
|
24
|
-
"content-security-policy": "default-src 'self'; script-src 'unsafe-inline'; style-src 'unsafe-inline'; connect-src 'self'; img-src 'self' data:; base-uri 'none'; frame-ancestors 'none'",
|
|
25
|
-
"referrer-policy": "no-referrer",
|
|
26
|
-
"x-content-type-options": "nosniff",
|
|
27
|
-
...(options.sessionCookie ? { "set-cookie": options.sessionCookie } : {}),
|
|
28
|
-
});
|
|
29
|
-
res.end(memoryViewerUserHtml());
|
|
30
|
-
return true;
|
|
31
|
-
}
|
|
32
|
-
if (req.method === "GET" && url.pathname === "/memory-viewer/logo") {
|
|
33
|
-
res.writeHead(200, {
|
|
34
|
-
"content-type": "image/png",
|
|
35
|
-
"cache-control": "private, max-age=86400",
|
|
36
|
-
"x-content-type-options": "nosniff",
|
|
37
|
-
});
|
|
38
|
-
res.end(MEMORY_VIEWER_LOGO);
|
|
39
|
-
return true;
|
|
40
|
-
}
|
|
41
|
-
if (req.method === "GET" && url.pathname === "/memory-viewer/favicon.png") {
|
|
42
|
-
res.writeHead(200, {
|
|
43
|
-
"content-type": "image/png",
|
|
44
|
-
"cache-control": "private, max-age=86400",
|
|
45
|
-
"x-content-type-options": "nosniff",
|
|
46
|
-
});
|
|
47
|
-
res.end(MEMORY_VIEWER_ICON);
|
|
48
|
-
return true;
|
|
49
|
-
}
|
|
50
|
-
if (req.method !== "GET" || url.pathname !== "/memory-viewer/api/summary") {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
const memoraxCodeHome = viewerMemoraxCodeHome(options);
|
|
54
|
-
const selectedClient = memoryViewerUserClient(url);
|
|
55
|
-
if (!selectedClient) {
|
|
56
|
-
json(res, 400, { ok: false, error: "client must be codex, claude-code, dsh, or opencode" });
|
|
57
|
-
return true;
|
|
58
|
-
}
|
|
59
|
-
const requestedProject = projectFilter(url);
|
|
60
|
-
const activeSessionSince = memoryViewerActivityCutoff(options.now?.() ?? Date.now());
|
|
61
|
-
const viewerData = await listMemoryViewerDataWithHistory(memoraxCodeHome, {
|
|
62
|
-
client: selectedClient.traceClient,
|
|
63
|
-
includePendingWritebacks: true,
|
|
64
|
-
activeSessionSince,
|
|
65
|
-
includeUserProjection: true,
|
|
66
|
-
...(requestedProject.projectId ? { projectId: requestedProject.projectId } : {}),
|
|
67
|
-
...(requestedProject.reject ? { rejectProjectFilter: true } : {}),
|
|
68
|
-
}, { claudeProjectsRoot: options.claudeProjectsRoot });
|
|
69
|
-
const projection = viewerData.userProjection
|
|
70
|
-
?? projectMemoryViewerUserData(viewerData.events);
|
|
71
|
-
const readiness = options.repoMemoryReadiness ?? repoMemoryReadinessForProject;
|
|
72
|
-
const projects = await Promise.all(activeMemoryViewerProjects(viewerData.activityProjects, viewerData.activityProjectSessions, activeSessionSince).map(async (project) => ({
|
|
73
|
-
projectId: project.projectId,
|
|
74
|
-
projectLabel: project.projectLabel,
|
|
75
|
-
repoMemory: await readiness(project.projectId, memoraxCodeHome),
|
|
76
|
-
})));
|
|
77
|
-
conditionalViewerJson(req, res, {
|
|
78
|
-
ok: true,
|
|
79
|
-
selectedClient: selectedClient.publicClient,
|
|
80
|
-
availableClients: ["codex", "claude-code", "dsh", "opencode"],
|
|
81
|
-
summary: projection.summary,
|
|
82
|
-
activities: projection.activities.slice(0, 100),
|
|
83
|
-
projects,
|
|
84
|
-
...(viewerData.selectedProjectId ? { selectedProjectId: viewerData.selectedProjectId } : {}),
|
|
85
|
-
...(viewerData.projectFilterStatus ? { projectFilterStatus: viewerData.projectFilterStatus } : {}),
|
|
86
|
-
});
|
|
87
|
-
return true;
|
|
88
|
-
}
|
|
89
|
-
function viewerMemoraxCodeHome(options) {
|
|
90
|
-
return options.memoraxCodeHome
|
|
91
|
-
?? (options.env?.MEMORAX_CODE_HOME?.trim() || memoraxCodeHomeForTrace(options.env));
|
|
92
|
-
}
|
|
93
|
-
function memoryViewerUserClient(url) {
|
|
94
|
-
const value = url.searchParams.get("client");
|
|
95
|
-
const traceClient = value === null ? "codex" : traceClientFromPublicValue(value.trim().toLowerCase());
|
|
96
|
-
return traceClient
|
|
97
|
-
? { traceClient, publicClient: traceClient === "claude" ? "claude-code" : traceClient }
|
|
98
|
-
: undefined;
|
|
99
|
-
}
|
|
100
|
-
function traceClientFromPublicValue(value) {
|
|
101
|
-
if (value === "codex" || value === "dsh" || value === "opencode")
|
|
102
|
-
return value;
|
|
103
|
-
return value === "claude" || value === "claude-code" || value === "cc"
|
|
104
|
-
? "claude"
|
|
105
|
-
: undefined;
|
|
106
|
-
}
|
|
107
|
-
function activeMemoryViewerProjects(projects, projectSessions, activeSessionSince) {
|
|
108
|
-
const activeProjectIds = new Set(projectSessions
|
|
109
|
-
.filter((entry) => {
|
|
110
|
-
const lastSeenAt = Date.parse(entry.lastSeenAt);
|
|
111
|
-
return Number.isFinite(lastSeenAt) && lastSeenAt >= activeSessionSince;
|
|
112
|
-
})
|
|
113
|
-
.map((entry) => entry.projectId));
|
|
114
|
-
return projects.filter((entry) => activeProjectIds.has(entry.projectId));
|
|
115
|
-
}
|
|
116
|
-
function memoryViewerActivityCutoff(now) {
|
|
117
|
-
const safeNow = Number.isFinite(now) ? now : Date.now();
|
|
118
|
-
return Math.floor(safeNow / MEMORY_VIEWER_ACTIVITY_CUTOFF_GRANULARITY_MS)
|
|
119
|
-
* MEMORY_VIEWER_ACTIVITY_CUTOFF_GRANULARITY_MS
|
|
120
|
-
- MEMORY_VIEWER_SESSION_ACTIVITY_WINDOW_MS;
|
|
121
|
-
}
|
|
122
|
-
function conditionalViewerJson(req, res, body) {
|
|
123
|
-
const payload = JSON.stringify(body);
|
|
124
|
-
const etag = `"${createHash("sha256").update(payload).digest("base64url")}"`;
|
|
125
|
-
const headers = {
|
|
126
|
-
"cache-control": "private, no-cache",
|
|
127
|
-
etag,
|
|
128
|
-
};
|
|
129
|
-
if (req.headers["if-none-match"] === etag) {
|
|
130
|
-
res.writeHead(304, headers);
|
|
131
|
-
res.end();
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
res.writeHead(200, { ...headers, "content-type": "application/json" });
|
|
135
|
-
res.end(payload);
|
|
136
|
-
}
|
|
137
|
-
function projectFilter(url) {
|
|
138
|
-
const rawProjectId = url.searchParams.get("projectId");
|
|
139
|
-
const projectId = (rawProjectId ?? "").trim();
|
|
140
|
-
if (projectId === UNCLASSIFIED_PROJECT_ID || isMemoryProjectId(projectId)) {
|
|
141
|
-
return { projectId };
|
|
142
|
-
}
|
|
143
|
-
if (rawProjectId !== null && projectId)
|
|
144
|
-
return { reject: true };
|
|
145
|
-
return {};
|
|
146
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,113 +0,0 @@
|
|
|
1
|
-
import { UNCLASSIFIED_PROJECT_ID } from "../../memory/project.js";
|
|
2
|
-
import { memoryViewerEventTurnIds } from "./turn-reference.js";
|
|
3
|
-
export function memoryViewerActivitySummary(allEvents) {
|
|
4
|
-
let recalledCount = 0;
|
|
5
|
-
let addCount = 0;
|
|
6
|
-
let searchCount = 0;
|
|
7
|
-
for (const event of allEvents) {
|
|
8
|
-
if (event.type === "memory_retrieve" || event.type === "memory_cli_search") {
|
|
9
|
-
searchCount += 1;
|
|
10
|
-
recalledCount += event.itemCount ?? event.results?.length ?? 0;
|
|
11
|
-
}
|
|
12
|
-
if (event.type === "memory_cli_add" || event.type === "memory_writeback")
|
|
13
|
-
addCount += 1;
|
|
14
|
-
}
|
|
15
|
-
return {
|
|
16
|
-
activityCount: allEvents.length,
|
|
17
|
-
recalledCount,
|
|
18
|
-
addCount,
|
|
19
|
-
searchCount,
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
export function memoryViewerActivityProjection(allEvents) {
|
|
23
|
-
const turns = new Map();
|
|
24
|
-
for (const event of allEvents) {
|
|
25
|
-
if (!event.sessionId)
|
|
26
|
-
continue;
|
|
27
|
-
const order = timestampOrder(event.timestamp);
|
|
28
|
-
for (const turnId of memoryViewerEventTurnIds(event)) {
|
|
29
|
-
const key = `${event.client}\u0000${event.sessionId}\u0000${turnId}`;
|
|
30
|
-
const current = turns.get(key);
|
|
31
|
-
if (!current) {
|
|
32
|
-
turns.set(key, {
|
|
33
|
-
firstOrder: order,
|
|
34
|
-
firstTimestamp: event.timestamp,
|
|
35
|
-
...(event.type === "turn_start" && Number.isFinite(order)
|
|
36
|
-
? { startOrder: order, startTimestamp: event.timestamp }
|
|
37
|
-
: {}),
|
|
38
|
-
});
|
|
39
|
-
continue;
|
|
40
|
-
}
|
|
41
|
-
if (Number.isFinite(order)
|
|
42
|
-
&& (!Number.isFinite(current.firstOrder) || order < current.firstOrder)) {
|
|
43
|
-
current.firstOrder = order;
|
|
44
|
-
current.firstTimestamp = event.timestamp;
|
|
45
|
-
}
|
|
46
|
-
if (event.type === "turn_start"
|
|
47
|
-
&& Number.isFinite(order)
|
|
48
|
-
&& (current.startOrder === undefined || order < current.startOrder)) {
|
|
49
|
-
current.startOrder = order;
|
|
50
|
-
current.startTimestamp = event.timestamp;
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const eventActivityOrder = new Map();
|
|
55
|
-
const entries = new Map();
|
|
56
|
-
for (const event of allEvents) {
|
|
57
|
-
let turn;
|
|
58
|
-
if (event.sessionId) {
|
|
59
|
-
for (const turnId of memoryViewerEventTurnIds(event)) {
|
|
60
|
-
const candidate = turns.get(`${event.client}\u0000${event.sessionId}\u0000${turnId}`);
|
|
61
|
-
if (!candidate)
|
|
62
|
-
continue;
|
|
63
|
-
const candidateOrder = candidate.startOrder ?? candidate.firstOrder;
|
|
64
|
-
const turnOrder = turn ? turn.startOrder ?? turn.firstOrder : Number.NEGATIVE_INFINITY;
|
|
65
|
-
if (!turn || candidateOrder > turnOrder)
|
|
66
|
-
turn = candidate;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const activityOrder = turn?.startOrder ?? turn?.firstOrder ?? timestampOrder(event.timestamp);
|
|
70
|
-
const activityTimestamp = turn?.startTimestamp ?? turn?.firstTimestamp ?? event.timestamp;
|
|
71
|
-
eventActivityOrder.set(event.eventKey, activityOrder);
|
|
72
|
-
const projectId = event.projectId ?? UNCLASSIFIED_PROJECT_ID;
|
|
73
|
-
const key = `${event.client}\u0000${projectId}\u0000${event.sessionId ?? ""}`;
|
|
74
|
-
const current = entries.get(key);
|
|
75
|
-
if (!current) {
|
|
76
|
-
entries.set(key, {
|
|
77
|
-
value: {
|
|
78
|
-
client: event.client,
|
|
79
|
-
projectId,
|
|
80
|
-
...(event.sessionId ? { sessionId: event.sessionId } : { unscoped: true }),
|
|
81
|
-
eventCount: 1,
|
|
82
|
-
lastSeenAt: activityTimestamp,
|
|
83
|
-
},
|
|
84
|
-
lastSeenOrder: activityOrder,
|
|
85
|
-
});
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
const lastSeenAt = activityOrder >= current.lastSeenOrder
|
|
89
|
-
? activityTimestamp
|
|
90
|
-
: current.value.lastSeenAt;
|
|
91
|
-
entries.set(key, {
|
|
92
|
-
value: {
|
|
93
|
-
...current.value,
|
|
94
|
-
eventCount: current.value.eventCount + 1,
|
|
95
|
-
lastSeenAt,
|
|
96
|
-
},
|
|
97
|
-
lastSeenOrder: Math.max(current.lastSeenOrder, activityOrder),
|
|
98
|
-
});
|
|
99
|
-
}
|
|
100
|
-
return {
|
|
101
|
-
eventActivityOrder,
|
|
102
|
-
projectSessions: [...entries.values()]
|
|
103
|
-
.map((entry) => entry.value)
|
|
104
|
-
.sort((left, right) => (left.projectId.localeCompare(right.projectId)
|
|
105
|
-
|| left.client.localeCompare(right.client)
|
|
106
|
-
|| timestampOrder(right.lastSeenAt) - timestampOrder(left.lastSeenAt)
|
|
107
|
-
|| (left.sessionId ?? "").localeCompare(right.sessionId ?? ""))),
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
function timestampOrder(value) {
|
|
111
|
-
const parsed = Date.parse(value);
|
|
112
|
-
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
|
113
|
-
}
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
export const MEMORY_VIEWER_DATA_PROJECTION_CAPACITY = 64;
|
|
2
|
-
const dataProjections = new Map();
|
|
3
|
-
const sharedDataProjections = new Map();
|
|
4
|
-
const projectionHistories = new Map();
|
|
5
|
-
export function prepareMemoryViewerDataProjectionHistory(projectionKey, historical) {
|
|
6
|
-
if (projectionHistories.get(projectionKey) === historical)
|
|
7
|
-
return;
|
|
8
|
-
dataProjections.delete(projectionKey);
|
|
9
|
-
sharedDataProjections.delete(projectionKey);
|
|
10
|
-
projectionHistories.set(projectionKey, historical);
|
|
11
|
-
}
|
|
12
|
-
export function shareMemoryViewerDataProjectionValue(projectionKey, valueKey, historical, liveVersion, create) {
|
|
13
|
-
let projections = sharedDataProjections.get(projectionKey);
|
|
14
|
-
if (!projections) {
|
|
15
|
-
projections = new Map();
|
|
16
|
-
sharedDataProjections.set(projectionKey, projections);
|
|
17
|
-
}
|
|
18
|
-
const cached = projections.get(valueKey);
|
|
19
|
-
if (cached && cached.historical === historical && cached.liveVersion === liveVersion)
|
|
20
|
-
return cached.value;
|
|
21
|
-
const value = create();
|
|
22
|
-
projections.set(valueKey, { historical, liveVersion, value });
|
|
23
|
-
return value;
|
|
24
|
-
}
|
|
25
|
-
export function readMemoryViewerDataProjection(projectionKey, filterKey, historical, liveVersion) {
|
|
26
|
-
const projections = dataProjections.get(projectionKey);
|
|
27
|
-
const cached = projections?.get(filterKey);
|
|
28
|
-
if (!cached || cached.historical !== historical || cached.liveVersion !== liveVersion)
|
|
29
|
-
return undefined;
|
|
30
|
-
projections?.delete(filterKey);
|
|
31
|
-
projections?.set(filterKey, cached);
|
|
32
|
-
return cached.data;
|
|
33
|
-
}
|
|
34
|
-
export function writeMemoryViewerDataProjection(projectionKey, filterKey, historical, liveVersion, data) {
|
|
35
|
-
let projections = dataProjections.get(projectionKey);
|
|
36
|
-
if (!projections) {
|
|
37
|
-
projections = new Map();
|
|
38
|
-
dataProjections.set(projectionKey, projections);
|
|
39
|
-
}
|
|
40
|
-
projections.set(filterKey, { historical, liveVersion, data });
|
|
41
|
-
while (projections.size > MEMORY_VIEWER_DATA_PROJECTION_CAPACITY) {
|
|
42
|
-
const oldestKey = projections.keys().next().value;
|
|
43
|
-
if (!oldestKey)
|
|
44
|
-
break;
|
|
45
|
-
projections.delete(oldestKey);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
export function clearMemoryViewerDataProjections() {
|
|
49
|
-
dataProjections.clear();
|
|
50
|
-
sharedDataProjections.clear();
|
|
51
|
-
}
|
|
52
|
-
export function clearMemoryViewerDataProjectionCache() {
|
|
53
|
-
dataProjections.clear();
|
|
54
|
-
sharedDataProjections.clear();
|
|
55
|
-
projectionHistories.clear();
|
|
56
|
-
}
|
|
57
|
-
export function memoryViewerDataProjectionCacheSize(projectionKey) {
|
|
58
|
-
return dataProjections.get(projectionKey)?.size ?? 0;
|
|
59
|
-
}
|
|
60
|
-
export function memoryViewerSharedDataProjectionCacheSize(projectionKey) {
|
|
61
|
-
return sharedDataProjections.get(projectionKey)?.size ?? 0;
|
|
62
|
-
}
|
|
63
|
-
export function shouldCacheMemoryViewerDataProjection(filter, data) {
|
|
64
|
-
if (filter.sessionId && !data.projectSessions.some((entry) => entry.sessionId === filter.sessionId))
|
|
65
|
-
return false;
|
|
66
|
-
if (filter.projectId && data.projectFilterStatus !== "resolved")
|
|
67
|
-
return false;
|
|
68
|
-
return true;
|
|
69
|
-
}
|
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
import { memoryViewerEventKey } from "./event-identity.js";
|
|
2
|
-
/**
|
|
3
|
-
* Marks a retained start without an end as interrupted only when a different
|
|
4
|
-
* Turn demonstrably started later in the same client session.
|
|
5
|
-
*
|
|
6
|
-
* This is a Viewer-only projection. It does not synthesize a trace event or
|
|
7
|
-
* change transcript and writeback authority.
|
|
8
|
-
*/
|
|
9
|
-
export function foldMemoryViewerSupersededTurnStarts(allEvents) {
|
|
10
|
-
const endedTurns = new Set();
|
|
11
|
-
const startsBySession = new Map();
|
|
12
|
-
for (const event of allEvents) {
|
|
13
|
-
if (!event.sessionId || !event.turnId)
|
|
14
|
-
continue;
|
|
15
|
-
const turnKey = viewerTurnKey(event);
|
|
16
|
-
if (event.type === "turn_end") {
|
|
17
|
-
endedTurns.add(turnKey);
|
|
18
|
-
continue;
|
|
19
|
-
}
|
|
20
|
-
if (event.type !== "turn_start")
|
|
21
|
-
continue;
|
|
22
|
-
const startedAt = Date.parse(event.timestamp);
|
|
23
|
-
if (!Number.isFinite(startedAt))
|
|
24
|
-
continue;
|
|
25
|
-
const sessionKey = JSON.stringify([event.client, event.sessionId]);
|
|
26
|
-
const starts = startsBySession.get(sessionKey) ?? new Map();
|
|
27
|
-
const current = starts.get(turnKey);
|
|
28
|
-
if (current === undefined || startedAt < current)
|
|
29
|
-
starts.set(turnKey, startedAt);
|
|
30
|
-
startsBySession.set(sessionKey, starts);
|
|
31
|
-
}
|
|
32
|
-
const interruptedTurns = new Set();
|
|
33
|
-
for (const starts of startsBySession.values()) {
|
|
34
|
-
const latestStartedAt = Math.max(...starts.values());
|
|
35
|
-
for (const [turnKey, startedAt] of starts) {
|
|
36
|
-
if (!endedTurns.has(turnKey) && startedAt < latestStartedAt) {
|
|
37
|
-
interruptedTurns.add(turnKey);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (interruptedTurns.size === 0)
|
|
42
|
-
return [...allEvents];
|
|
43
|
-
return allEvents.map((event) => (event.type === "turn_start"
|
|
44
|
-
&& !event.turnOutcome
|
|
45
|
-
&& interruptedTurns.has(viewerTurnKey(event))
|
|
46
|
-
? { ...event, turnOutcome: "interrupted" }
|
|
47
|
-
: event));
|
|
48
|
-
}
|
|
49
|
-
export function foldMemoryViewerTurnMaterializations(allEvents) {
|
|
50
|
-
const byOriginalEventKey = new Map();
|
|
51
|
-
for (const event of allEvents) {
|
|
52
|
-
if (event.type === "turn_materialized" && event.originalEventId) {
|
|
53
|
-
byOriginalEventKey.set(memoryViewerEventKey({
|
|
54
|
-
id: event.originalEventId,
|
|
55
|
-
client: event.client,
|
|
56
|
-
...(event.sessionId ? { sessionId: event.sessionId } : {}),
|
|
57
|
-
}), event);
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
return allEvents
|
|
61
|
-
.filter((event) => event.type !== "turn_materialized")
|
|
62
|
-
.map((event) => {
|
|
63
|
-
if (event.type !== "turn_end")
|
|
64
|
-
return event;
|
|
65
|
-
const materialized = byOriginalEventKey.get(event.eventKey);
|
|
66
|
-
if (!materialized)
|
|
67
|
-
return event;
|
|
68
|
-
const answer = materialized.answer ?? event.answer;
|
|
69
|
-
return {
|
|
70
|
-
...event,
|
|
71
|
-
...(materialized.turnOutcome ? { turnOutcome: materialized.turnOutcome } : {}),
|
|
72
|
-
...(answer ? { answer, content: answer } : {}),
|
|
73
|
-
...(materialized.details === undefined ? {} : { details: materialized.details }),
|
|
74
|
-
};
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
function viewerTurnKey(event) {
|
|
78
|
-
return JSON.stringify([event.client, event.sessionId, event.turnId]);
|
|
79
|
-
}
|
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
export function sanitizeMemoryViewerDetails(value, depth = 0) {
|
|
2
|
-
if (depth > 5)
|
|
3
|
-
return "[truncated]";
|
|
4
|
-
if (typeof value === "string")
|
|
5
|
-
return redactViewerPaths(value.slice(0, 12_000));
|
|
6
|
-
if (value === null || typeof value !== "object")
|
|
7
|
-
return value;
|
|
8
|
-
if (Array.isArray(value)) {
|
|
9
|
-
return value.slice(0, 50).map((item) => sanitizeMemoryViewerDetails(item, depth + 1));
|
|
10
|
-
}
|
|
11
|
-
const output = {};
|
|
12
|
-
for (const [key, item] of Object.entries(value)) {
|
|
13
|
-
if (/authorization|api[-_]?key|token|secret/i.test(key))
|
|
14
|
-
continue;
|
|
15
|
-
if (key.toLowerCase() === "route"
|
|
16
|
-
&& typeof item === "string"
|
|
17
|
-
&& /^\/api(?:\/[^/\s?#]+)*\/?$/.test(item)) {
|
|
18
|
-
output[key] = item;
|
|
19
|
-
continue;
|
|
20
|
-
}
|
|
21
|
-
if (viewerPathLikeKey(key)) {
|
|
22
|
-
output[key] = "[REDACTED]";
|
|
23
|
-
continue;
|
|
24
|
-
}
|
|
25
|
-
output[key] = sanitizeMemoryViewerDetails(item, depth + 1);
|
|
26
|
-
}
|
|
27
|
-
return output;
|
|
28
|
-
}
|
|
29
|
-
function viewerPathLikeKey(key) {
|
|
30
|
-
const normalized = key.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
31
|
-
return normalized === "cwd"
|
|
32
|
-
|| normalized === "path"
|
|
33
|
-
|| normalized.endsWith("path")
|
|
34
|
-
|| normalized === "root"
|
|
35
|
-
|| normalized.endsWith("root")
|
|
36
|
-
|| normalized === "workspace"
|
|
37
|
-
|| normalized === "workingdirectory";
|
|
38
|
-
}
|
|
39
|
-
export function redactViewerPaths(value) {
|
|
40
|
-
return value
|
|
41
|
-
.split(/(\bhttps?:\/\/[^\s)'"`]+)/gi)
|
|
42
|
-
.map((segment) => {
|
|
43
|
-
if (/^https?:\/\//i.test(segment))
|
|
44
|
-
return segment;
|
|
45
|
-
return segment
|
|
46
|
-
.replace(/\bfile:\/\/[^\s)'"`]+/gi, "[REDACTED]")
|
|
47
|
-
.replace(/(^|[\s('"`])\\\\[^\\/\s)'"`]+[\\/][^\s)'"`]+/g, "$1[REDACTED]")
|
|
48
|
-
.replace(/\b[A-Za-z]:[\\/][^\s)'"`]+/g, "[REDACTED]")
|
|
49
|
-
.replace(/(^|[\s('"`])\/(?!\/)[^\s)'"`]+/g, "$1[REDACTED]");
|
|
50
|
-
})
|
|
51
|
-
.join("");
|
|
52
|
-
}
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
export function memoryViewerEventTurnIds(event) {
|
|
2
|
-
return [...new Set([
|
|
3
|
-
safeTurnId(event.turnId),
|
|
4
|
-
...(event.turnReferences ?? []).map((reference) => safeTurnId(reference.turnId)),
|
|
5
|
-
].filter(Boolean))];
|
|
6
|
-
}
|
|
7
|
-
function safeTurnId(value) {
|
|
8
|
-
const candidate = typeof value === "string" ? value.trim() : "";
|
|
9
|
-
if (!candidate || candidate.length > 512 || /[\u0000-\u001f\u007f\\/]/.test(candidate))
|
|
10
|
-
return "";
|
|
11
|
-
return candidate;
|
|
12
|
-
}
|