@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.
Files changed (70) 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/codex/rollout-turn.js +52 -2
  10. package/lib/memorax-code-backend/dist/clients/opencode/message-turn.js +68 -2
  11. package/lib/memorax-code-backend/dist/lifecycle/backend/service.js +0 -5
  12. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +1 -14
  13. package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +8 -5
  14. package/lib/memorax-code-backend/dist/memory/project.js +4 -26
  15. package/lib/memorax-code-backend/dist/memory/quota-notice.js +79 -19
  16. package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +1 -4
  17. package/lib/memorax-code-backend/dist/provider/memorax/http.js +0 -28
  18. package/lib/memorax-code-backend/dist/transport/http/request.js +1 -39
  19. package/lib/memorax-code-backend/package.json +2 -3
  20. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  21. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  22. package/lib/memorax-code-claude-adapter/package.json +1 -1
  23. package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
  24. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  25. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  26. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  27. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  28. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  29. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +0 -6
  30. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  31. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  32. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  33. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  34. package/lib/memorax-code-codex-adapter/package.json +1 -1
  35. package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +0 -6
  36. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  37. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  38. package/lib/memorax-code-dsh-adapter/package.json +1 -1
  39. package/lib/memorax-code-dsh-adapter/skills/memorax-code/SKILL.md +0 -6
  40. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  41. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  42. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  43. package/lib/memorax-code-opencode-adapter/skills/memorax-code/SKILL.md +0 -6
  44. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +1 -1
  45. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +1 -1
  46. package/lib/memorax-code-opencode-adapter/src/plugin.mjs +121 -16
  47. package/lib/memorax-code-opencode-adapter/src/repo-memory-server-runner.mjs +2 -0
  48. package/package.json +1 -1
  49. package/lib/memorax-code-backend/dist/clients/codex/effective-prompt.js +0 -54
  50. package/lib/memorax-code-backend/dist/memory/writeback-reconciler.js +0 -244
  51. package/lib/memorax-code-backend/dist/memory/writeback-task-projection.js +0 -377
  52. package/lib/memorax-code-backend/dist/repository/readiness.js +0 -188
  53. package/lib/memorax-code-backend/dist/shared/incremental-jsonl-projection.js +0 -271
  54. package/lib/memorax-code-backend/dist/viewer/history/claude-transcript.js +0 -648
  55. package/lib/memorax-code-backend/dist/viewer/history/session-title.js +0 -93
  56. package/lib/memorax-code-backend/dist/viewer/http/public-routes.js +0 -146
  57. package/lib/memorax-code-backend/dist/viewer/model.js +0 -1
  58. package/lib/memorax-code-backend/dist/viewer/projection/activity.js +0 -113
  59. package/lib/memorax-code-backend/dist/viewer/projection/cache.js +0 -69
  60. package/lib/memorax-code-backend/dist/viewer/projection/event-identity.js +0 -3
  61. package/lib/memorax-code-backend/dist/viewer/projection/history.js +0 -79
  62. package/lib/memorax-code-backend/dist/viewer/projection/observability.js +0 -8
  63. package/lib/memorax-code-backend/dist/viewer/projection/redaction.js +0 -52
  64. package/lib/memorax-code-backend/dist/viewer/projection/turn-reference.js +0 -12
  65. package/lib/memorax-code-backend/dist/viewer/projection/user.js +0 -168
  66. package/lib/memorax-code-backend/dist/viewer/projection/writeback-status.js +0 -97
  67. package/lib/memorax-code-backend/dist/viewer/store.js +0 -991
  68. package/lib/memorax-code-backend/dist/viewer/ui/icon.js +0 -1
  69. package/lib/memorax-code-backend/dist/viewer/ui/logo.js +0 -1
  70. 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
@@ -32,6 +32,8 @@ export function codexRolloutTurnFromJsonLines(transcript, input) {
32
32
  if (!codexRolloutSessionMatches(scan, input.sessionId)) {
33
33
  return { ok: false, reason: "transcript_session_mismatch" };
34
34
  }
35
+ if (scan.turnMetadataMismatch)
36
+ return { ok: false, reason: "turn_metadata_mismatch" };
35
37
  if (!scan.targetSeen)
36
38
  return { ok: false, reason: "turn_not_found" };
37
39
  if (!scan.userPrompt)
@@ -48,6 +50,8 @@ export function codexInterruptedRolloutTurnFromJsonLines(transcript, input) {
48
50
  if (!codexRolloutSessionMatches(scan, input.sessionId)) {
49
51
  return { ok: false, reason: "transcript_session_mismatch" };
50
52
  }
53
+ if (scan.turnMetadataMismatch)
54
+ return { ok: false, reason: "turn_metadata_mismatch" };
51
55
  if (!scan.targetSeen)
52
56
  return { ok: false, reason: "turn_not_found" };
53
57
  if (!scan.userPrompt)
@@ -74,9 +78,12 @@ function scanCodexRolloutTurn(transcript, targetTurnId) {
74
78
  let targetBoundarySeen = false;
75
79
  let activeTurnId;
76
80
  let targetSeen = false;
81
+ let turnMetadataMismatch = false;
77
82
  let userPrompt;
78
83
  let assistantReply;
79
84
  const visibleAssistantMessages = [];
85
+ let responseItemUserPrompt;
86
+ let responseItemAssistantReply;
80
87
  let interrupted = false;
81
88
  let interruptedAt;
82
89
  let rolledBack = false;
@@ -145,6 +152,26 @@ function scanCodexRolloutTurn(transcript, targetTurnId) {
145
152
  const activities = activityCandidatesFromResponseItem(payload);
146
153
  if (activities.length > 0)
147
154
  targetActivityGroups.push(activities);
155
+ if (stringValue(payload.type) === "message") {
156
+ const role = stringValue(payload.role);
157
+ const message = responseItemMessageText(payload.content, role);
158
+ const authoritativeMessage = role === "user"
159
+ || (role === "assistant" && payload.phase === "final_answer");
160
+ if (authoritativeMessage && message) {
161
+ const responseTurnId = responseItemTurnId(payload);
162
+ if (responseTurnId && responseTurnId !== activeTurnId) {
163
+ turnMetadataMismatch = true;
164
+ continue;
165
+ }
166
+ if (role === "user") {
167
+ userMessageTurnIds.add(activeTurnId);
168
+ responseItemUserPrompt = message;
169
+ }
170
+ else {
171
+ responseItemAssistantReply = message;
172
+ }
173
+ }
174
+ }
148
175
  }
149
176
  continue;
150
177
  }
@@ -231,8 +258,9 @@ function scanCodexRolloutTurn(transcript, targetTurnId) {
231
258
  ambiguousSessionMetadata,
232
259
  composite,
233
260
  targetSeen,
234
- userPrompt,
235
- assistantReply,
261
+ turnMetadataMismatch,
262
+ userPrompt: responseItemUserPrompt ?? userPrompt,
263
+ assistantReply: responseItemAssistantReply ?? assistantReply,
236
264
  visibleAssistantMessages,
237
265
  interrupted,
238
266
  interruptedAt,
@@ -245,6 +273,28 @@ function scanCodexRolloutTurn(transcript, targetTurnId) {
245
273
  userMessageTurnIds,
246
274
  };
247
275
  }
276
+ function responseItemTurnId(payload) {
277
+ const metadata = isRecord(payload.internal_chat_message_metadata_passthrough)
278
+ ? payload.internal_chat_message_metadata_passthrough
279
+ : undefined;
280
+ return metadata
281
+ ? stringValue(metadata.turn_id) ?? stringValue(metadata.turnId)
282
+ : undefined;
283
+ }
284
+ function responseItemMessageText(value, role) {
285
+ if (!Array.isArray(value))
286
+ return undefined;
287
+ const expectedType = role === "user" ? "input_text" : role === "assistant" ? "output_text" : undefined;
288
+ if (!expectedType)
289
+ return undefined;
290
+ const parts = value.flatMap((item) => {
291
+ if (!isRecord(item) || stringValue(item.type) !== expectedType)
292
+ return [];
293
+ const text = nonBlankString(item.text);
294
+ return text ? [text] : [];
295
+ });
296
+ return parts.length > 0 ? parts.join("\n") : undefined;
297
+ }
248
298
  function codexRolloutSessionMatches(scan, sessionId) {
249
299
  return scan.authoritySessionId === sessionId && !scan.ambiguousSessionMetadata;
250
300
  }
@@ -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],