@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
package/README.md
CHANGED
|
@@ -14,14 +14,45 @@ Harness, and OpenCode.
|
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
npm install -g @memorax/memorax-code
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
### Connect a MemoraX Account (Recommended)
|
|
20
|
+
|
|
21
|
+
[Create a MemoraX account](https://platform.memorax.net/) or use an existing
|
|
22
|
+
one, then run:
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
memorax-code setup --existing-account
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
> Using MemoraX Code across devices? Find the MemoraX username and API key
|
|
29
|
+
> needed by setup in the MemoraX Code configuration file on a configured device
|
|
30
|
+
> (normally `~/.memorax-code/config.toml`), then enter them locally during setup
|
|
31
|
+
> on another device. This file contains your API key—keep it private and never
|
|
32
|
+
> paste it into chats or public issues.
|
|
33
|
+
|
|
34
|
+
### Or Try Without an Account (90-Day Guest Mode)
|
|
35
|
+
|
|
36
|
+
To start immediately and connect an account later, run:
|
|
37
|
+
|
|
38
|
+
```bash
|
|
17
39
|
memorax-code setup
|
|
18
40
|
```
|
|
19
41
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
42
|
+
To activate your guest account, first run this command directly in your local
|
|
43
|
+
terminal:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
memorax-code account --show-mark-id
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
After obtaining the Mark ID, create your MemoraX account. The platform does
|
|
50
|
+
not currently support attaching a Mark ID to an account that has already been
|
|
51
|
+
registered.
|
|
52
|
+
|
|
53
|
+
Both setup paths automatically detect supported coding agents. Later setup
|
|
54
|
+
runs reuse a complete saved configuration; use
|
|
55
|
+
`memorax-code setup --reconfigure` to replace it.
|
|
25
56
|
|
|
26
57
|
After the first installation, restart or refresh the detected coding agents
|
|
27
58
|
before opening a new session. In Codex, enable **MemoraX Code Codex Adapter**
|
|
@@ -260,9 +260,6 @@ printPostinstallSummary(
|
|
|
260
260
|
backendAndAdaptersStatus,
|
|
261
261
|
backendAndAdapters.dshAdapterUnavailable,
|
|
262
262
|
);
|
|
263
|
-
if (backendAndAdaptersStatus === "enabled") {
|
|
264
|
-
log("View local memory activity: http://127.0.0.1:8787/memory-viewer");
|
|
265
|
-
}
|
|
266
263
|
if (backendAndAdaptersStatus !== "enabled") process.exit(1);
|
|
267
264
|
if (!updateMode && readMemoraxInstallStatus()?.configured !== true) {
|
|
268
265
|
logRed("Setup could not verify a ready MemoraX connection after Backend reconciliation.");
|
package/docs/configuration.md
CHANGED
|
@@ -354,9 +354,7 @@ export, or public collector.
|
|
|
354
354
|
|
|
355
355
|
DSH trace contains only normalized lifecycle and memory-operation events. Its
|
|
356
356
|
native Session Event Log and raw events remain local to DSH; MemoraX Code does
|
|
357
|
-
not copy that log into trace.
|
|
358
|
-
normalized operational events and retained trace, and never reads the native
|
|
359
|
-
Session Event Log.
|
|
357
|
+
not copy that log into trace.
|
|
360
358
|
|
|
361
359
|
## Backend runtime settings
|
|
362
360
|
|
package/docs/troubleshooting.md
CHANGED
|
@@ -135,19 +135,20 @@ again at 0%; raw quota counts are not shown.
|
|
|
135
135
|
Automatic quota reminders are currently supported in Codex, Claude Code, and
|
|
136
136
|
OpenCode. DeepSeek Harness does not currently surface these reminders.
|
|
137
137
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
138
|
+
A guest reminder displays the complete Mark ID when the ready local trial
|
|
139
|
+
identity matches the active API key. Registered-account reminders do not
|
|
140
|
+
include it. If a guest reminder cannot verify a matching local identity, it
|
|
141
|
+
instead directs you to run this command yourself in a local terminal:
|
|
141
142
|
|
|
142
143
|
```sh
|
|
143
144
|
memorax-code account --show-mark-id
|
|
144
145
|
```
|
|
145
146
|
|
|
146
|
-
The command reads a ready local trial identity and prints only its Mark ID.
|
|
147
|
-
|
|
148
|
-
or log. If no ready local trial identity exists,
|
|
149
|
-
connection copied from another computer does not
|
|
150
|
-
device-local Mark ID.
|
|
147
|
+
The command reads a ready local trial identity and prints only its Mark ID. A
|
|
148
|
+
reminder or command output containing the Mark ID is sensitive; do not share it
|
|
149
|
+
in a conversation, screenshot, or log. If no ready local trial identity exists,
|
|
150
|
+
use `memorax-code setup`; a connection copied from another computer does not
|
|
151
|
+
include that computer's device-local Mark ID.
|
|
151
152
|
|
|
152
153
|
## Backend does not start
|
|
153
154
|
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
import { createServer, } from "node:http";
|
|
2
2
|
import { backendDebug } from "../shared/debug-log.js";
|
|
3
3
|
import { createMemoryReminderTraceRecorder } from "../memory/reminder-trace-recorder.js";
|
|
4
|
-
import { handleMemoryViewerRequest } from "../viewer/http/public-routes.js";
|
|
5
4
|
import { handleMemoryHookRequest } from "../transport/http/memory-hook.js";
|
|
6
5
|
import { createBackendMemoryObservability } from "./memory-observability.js";
|
|
7
6
|
import { createMemoryService } from "../memory/service.js";
|
|
8
7
|
import { handleHealthRequest } from "../transport/http/health.js";
|
|
9
|
-
import { authorized,
|
|
8
|
+
import { authorized, publicErrorMessage, statusCodeFromError } from "../transport/http/request.js";
|
|
10
9
|
import { json } from "../transport/http/json.js";
|
|
11
10
|
import { createBackendState } from "./state.js";
|
|
12
11
|
import { TRACE_CLIENTS } from "../trace/config.js";
|
|
13
12
|
import { pruneExpiredTraceSessionsForClient } from "../trace/store.js";
|
|
14
|
-
import { startMemoryWritebackReconciler } from "../memory/writeback-reconciler.js";
|
|
15
|
-
import { createMemoryWritebackTaskProjection } from "../memory/writeback-task-projection.js";
|
|
16
|
-
import { readActiveManagedClients } from "../lifecycle/active-clients.js";
|
|
17
13
|
const DEFAULT_BACKEND_SHUTDOWN_TIMEOUT_MS = 4_000;
|
|
18
14
|
export function createBackendServer(state = createBackendState(), options = {}) {
|
|
19
15
|
const memoryEnv = { ...process.env, MEMORAX_CODE_HOME: state.sessionHome };
|
|
20
|
-
const
|
|
21
|
-
memoraxCodeHome: state.sessionHome,
|
|
22
|
-
client,
|
|
23
|
-
}));
|
|
24
|
-
const memoryObservability = createBackendMemoryObservability(state.sessionHome, options.memoryObservability, memoryEnv, memoryWritebackTaskProjections.map((projection) => projection.observabilityHook));
|
|
16
|
+
const memoryObservability = createBackendMemoryObservability(state.sessionHome, options.memoryObservability, memoryEnv);
|
|
25
17
|
const memoryService = createMemoryService({
|
|
26
18
|
diagnosticLogger: backendDebug,
|
|
27
19
|
env: memoryEnv,
|
|
@@ -46,11 +38,6 @@ export function createBackendServer(state = createBackendState(), options = {})
|
|
|
46
38
|
});
|
|
47
39
|
});
|
|
48
40
|
}
|
|
49
|
-
const memoryWritebackReconcilers = memoryWritebackTaskProjections.map((taskProjection) => (startMemoryWritebackReconciler({
|
|
50
|
-
memoraxCodeHome: state.sessionHome,
|
|
51
|
-
env: memoryEnv,
|
|
52
|
-
taskProjection,
|
|
53
|
-
})));
|
|
54
41
|
const handleRequest = async (req, res) => {
|
|
55
42
|
const url = new URL(req.url ?? "/", `http://${req.headers.host ?? "127.0.0.1"}`);
|
|
56
43
|
backendDebug("http.request", {
|
|
@@ -62,17 +49,6 @@ export function createBackendServer(state = createBackendState(), options = {})
|
|
|
62
49
|
return handleHealthRequest(state, res);
|
|
63
50
|
if (!authorized(state, req, url))
|
|
64
51
|
return json(res, 401, { ok: false, error: "unauthorized" });
|
|
65
|
-
if (url.pathname === "/memory-viewer"
|
|
66
|
-
|| url.pathname.startsWith("/memory-viewer/")) {
|
|
67
|
-
if (await handleMemoryViewerRequest(url, req, res, {
|
|
68
|
-
memoraxCodeHome: state.sessionHome,
|
|
69
|
-
claudeProjectsRoot: readActiveManagedClients(state.sessionHome)?.claude === true
|
|
70
|
-
? state.claudeProjectsRoot
|
|
71
|
-
: false,
|
|
72
|
-
sessionCookie: memoryViewerSessionCookieHeader(state, req, url),
|
|
73
|
-
}))
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
52
|
if (await handleMemoryHookRequest(memoryHookDependencies, url, req, res))
|
|
77
53
|
return;
|
|
78
54
|
return json(res, 404, { ok: false, error: "not found" });
|
|
@@ -98,7 +74,6 @@ export function createBackendServer(state = createBackendState(), options = {})
|
|
|
98
74
|
activeRequests,
|
|
99
75
|
memoryObservability,
|
|
100
76
|
memoryService,
|
|
101
|
-
memoryWritebackReconcilers,
|
|
102
77
|
timeoutMs: positiveShutdownTimeout(options.shutdownTimeoutMs),
|
|
103
78
|
});
|
|
104
79
|
}
|
|
@@ -118,7 +93,6 @@ function attachBackendShutdown(server, resources) {
|
|
|
118
93
|
}
|
|
119
94
|
async function shutdownBackend(server, closeHttpServer, resources) {
|
|
120
95
|
const deadline = Date.now() + resources.timeoutMs;
|
|
121
|
-
const reconcilerDrain = settleShutdownWork("memory_writeback_reconcilers", Promise.all(resources.memoryWritebackReconcilers.map((reconciler) => reconciler.close())));
|
|
122
96
|
try {
|
|
123
97
|
await waitForShutdownPhase("http_server", closeBackendHttpServer(server, closeHttpServer, resources.timeoutMs), deadline);
|
|
124
98
|
await waitForShutdownPhase("http_requests", waitForActiveRequests(resources.activeRequests), deadline);
|
|
@@ -128,7 +102,6 @@ async function shutdownBackend(server, closeHttpServer, resources) {
|
|
|
128
102
|
if (Date.now() < deadline && resources.memoryObservability?.drain) {
|
|
129
103
|
await waitForShutdownPhase("memory_observability", settleShutdownWork("memory_observability", resources.memoryObservability.drain()), deadline);
|
|
130
104
|
}
|
|
131
|
-
await waitForShutdownPhase("background_work", reconcilerDrain, deadline);
|
|
132
105
|
}
|
|
133
106
|
finally {
|
|
134
107
|
resources.memoryService.close();
|
|
@@ -2,14 +2,11 @@ import { randomUUID } from "node:crypto";
|
|
|
2
2
|
import { backendDebug } from "../shared/debug-log.js";
|
|
3
3
|
import { TRACE_CLIENTS, clientTraceConfigFromEnv, } from "../trace/config.js";
|
|
4
4
|
import { recordTraceEvent } from "../trace/store.js";
|
|
5
|
-
|
|
6
|
-
export function createBackendMemoryObservability(memoraxCodeHome, existingHook, env = process.env, additionalDefaultHooks = []) {
|
|
5
|
+
export function createBackendMemoryObservability(memoraxCodeHome, existingHook, env = process.env) {
|
|
7
6
|
if (existingHook)
|
|
8
7
|
return existingHook;
|
|
9
8
|
const effectiveEnv = { ...env, MEMORAX_CODE_HOME: memoraxCodeHome };
|
|
10
9
|
return composeMemoryObservabilityHooks([
|
|
11
|
-
...additionalDefaultHooks,
|
|
12
|
-
memoryViewerObservabilityHook(),
|
|
13
10
|
sessionTraceObservabilityHook(memoraxCodeHome, effectiveEnv),
|
|
14
11
|
]);
|
|
15
12
|
}
|
|
@@ -1,11 +1,9 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
|
-
import { join
|
|
2
|
+
import { join } from "node:path";
|
|
3
3
|
import { backendEnv } from "../config/backend-env.js";
|
|
4
4
|
export function createBackendState(host = "127.0.0.1", options = {}) {
|
|
5
5
|
const authToken = options.authToken ?? backendEnv("TOKEN");
|
|
6
6
|
const sessionHome = options.sessionHome ?? process.env.MEMORAX_CODE_HOME ?? join(homedir(), ".memorax-code");
|
|
7
|
-
const claudeProjectsRoot = options.claudeProjectsRoot
|
|
8
|
-
?? memoryViewerClaudeProjectsRootFromEnv(process.env);
|
|
9
7
|
const security = {
|
|
10
8
|
...envBackendSecurity(),
|
|
11
9
|
...options.security,
|
|
@@ -13,19 +11,10 @@ export function createBackendState(host = "127.0.0.1", options = {}) {
|
|
|
13
11
|
validateBackendSecurity(host, authToken, security);
|
|
14
12
|
return {
|
|
15
13
|
sessionHome,
|
|
16
|
-
claudeProjectsRoot,
|
|
17
14
|
authToken,
|
|
18
15
|
security,
|
|
19
16
|
};
|
|
20
17
|
}
|
|
21
|
-
export function memoryViewerClaudeProjectsRootFromEnv(env) {
|
|
22
|
-
const configured = env.MEMORAX_CODE_MEMORY_VIEWER_CLAUDE_PROJECTS_ROOT?.trim();
|
|
23
|
-
if (!configured)
|
|
24
|
-
return undefined;
|
|
25
|
-
if (configured === "disabled")
|
|
26
|
-
return false;
|
|
27
|
-
return resolve(configured);
|
|
28
|
-
}
|
|
29
18
|
function envBackendSecurity() {
|
|
30
19
|
const mode = backendEnv("MODE") === "server" ? "server" : "local";
|
|
31
20
|
return {
|
|
@@ -137,53 +137,6 @@ export function claudeInterruptedTranscriptTurnFromJsonLines(transcript, input)
|
|
|
137
137
|
}
|
|
138
138
|
return { ok: false, reason: "turn_not_interrupted" };
|
|
139
139
|
}
|
|
140
|
-
export function claudeTranscriptMemoryActivitiesFromJsonLines(transcript, input) {
|
|
141
|
-
const requested = requestedTranscriptRecords(transcript, input.sessionId);
|
|
142
|
-
if (!requested.ok)
|
|
143
|
-
return [];
|
|
144
|
-
const records = requested.records;
|
|
145
|
-
const recordsByUuid = indexRecordsByUuid(records);
|
|
146
|
-
const terminals = new Map();
|
|
147
|
-
const interrupted = new Map();
|
|
148
|
-
for (const record of records) {
|
|
149
|
-
if (isMemoryActivityTerminalAssistantRecord(record)) {
|
|
150
|
-
const branch = promptBranch(record, recordsByUuid);
|
|
151
|
-
if (!branch.ambiguous && branch.promptId && branch.userPrompt) {
|
|
152
|
-
const candidates = terminals.get(branch.promptId) ?? [];
|
|
153
|
-
candidates.push(branch);
|
|
154
|
-
terminals.set(branch.promptId, candidates);
|
|
155
|
-
}
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
if (!interruptionMarker(record))
|
|
159
|
-
continue;
|
|
160
|
-
const branch = promptBranch(record, recordsByUuid);
|
|
161
|
-
if (branch.ambiguous
|
|
162
|
-
|| !branch.promptId
|
|
163
|
-
|| !branch.userPrompt
|
|
164
|
-
|| branch.assistantMessages.some((message) => message.stop_reason === "end_turn")) {
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
const candidates = interrupted.get(branch.promptId) ?? [];
|
|
168
|
-
candidates.push(branch);
|
|
169
|
-
interrupted.set(branch.promptId, candidates);
|
|
170
|
-
}
|
|
171
|
-
const activities = [];
|
|
172
|
-
const promptIds = new Set([...terminals.keys(), ...interrupted.keys()]);
|
|
173
|
-
for (const promptId of promptIds) {
|
|
174
|
-
const terminalBranches = maximalTerminalLineages((terminals.get(promptId) ?? []).map((branch) => ({ branch }))).map((candidate) => candidate.branch);
|
|
175
|
-
const interruptedBranches = interrupted.get(promptId) ?? [];
|
|
176
|
-
const branch = terminalBranches.length === 1
|
|
177
|
-
? terminalBranches[0]
|
|
178
|
-
: terminalBranches.length === 0 && interruptedBranches.length === 1
|
|
179
|
-
? interruptedBranches[0]
|
|
180
|
-
: undefined;
|
|
181
|
-
if (!branch)
|
|
182
|
-
continue;
|
|
183
|
-
activities.push(...claudeMemoryActivityDetails(promptId, branch, input.includeAuthority === true));
|
|
184
|
-
}
|
|
185
|
-
return activities;
|
|
186
|
-
}
|
|
187
140
|
function requestedTranscriptRecords(transcript, sessionId) {
|
|
188
141
|
const parsed = transcriptRecords(transcript);
|
|
189
142
|
if (!parsed.ok) {
|
|
@@ -383,86 +336,6 @@ function transcriptRecordTimestamp(record) {
|
|
|
383
336
|
const timestamp = stringValue(record.timestamp);
|
|
384
337
|
return timestamp && Number.isFinite(Date.parse(timestamp)) ? timestamp : undefined;
|
|
385
338
|
}
|
|
386
|
-
function claudeMemoryActivityDetails(promptId, branch, includeAuthority) {
|
|
387
|
-
const activities = [];
|
|
388
|
-
const terminalRecordId = includeAuthority
|
|
389
|
-
? stringValue(branch.records[0]?.uuid)
|
|
390
|
-
: undefined;
|
|
391
|
-
const seenToolUseIds = new Set();
|
|
392
|
-
let index = 1;
|
|
393
|
-
for (const record of branch.records.slice().reverse()) {
|
|
394
|
-
const message = assistantMessageFromRecord(record);
|
|
395
|
-
if (!message || !Array.isArray(message.content))
|
|
396
|
-
continue;
|
|
397
|
-
for (const block of message.content) {
|
|
398
|
-
if (!isRecord(block) || block.type !== "tool_use")
|
|
399
|
-
continue;
|
|
400
|
-
const toolUseId = stringValue(block.id);
|
|
401
|
-
if (!toolUseId || seenToolUseIds.has(toolUseId))
|
|
402
|
-
continue;
|
|
403
|
-
seenToolUseIds.add(toolUseId);
|
|
404
|
-
if (block.name !== "Bash")
|
|
405
|
-
continue;
|
|
406
|
-
const toolInput = isRecord(block.input) ? block.input : undefined;
|
|
407
|
-
const command = toolInput ? stringValue(toolInput.command) : undefined;
|
|
408
|
-
if (!command)
|
|
409
|
-
continue;
|
|
410
|
-
const types = memoryCliCommandTypes(command);
|
|
411
|
-
if (types.length === 0)
|
|
412
|
-
continue;
|
|
413
|
-
const timestamp = canonicalTranscriptActivityTimestamp(record);
|
|
414
|
-
const result = claudeToolResultDetails(branch.records, toolUseId, types.length === 1 && types[0] === "memory_cli_search");
|
|
415
|
-
for (const [occurrence, type] of types.entries()) {
|
|
416
|
-
activities.push({
|
|
417
|
-
promptId,
|
|
418
|
-
index,
|
|
419
|
-
occurrence: occurrence + 1,
|
|
420
|
-
type,
|
|
421
|
-
toolUseId,
|
|
422
|
-
...(terminalRecordId ? { terminalRecordId } : {}),
|
|
423
|
-
...(timestamp ? { timestamp } : {}),
|
|
424
|
-
...(result.ok === undefined ? {} : { ok: result.ok }),
|
|
425
|
-
...(type === "memory_cli_search" && result.itemCount !== undefined
|
|
426
|
-
? { itemCount: result.itemCount }
|
|
427
|
-
: {}),
|
|
428
|
-
});
|
|
429
|
-
}
|
|
430
|
-
index += 1;
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
return activities;
|
|
434
|
-
}
|
|
435
|
-
function canonicalTranscriptActivityTimestamp(record) {
|
|
436
|
-
const timestamp = transcriptRecordTimestamp(record);
|
|
437
|
-
return timestamp ? new Date(Date.parse(timestamp)).toISOString() : undefined;
|
|
438
|
-
}
|
|
439
|
-
function claudeToolResultDetails(branchRecords, toolUseId, deriveSearchItemCount) {
|
|
440
|
-
const matches = [];
|
|
441
|
-
let failed = false;
|
|
442
|
-
for (const record of branchRecords) {
|
|
443
|
-
const message = isRecord(record.message) ? record.message : undefined;
|
|
444
|
-
if (message?.role !== "user" || !Array.isArray(message.content))
|
|
445
|
-
continue;
|
|
446
|
-
for (const block of message.content) {
|
|
447
|
-
if (!isRecord(block)
|
|
448
|
-
|| block.type !== "tool_result"
|
|
449
|
-
|| stringValue(block.tool_use_id ?? block.toolUseId) !== toolUseId) {
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
matches.push(block);
|
|
453
|
-
if (block.is_error === true)
|
|
454
|
-
failed = true;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
457
|
-
if (matches.length === 0)
|
|
458
|
-
return {};
|
|
459
|
-
if (failed)
|
|
460
|
-
return { ok: false };
|
|
461
|
-
if (!deriveSearchItemCount || matches.length !== 1)
|
|
462
|
-
return { ok: true };
|
|
463
|
-
const itemCount = safeMemorySearchItemCount(matches[0]?.content);
|
|
464
|
-
return itemCount === undefined ? { ok: true } : { ok: true, itemCount };
|
|
465
|
-
}
|
|
466
339
|
function memoryCliCommandTypes(command) {
|
|
467
340
|
const activities = [];
|
|
468
341
|
for (const segment of shellCommandSegments(command)) {
|
|
@@ -623,54 +496,6 @@ function isNamedExecutable(word, name) {
|
|
|
623
496
|
const basename = normalized.slice(normalized.lastIndexOf("/") + 1);
|
|
624
497
|
return basename === name || basename === `${name}.mjs`;
|
|
625
498
|
}
|
|
626
|
-
function safeMemorySearchItemCount(content) {
|
|
627
|
-
if (typeof content !== "string" || content.length > 2 * 1024 * 1024)
|
|
628
|
-
return undefined;
|
|
629
|
-
const output = content.trim();
|
|
630
|
-
if (!output)
|
|
631
|
-
return undefined;
|
|
632
|
-
if (output.startsWith("{")) {
|
|
633
|
-
try {
|
|
634
|
-
const parsed = JSON.parse(output);
|
|
635
|
-
if (!isRecord(parsed)
|
|
636
|
-
|| parsed.ok !== true
|
|
637
|
-
|| parsed.action !== "memory.search"
|
|
638
|
-
|| !Array.isArray(parsed.items)
|
|
639
|
-
|| parsed.items.length > 100) {
|
|
640
|
-
return undefined;
|
|
641
|
-
}
|
|
642
|
-
return parsed.items.length;
|
|
643
|
-
}
|
|
644
|
-
catch {
|
|
645
|
-
return undefined;
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
const lines = output.split(/\r?\n/u);
|
|
649
|
-
if (lines[0] !== "<memories>" || lines.at(-1) !== "</memories>")
|
|
650
|
-
return undefined;
|
|
651
|
-
let insideFacts = false;
|
|
652
|
-
let factsGroups = 0;
|
|
653
|
-
let itemCount = 0;
|
|
654
|
-
for (const line of lines.slice(1, -1)) {
|
|
655
|
-
if (!insideFacts && /^ {2}<facts(?: memory_type="[^"\r\n]*")?>$/u.test(line)) {
|
|
656
|
-
insideFacts = true;
|
|
657
|
-
factsGroups += 1;
|
|
658
|
-
continue;
|
|
659
|
-
}
|
|
660
|
-
if (insideFacts && line === " </facts>") {
|
|
661
|
-
insideFacts = false;
|
|
662
|
-
continue;
|
|
663
|
-
}
|
|
664
|
-
if (insideFacts && /^ {3}-(?:\[[^\]\r\n]+\] | )\S.*$/u.test(line)) {
|
|
665
|
-
itemCount += 1;
|
|
666
|
-
if (itemCount > 100)
|
|
667
|
-
return undefined;
|
|
668
|
-
continue;
|
|
669
|
-
}
|
|
670
|
-
return undefined;
|
|
671
|
-
}
|
|
672
|
-
return !insideFacts && factsGroups > 0 && itemCount > 0 ? itemCount : undefined;
|
|
673
|
-
}
|
|
674
499
|
function claudeMemoryActivities(assistantMessages) {
|
|
675
500
|
const activities = [];
|
|
676
501
|
const seenToolUseIds = new Set();
|
|
@@ -820,13 +645,6 @@ function completedAssistantReply(record) {
|
|
|
820
645
|
function isCompletedAssistantRecord(record) {
|
|
821
646
|
return assistantMessageFromRecord(record)?.stop_reason === "end_turn";
|
|
822
647
|
}
|
|
823
|
-
function isMemoryActivityTerminalAssistantRecord(record) {
|
|
824
|
-
const message = assistantMessageFromRecord(record);
|
|
825
|
-
const stopReason = message ? stringValue(message.stop_reason) : undefined;
|
|
826
|
-
return Boolean(message && ((stopReason && stopReason !== "tool_use")
|
|
827
|
-
|| record.isApiErrorMessage === true
|
|
828
|
-
|| record.error));
|
|
829
|
-
}
|
|
830
648
|
function assistantMessageFromRecord(record) {
|
|
831
649
|
if (record.type !== "assistant"
|
|
832
650
|
|| record.isSidechain === true
|
|
@@ -10,7 +10,7 @@ export function openCodeMessageTurn(messages, input) {
|
|
|
10
10
|
}
|
|
11
11
|
if (stringField(user.info, "sessionID") !== input.sessionId
|
|
12
12
|
|| stringField(assistant.info, "sessionID") !== input.sessionId
|
|
13
|
-
||
|
|
13
|
+
|| !assistantBelongsToTurn(messages, input, assistant)) {
|
|
14
14
|
return { ok: false, reason: "message_identity_mismatch" };
|
|
15
15
|
}
|
|
16
16
|
const assistantTime = isRecord(assistant.info.time) ? assistant.info.time : undefined;
|
|
@@ -18,7 +18,7 @@ export function openCodeMessageTurn(messages, input) {
|
|
|
18
18
|
return { ok: false, reason: "assistant_not_completed" };
|
|
19
19
|
}
|
|
20
20
|
const assistantError = assistant.info.error;
|
|
21
|
-
const interrupted = isRecord(assistantError) && assistantError
|
|
21
|
+
const interrupted = isRecord(assistantError) && stringField(assistantError, "name") !== undefined;
|
|
22
22
|
if (assistantError !== undefined && assistantError !== null && !interrupted) {
|
|
23
23
|
return { ok: false, reason: "assistant_error" };
|
|
24
24
|
}
|
|
@@ -46,6 +46,52 @@ export function openCodeMessageTurn(messages, input) {
|
|
|
46
46
|
},
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
+
function assistantBelongsToTurn(messages, input, assistant) {
|
|
50
|
+
return stringField(assistant.info, "parentID") === terminalUserMessageFor(messages, input);
|
|
51
|
+
}
|
|
52
|
+
function terminalUserMessageFor(messages, input) {
|
|
53
|
+
const sessionMessages = messages.filter((message) => (isMessageRecord(message)
|
|
54
|
+
&& stringField(message.info, "sessionID") === input.sessionId
|
|
55
|
+
&& messageId(message) !== undefined));
|
|
56
|
+
const startIndex = sessionMessages.findIndex((message) => (message.info.role === "user" && messageId(message) === input.userMessageId));
|
|
57
|
+
if (startIndex < 0)
|
|
58
|
+
return undefined;
|
|
59
|
+
const lineageAssistantIds = new Set();
|
|
60
|
+
let terminalUserMessageId = input.userMessageId;
|
|
61
|
+
let awaitingContinuation = false;
|
|
62
|
+
for (const message of sessionMessages.slice(startIndex + 1)) {
|
|
63
|
+
if (message.info.role === "assistant") {
|
|
64
|
+
if (stringField(message.info, "parentID") === terminalUserMessageId) {
|
|
65
|
+
const id = messageId(message);
|
|
66
|
+
if (id)
|
|
67
|
+
lineageAssistantIds.add(id);
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (message.info.role !== "user")
|
|
72
|
+
continue;
|
|
73
|
+
if (hasCompactionPart(message.parts)) {
|
|
74
|
+
const compactionTailId = compactionTailStartId(message, input.sessionId);
|
|
75
|
+
if (!compactionTailId || !lineageAssistantIds.has(compactionTailId) || awaitingContinuation) {
|
|
76
|
+
return undefined;
|
|
77
|
+
}
|
|
78
|
+
awaitingContinuation = true;
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
if (isCompactionContinuation(message, input.sessionId)) {
|
|
82
|
+
if (!awaitingContinuation)
|
|
83
|
+
return undefined;
|
|
84
|
+
const id = messageId(message);
|
|
85
|
+
if (!id)
|
|
86
|
+
return undefined;
|
|
87
|
+
terminalUserMessageId = id;
|
|
88
|
+
awaitingContinuation = false;
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
return !awaitingContinuation ? terminalUserMessageId : undefined;
|
|
94
|
+
}
|
|
49
95
|
function isMessageRecord(value) {
|
|
50
96
|
return isRecord(value) && isRecord(value.info) && Array.isArray(value.parts);
|
|
51
97
|
}
|
|
@@ -72,6 +118,26 @@ function messageText(message, sessionId, messageId) {
|
|
|
72
118
|
function hasCompactionPart(parts) {
|
|
73
119
|
return parts.some((part) => isRecord(part) && part.type === "compaction");
|
|
74
120
|
}
|
|
121
|
+
function compactionTailStartId(message, sessionId) {
|
|
122
|
+
const id = messageId(message);
|
|
123
|
+
const parts = message.parts.filter((part) => (isRecord(part)
|
|
124
|
+
&& part.type === "compaction"
|
|
125
|
+
&& stringField(part, "sessionID") === sessionId
|
|
126
|
+
&& stringField(part, "messageID") === id));
|
|
127
|
+
if (parts.length !== 1 || !isRecord(parts[0]))
|
|
128
|
+
return undefined;
|
|
129
|
+
return stringField(parts[0], "tail_start_id");
|
|
130
|
+
}
|
|
131
|
+
function isCompactionContinuation(message, sessionId) {
|
|
132
|
+
const id = messageId(message);
|
|
133
|
+
return message.parts.some((part) => (isRecord(part)
|
|
134
|
+
&& part.type === "text"
|
|
135
|
+
&& part.synthetic === true
|
|
136
|
+
&& isRecord(part.metadata)
|
|
137
|
+
&& part.metadata.compaction_continue === true
|
|
138
|
+
&& stringField(part, "sessionID") === sessionId
|
|
139
|
+
&& stringField(part, "messageID") === id));
|
|
140
|
+
}
|
|
75
141
|
function stringField(value, key) {
|
|
76
142
|
const field = value[key];
|
|
77
143
|
return typeof field === "string" && field.trim() ? field.trim() : undefined;
|
|
@@ -144,11 +144,6 @@ export async function startBackendService(options = {}, runtime = {}) {
|
|
|
144
144
|
MEMORAX_CODE_BACKEND_HOST: host,
|
|
145
145
|
MEMORAX_CODE_BACKEND_PORT: String(port),
|
|
146
146
|
MEMORAX_CODE_BACKEND_INSTANCE_ID: instanceId,
|
|
147
|
-
...(options.claudeProjectsRoot === false
|
|
148
|
-
? { MEMORAX_CODE_MEMORY_VIEWER_CLAUDE_PROJECTS_ROOT: "disabled" }
|
|
149
|
-
: typeof options.claudeProjectsRoot === "string" && options.claudeProjectsRoot.trim()
|
|
150
|
-
? { MEMORAX_CODE_MEMORY_VIEWER_CLAUDE_PROJECTS_ROOT: resolve(options.claudeProjectsRoot.trim()) }
|
|
151
|
-
: {}),
|
|
152
147
|
...(token ? { MEMORAX_CODE_BACKEND_TOKEN: token } : {}),
|
|
153
148
|
}, url),
|
|
154
149
|
stdio: ["ignore", outFd, errFd],
|
|
@@ -276,11 +276,7 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
|
|
|
276
276
|
...(opencodeAdapter ? { opencodeAdapter } : {}),
|
|
277
277
|
};
|
|
278
278
|
}
|
|
279
|
-
const
|
|
280
|
-
...serviceOptions,
|
|
281
|
-
claudeProjectsRoot: claudeProjectsRootForStart(argv, claudeAdapter),
|
|
282
|
-
};
|
|
283
|
-
const backend = await startBackendService(backendStartOptions);
|
|
279
|
+
const backend = await startBackendService(serviceOptions);
|
|
284
280
|
if (!backend.ok) {
|
|
285
281
|
const disabledCodex = codexAdapter
|
|
286
282
|
? await codexAdapterLifecycle.disable({ argv, serviceOptions })
|
|
@@ -314,15 +310,6 @@ async function executeMemoraxCodeStart(serviceOptions, argv, backendUrl, memorax
|
|
|
314
310
|
...(opencodeAdapter ? { opencodeAdapter } : {}),
|
|
315
311
|
};
|
|
316
312
|
}
|
|
317
|
-
function claudeProjectsRootForStart(argv, claudeAdapter) {
|
|
318
|
-
if (claudeAdapter?.ok === false || claudeAdapter?.enabled !== true)
|
|
319
|
-
return false;
|
|
320
|
-
const claudeHome = argValue(argv, "--claude-home")?.trim()
|
|
321
|
-
|| process.env.CLAUDE_CONFIG_DIR?.trim()
|
|
322
|
-
|| process.env.CLAUDE_HOME?.trim()
|
|
323
|
-
|| join(homedir(), ".claude");
|
|
324
|
-
return join(resolve(claudeHome), "projects");
|
|
325
|
-
}
|
|
326
313
|
function expectedBackendUrl(serviceOptions) {
|
|
327
314
|
return preflightBackendServiceStart(serviceOptions).url;
|
|
328
315
|
}
|
|
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
|
|
|
2
2
|
import { createMemoryWritebackBufferRuntime, } from "./writeback-buffer.js";
|
|
3
3
|
import { memoryWritebackAddParts, } from "./writeback-chunk.js";
|
|
4
4
|
import { invokeMemoraxMemoryProvider, } from "../provider/memorax/adapter.js";
|
|
5
|
-
import { memoryWritebackBufferEnabled, memoryWritebackEnabled, memoryWritebackMaxMessageChars, } from "../provider/memorax/config.js";
|
|
5
|
+
import { memoraxConfigFromEnv, memoryWritebackBufferEnabled, memoryWritebackEnabled, memoryWritebackMaxMessageChars, } from "../provider/memorax/config.js";
|
|
6
6
|
import { hasMeaningfulMemoryPayloadText, redactMemoryPayloadText, } from "./payload-redaction.js";
|
|
7
7
|
const WRITEBACK_DEDUPE_TTL_MS = 5 * 60 * 1000;
|
|
8
8
|
const WRITEBACK_DEDUPE_MAX = 1024;
|
|
@@ -190,6 +190,7 @@ async function enqueueAutomaticMemoryWritebackAsync(state, decision, options) {
|
|
|
190
190
|
const parts = memoryWritebackAddParts(decision, options.env ?? process.env);
|
|
191
191
|
for (const [index, part] of parts.entries()) {
|
|
192
192
|
for (let attempt = 1; attempt <= AUTOMATIC_MEMORY_WRITEBACK_MAX_ATTEMPTS; attempt += 1) {
|
|
193
|
+
const configResult = memoraxConfigFromEnv(options.env);
|
|
193
194
|
const response = await invokeMemoraxMemoryProvider({
|
|
194
195
|
sessionId: decision.sessionKey,
|
|
195
196
|
prompt: part.messages.find((message) => message.role === "user")?.content ?? "",
|
|
@@ -208,6 +209,7 @@ async function enqueueAutomaticMemoryWritebackAsync(state, decision, options) {
|
|
|
208
209
|
...(part.chunk ? { chunk: part.chunk } : {}),
|
|
209
210
|
},
|
|
210
211
|
}, {
|
|
212
|
+
...(configResult.ok ? { config: configResult.config } : {}),
|
|
211
213
|
env: options.env,
|
|
212
214
|
fetchImpl: options.fetchImpl,
|
|
213
215
|
observability: options.memoryObservability,
|
|
@@ -245,8 +247,9 @@ async function enqueueAutomaticMemoryWritebackAsync(state, decision, options) {
|
|
|
245
247
|
httpStatus: response.ok ? undefined : response.httpStatus,
|
|
246
248
|
});
|
|
247
249
|
if (response.ok) {
|
|
248
|
-
if (response.result.quota)
|
|
249
|
-
queueQuotaNoticeBestEffort(state, response.result.quota);
|
|
250
|
+
if (response.result.quota && configResult.ok) {
|
|
251
|
+
queueQuotaNoticeBestEffort(state, configResult.config, response.result.quota);
|
|
252
|
+
}
|
|
250
253
|
break;
|
|
251
254
|
}
|
|
252
255
|
if (!retrying) {
|
|
@@ -287,9 +290,9 @@ function trackAutomaticMemoryWriteback(state, writeback) {
|
|
|
287
290
|
state.inFlight.add(writeback);
|
|
288
291
|
void writeback.then(() => state.inFlight.delete(writeback), () => state.inFlight.delete(writeback));
|
|
289
292
|
}
|
|
290
|
-
function queueQuotaNoticeBestEffort(state, quota) {
|
|
293
|
+
function queueQuotaNoticeBestEffort(state, config, quota) {
|
|
291
294
|
try {
|
|
292
|
-
state.queueQuotaNotice?.(quota);
|
|
295
|
+
state.queueQuotaNotice?.(config, quota);
|
|
293
296
|
}
|
|
294
297
|
catch {
|
|
295
298
|
state.diagnosticLogger("memory.automatic_writeback.quota_notice_queue_failed", {});
|