@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
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
- Setup automatically detects supported coding agents and completes an
21
- account-free connection. If you already have a MemoraX account, run
22
- `memorax-code setup --existing-account` instead. Later setup runs reuse a
23
- complete saved configuration; use `memorax-code setup --reconfigure` to
24
- replace it.
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.");
@@ -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. Memory Viewer includes DSH activity from
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
 
@@ -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
- Routine reminders do not include a complete Mark ID. If this device uses an
139
- unregistered anonymous identity and the MemoraX account page requires its Mark
140
- ID, run this command yourself in a local terminal:
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. Do
147
- not ask an Agent to run it or paste the output into a conversation, screenshot,
148
- or log. If no ready local trial identity exists, use `memorax-code setup`; a
149
- connection copied from another computer does not include that computer's
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, memoryViewerSessionCookieHeader, publicErrorMessage, statusCodeFromError } from "../transport/http/request.js";
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 memoryWritebackTaskProjections = TRACE_CLIENTS.map((client) => createMemoryWritebackTaskProjection({
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
- import { memoryViewerObservabilityHook } from "../viewer/projection/observability.js";
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, resolve } from "node:path";
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
- || stringField(assistant.info, "parentID") !== input.userMessageId) {
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.name === "MessageAbortedError";
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 backendStartOptions = {
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", {});