@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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax-code/dsh-memorax-code",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "DeepSeek Harness native memory adapter for MemoraX Code.",
@@ -82,10 +82,4 @@ Apply instructions in this order: system and developer instructions, `AGENTS.md`
82
82
 
83
83
  For MemoraX Code coding memory, use `memorax-cli` exactly as described by the selected reference. Invoke this skill as `$memorax-code` in Codex or `/memorax-code` in Claude Code. In OpenCode, ask the agent to use the `memorax-code` skill by name. These invocation forms are not shell commands. `memorax-code` is the lifecycle CLI and must not be used for memory search or add. Do not call MemoraX HTTP endpoints directly.
84
84
 
85
- When explaining Memory Viewer, describe only `/memory-viewer`. It is a
86
- content-free local summary and must not expose conversation or memory text,
87
- session or turn identifiers, paths, or trace details. The page never queries
88
- MemoraX directly; it projects client-qualified local activity and keeps each
89
- supported harness isolated.
90
-
91
85
  Never store secrets, credentials, `.env` content, sensitive personal data, raw transcripts, hidden tests, exact patches, temporary target commits, or unsafe destructive commands. Do not announce internal routing or reference loading.
@@ -83,6 +83,6 @@ Do not add speculation, assistant-only praise, or a current instruction merely b
83
83
 
84
84
  ## Output
85
85
 
86
- If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or conditional local Mark ID retrieval instructions. Never run `memorax-code account --show-mark-id` for the user, ask for its output, or reproduce a Mark ID in chat. Treat the reminder as user-facing operational output, not coding memory content, and continue the current task.
86
+ If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not coding memory content, and continue the current task.
87
87
 
88
88
  After an add request is accepted, confirm briefly that it was submitted for processing and identify the reusable coding lesson at a high level. If add is disabled or fails, report the issue and continue without bypassing the CLI.
@@ -88,6 +88,6 @@ Do not apply this retry to `memorax-cli add`, authentication or configuration fa
88
88
 
89
89
  ## Output
90
90
 
91
- If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or conditional local Mark ID retrieval instructions. Never run `memorax-code account --show-mark-id` for the user, ask for its output, or reproduce a Mark ID in chat. Treat the reminder as user-facing operational output, not recalled memory, and continue the current task.
91
+ If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not recalled memory, and continue the current task.
92
92
 
93
93
  Mention only an invariant, pitfall, convention, or validation idea that materially affects the answer. Ground claims about current implementation behavior in live code and checks.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax-code/opencode-adapter",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "OpenCode plugin adapter for MemoraX Code memory services.",
@@ -82,10 +82,4 @@ Apply instructions in this order: system and developer instructions, `AGENTS.md`
82
82
 
83
83
  For MemoraX Code coding memory, use `memorax-cli` exactly as described by the selected reference. Invoke this skill as `$memorax-code` in Codex or `/memorax-code` in Claude Code. In OpenCode, ask the agent to use the `memorax-code` skill by name. These invocation forms are not shell commands. `memorax-code` is the lifecycle CLI and must not be used for memory search or add. Do not call MemoraX HTTP endpoints directly.
84
84
 
85
- When explaining Memory Viewer, describe only `/memory-viewer`. It is a
86
- content-free local summary and must not expose conversation or memory text,
87
- session or turn identifiers, paths, or trace details. The page never queries
88
- MemoraX directly; it projects client-qualified local activity and keeps each
89
- supported harness isolated.
90
-
91
85
  Never store secrets, credentials, `.env` content, sensitive personal data, raw transcripts, hidden tests, exact patches, temporary target commits, or unsafe destructive commands. Do not announce internal routing or reference loading.
@@ -83,6 +83,6 @@ Do not add speculation, assistant-only praise, or a current instruction merely b
83
83
 
84
84
  ## Output
85
85
 
86
- If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or conditional local Mark ID retrieval instructions. Never run `memorax-code account --show-mark-id` for the user, ask for its output, or reproduce a Mark ID in chat. Treat the reminder as user-facing operational output, not coding memory content, and continue the current task.
86
+ If a successful Add returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not coding memory content, and continue the current task.
87
87
 
88
88
  After an add request is accepted, confirm briefly that it was submitted for processing and identify the reusable coding lesson at a high level. If add is disabled or fails, report the issue and continue without bypassing the CLI.
@@ -88,6 +88,6 @@ Do not apply this retry to `memorax-cli add`, authentication or configuration fa
88
88
 
89
89
  ## Output
90
90
 
91
- If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or conditional local Mark ID retrieval instructions. Never run `memorax-code account --show-mark-id` for the user, ask for its output, or reproduce a Mark ID in chat. Treat the reminder as user-facing operational output, not recalled memory, and continue the current task.
91
+ If a successful Search returns `quotaNotice`, or prints it as a quota-reminder line in the default CLI output, present the complete reminder once and prominently before the normal result summary. Do not reduce it to only a percentage or omit its account URL or anonymous-account claim details. A guest reminder may already contain the local Mark ID; present it once as part of the reminder, but do not repeat or separately quote it. Never run `memorax-code account --show-mark-id` for the user or ask for its output. Treat the reminder as user-facing operational output, not recalled memory, and continue the current task.
92
92
 
93
93
  Mention only an invariant, pitfall, convention, or validation idea that materially affects the answer. Ground claims about current implementation behavior in live code and checks.
@@ -103,13 +103,14 @@ export function createMemoraxOpenCodePlugin(options = {}) {
103
103
  && message.info.id === turn.userMessageId
104
104
  && message.info.sessionID === sessionId
105
105
  ));
106
- const assistant = terminalAssistantFor(
106
+ const terminal = terminalTurnFor(
107
107
  messages,
108
108
  sessionId,
109
109
  turn.userMessageId,
110
110
  target?.assistantMessageId,
111
111
  );
112
- if (!user || !assistant) continue;
112
+ if (!user || !terminal) continue;
113
+ const { assistant, evidence } = terminal;
113
114
  let result;
114
115
  try {
115
116
  result = await postBackend(options, "/memory/writeback", {
@@ -118,7 +119,7 @@ export function createMemoraxOpenCodePlugin(options = {}) {
118
119
  sessionId,
119
120
  userMessageId: turn.userMessageId,
120
121
  assistantMessageId: assistant.info.id,
121
- messages: [user, assistant],
122
+ messages: evidence,
122
123
  cwd: workspaceRoot,
123
124
  workspaceKind,
124
125
  }, WRITEBACK_TIMEOUT_MS);
@@ -281,10 +282,13 @@ export function createMemoraxOpenCodePlugin(options = {}) {
281
282
  return;
282
283
  }
283
284
  if (event?.type === "message.updated") {
284
- const interrupted = interruptedAssistantFromEvent(event, pendingTurns);
285
+ const interrupted = terminalErrorAssistantFromEvent(event, pendingTurns);
285
286
  if (interrupted) {
286
287
  track(
287
- queueSessionFlush(interrupted.sessionId, interrupted),
288
+ queueSessionFlush(
289
+ interrupted.sessionId,
290
+ interrupted.userMessageId ? interrupted : undefined,
291
+ ),
288
292
  "opencode interrupted turn finalization failed",
289
293
  );
290
294
  }
@@ -310,18 +314,16 @@ export function createMemoraxOpenCodePlugin(options = {}) {
310
314
  export const MemoraxOpenCodePlugin = createMemoraxOpenCodePlugin();
311
315
  export default MemoraxOpenCodePlugin;
312
316
 
313
- function terminalAssistantFor(messages, sessionId, userMessageId, assistantMessageId) {
314
- return messages
317
+ function terminalTurnFor(messages, sessionId, userMessageId, assistantMessageId) {
318
+ const lineage = turnLineage(messages, sessionId, userMessageId);
319
+ if (!lineage || lineage.awaitingContinuation) return undefined;
320
+ const assistant = lineage.messages
315
321
  .filter((message) => (
316
322
  message?.info?.role === "assistant"
317
323
  && message.info.sessionID === sessionId
318
- && message.info.parentID === userMessageId
324
+ && message.info.parentID === lineage.terminalUserMessageId
319
325
  && (!assistantMessageId || message.info.id === assistantMessageId)
320
326
  && Number.isFinite(message.info.time?.completed)
321
- && (
322
- message.info.error === undefined
323
- || message.info.error?.name === "MessageAbortedError"
324
- )
325
327
  && message.info.summary !== true
326
328
  && !message.parts?.some((part) => part?.type === "compaction")
327
329
  ))
@@ -330,24 +332,127 @@ function terminalAssistantFor(messages, sessionId, userMessageId, assistantMessa
330
332
  || String(left.info.id).localeCompare(String(right.info.id))
331
333
  ))
332
334
  .at(-1);
335
+ if (!assistant) return undefined;
336
+ return {
337
+ assistant,
338
+ evidence: uniqueMessages([...lineage.evidence, assistant]),
339
+ };
333
340
  }
334
341
 
335
- function interruptedAssistantFromEvent(event, pendingTurns) {
342
+ function turnLineage(messages, sessionId, userMessageId) {
343
+ const sessionMessages = messages.filter((message) => (
344
+ message?.info?.sessionID === sessionId
345
+ && stringValue(message.info.id)
346
+ && Array.isArray(message.parts)
347
+ ));
348
+ const startIndex = sessionMessages.findIndex((message) => (
349
+ message.info.role === "user" && message.info.id === userMessageId
350
+ ));
351
+ if (startIndex < 0) return undefined;
352
+ const original = sessionMessages[startIndex];
353
+ const lineageAssistants = new Map();
354
+ const evidence = [original];
355
+ let terminalUserMessageId = userMessageId;
356
+ let awaitingContinuation = false;
357
+
358
+ for (const message of sessionMessages.slice(startIndex + 1)) {
359
+ if (message.info.role === "assistant") {
360
+ if (message.info.parentID === terminalUserMessageId) {
361
+ lineageAssistants.set(message.info.id, message);
362
+ }
363
+ continue;
364
+ }
365
+ if (message.info.role !== "user") continue;
366
+ if (hasCompactionPart(message.parts)) {
367
+ const compactionTailId = compactionTailStartId(message, sessionId);
368
+ if (!compactionTailId || awaitingContinuation) return undefined;
369
+ const tail = lineageAssistants.get(compactionTailId);
370
+ if (!tail) return undefined;
371
+ evidence.push(minimalAssistantEvidence(tail), message);
372
+ awaitingContinuation = true;
373
+ continue;
374
+ }
375
+ if (isCompactionContinuation(message, sessionId)) {
376
+ if (!awaitingContinuation) return undefined;
377
+ terminalUserMessageId = message.info.id;
378
+ evidence.push(message);
379
+ awaitingContinuation = false;
380
+ continue;
381
+ }
382
+ break;
383
+ }
384
+ return {
385
+ messages: sessionMessages.slice(startIndex + 1),
386
+ terminalUserMessageId,
387
+ awaitingContinuation,
388
+ evidence,
389
+ };
390
+ }
391
+
392
+ function hasCompactionPart(parts) {
393
+ return parts.some((part) => part?.type === "compaction");
394
+ }
395
+
396
+ function compactionTailStartId(message, sessionId) {
397
+ const messageId = stringValue(message?.info?.id);
398
+ const parts = message?.parts?.filter((part) => (
399
+ part?.type === "compaction"
400
+ && part.sessionID === sessionId
401
+ && part.messageID === messageId
402
+ )) ?? [];
403
+ if (parts.length !== 1) return undefined;
404
+ return stringValue(parts[0].tail_start_id);
405
+ }
406
+
407
+ function minimalAssistantEvidence(message) {
408
+ return {
409
+ info: {
410
+ id: message.info.id,
411
+ sessionID: message.info.sessionID,
412
+ role: message.info.role,
413
+ parentID: message.info.parentID,
414
+ },
415
+ parts: [],
416
+ };
417
+ }
418
+
419
+ function isCompactionContinuation(message, sessionId) {
420
+ const messageId = stringValue(message?.info?.id);
421
+ return message?.parts?.some((part) => (
422
+ part?.type === "text"
423
+ && part.synthetic === true
424
+ && part.metadata?.compaction_continue === true
425
+ && part.sessionID === sessionId
426
+ && part.messageID === messageId
427
+ )) === true;
428
+ }
429
+
430
+ function uniqueMessages(messages) {
431
+ const unique = new Map();
432
+ for (const message of messages) unique.set(message.info.id, message);
433
+ return [...unique.values()];
434
+ }
435
+
436
+ function terminalErrorAssistantFromEvent(event, pendingTurns) {
336
437
  const info = event?.properties?.info;
337
438
  const sessionId = stringValue(info?.sessionID);
338
439
  const userMessageId = stringValue(info?.parentID);
339
440
  const assistantMessageId = stringValue(info?.id);
340
441
  if (
341
442
  info?.role !== "assistant"
342
- || info?.error?.name !== "MessageAbortedError"
443
+ || !stringValue(info?.error?.name)
343
444
  || !Number.isFinite(info?.time?.completed)
344
445
  || info?.summary === true
345
446
  || !sessionId
346
447
  || !userMessageId
347
448
  || !assistantMessageId
348
- || !pendingTurns.has(turnKey({ sessionId, userMessageId }))
349
449
  ) return undefined;
350
- return { sessionId, userMessageId, assistantMessageId };
450
+ if (pendingTurns.has(turnKey({ sessionId, userMessageId }))) {
451
+ return { sessionId, userMessageId, assistantMessageId };
452
+ }
453
+ return [...pendingTurns.values()].some((turn) => turn.sessionId === sessionId)
454
+ ? { sessionId }
455
+ : undefined;
351
456
  }
352
457
 
353
458
  function textParts(parts) {
@@ -7,6 +7,7 @@ import { fileURLToPath } from "node:url";
7
7
  export const OPENCODE_REPO_MEMORY_AGENT = "memorax-code-repo-memory";
8
8
 
9
9
  const OWNED_SERVER_USERNAME = "memorax-code";
10
+ const OWNED_SERVER_DATABASE = ":memory:";
10
11
  const DEFAULT_SERVER_START_TIMEOUT_MS = 20_000;
11
12
  const DEFAULT_SERVER_STOP_TIMEOUT_MS = 5_000;
12
13
  const MAX_SERVER_START_OUTPUT = 8_192;
@@ -155,6 +156,7 @@ async function startOwnedOpenCodeServer(input) {
155
156
  cwd: input.directory,
156
157
  env: {
157
158
  ...input.env,
159
+ OPENCODE_DB: OWNED_SERVER_DATABASE,
158
160
  OPENCODE_SERVER_USERNAME: username,
159
161
  OPENCODE_SERVER_PASSWORD: password,
160
162
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@memorax/memorax-code",
3
- "version": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Persistent coding memory for Codex, Claude Code, DeepSeek Harness, and OpenCode.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,54 +0,0 @@
1
- export function stripCodexPreambleSegments(text) {
2
- let remaining = text;
3
- for (;;) {
4
- const next = stripOneLeadingCodexPreambleSegment(remaining);
5
- if (next === remaining) {
6
- return stripCodexSkillSegments(stripCodexAmbientUiSegments(remaining)).trim();
7
- }
8
- remaining = next.trimStart();
9
- }
10
- }
11
- function stripOneLeadingCodexPreambleSegment(text) {
12
- return stripLeadingEnvironmentContext(text)
13
- ?? stripLeadingInstructionsBlock(text)
14
- ?? stripLeadingSessionPreamble(text)
15
- ?? text;
16
- }
17
- function stripCodexSkillSegments(text) {
18
- return text.replace(/(^|\n)[^\S\n]*<skill\b[\s\S]*?<\/skill>[^\S\n]*(?=\n|$)/gi, (match, prefix) => {
19
- const block = match.slice(prefix.length).trim();
20
- return isCodexSkillSegment(block) ? prefix : match;
21
- });
22
- }
23
- function stripCodexAmbientUiSegments(text) {
24
- let removedAmbientContext = false;
25
- const withoutAmbientContext = text.replace(/(^|\n)[^\S\n]*<in-app-browser-context\b[\s\S]*?<\/in-app-browser-context>[^\S\n]*(?=\n|$)/gi, (_match, prefix) => {
26
- removedAmbientContext = true;
27
- return prefix;
28
- });
29
- if (!removedAmbientContext)
30
- return text;
31
- return withoutAmbientContext
32
- .replace(/(^|\n)[^\S\n]*##\s+My request for Codex:[^\S\n]*(?=\n|$)/gi, "$1")
33
- .replace(/\n[^\S\n]*(?:\n[^\S\n]*){2,}/g, "\n\n");
34
- }
35
- function isCodexSkillSegment(text) {
36
- return /^<skill\b/i.test(text)
37
- && /<name>[^<\n]+<\/name>/i.test(text)
38
- && /<path>[\s\S]*?SKILL\.md[\s\S]*?<\/path>/i.test(text);
39
- }
40
- function stripLeadingEnvironmentContext(text) {
41
- const match = text.match(/^\s*<environment_context\b[\s\S]*?<\/environment_context>\s*/);
42
- return match ? text.slice(match[0].length) : undefined;
43
- }
44
- function stripLeadingInstructionsBlock(text) {
45
- const match = text.match(/^\s*#\s+(?:AGENTS|CLAUDE)\.md[^\n]*[\s\S]*?<\/INSTRUCTIONS>\s*/i);
46
- return match ? text.slice(match[0].length) : undefined;
47
- }
48
- function stripLeadingSessionPreamble(text) {
49
- const trimmed = text.trimStart();
50
- if (!trimmed.startsWith("You are Claude Code"))
51
- return undefined;
52
- const boundary = trimmed.indexOf("\n\n");
53
- return boundary >= 0 ? trimmed.slice(boundary + 2) : "";
54
- }
@@ -1,244 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { backendDebug } from "../shared/debug-log.js";
3
- import { callMemoAddStatus, memoraxConfigFromEnv } from "../provider/memorax/adapter.js";
4
- import { MEMORAX_PROVIDER_ID, memoraxWritebackEnabled } from "../provider/memorax/config.js";
5
- import { completeMemoryWritebackTask, createMemoryWritebackTaskProjection, } from "./writeback-task-projection.js";
6
- import { clientTraceConfigFromEnv } from "../trace/config.js";
7
- import { recordTraceEvent } from "../trace/store.js";
8
- const DEFAULT_INTERVAL_MS = 60_000;
9
- const DEFAULT_MAX_TASKS_PER_RUN = 20;
10
- const DEFAULT_CONCURRENCY = 4;
11
- const DEFAULT_MAX_BACKOFF_MS = 60 * 60_000;
12
- export async function reconcileMemoryWritebackStatuses(options) {
13
- return reconcileMemoryWritebackStatusesWithPolicy(options);
14
- }
15
- export function startMemoryWritebackReconciler(options) {
16
- let closed = false;
17
- let active;
18
- let candidateOffset = 0;
19
- const intervalMs = positiveInteger(options.intervalMs, DEFAULT_INTERVAL_MS);
20
- const maxTasks = positiveInteger(options.maxTasks, DEFAULT_MAX_TASKS_PER_RUN);
21
- const taskProjection = resolveTaskProjection(options);
22
- const policy = createReconcileBackoffPolicy({
23
- now: options.now ?? Date.now,
24
- baseDelayMs: intervalMs,
25
- maxDelayMs: Math.max(intervalMs, positiveInteger(options.maxBackoffMs, DEFAULT_MAX_BACKOFF_MS)),
26
- });
27
- const runNow = () => {
28
- if (closed)
29
- return Promise.resolve(emptyReport());
30
- if (active)
31
- return active;
32
- active = reconcileMemoryWritebackStatusesWithPolicy({
33
- ...options,
34
- maxTasks,
35
- candidateOffset,
36
- taskProjection,
37
- }, policy).finally(() => {
38
- candidateOffset += maxTasks;
39
- active = undefined;
40
- });
41
- return active;
42
- };
43
- const scheduleRun = () => {
44
- void runNow().catch((error) => {
45
- backendDebug("memory_writeback_reconciler.run_failed", {
46
- error: error instanceof Error ? error.message : String(error),
47
- });
48
- });
49
- };
50
- scheduleRun();
51
- const timer = setInterval(scheduleRun, intervalMs);
52
- timer.unref?.();
53
- return {
54
- runNow,
55
- async close() {
56
- closed = true;
57
- clearInterval(timer);
58
- await active?.catch(() => undefined);
59
- },
60
- };
61
- }
62
- async function reconcileMemoryWritebackStatusesWithPolicy(options, policy) {
63
- const env = { ...(options.env ?? process.env), MEMORAX_CODE_HOME: options.memoraxCodeHome };
64
- const config = memoraxConfigFromEnv(env);
65
- const taskProjection = resolveTaskProjection(options);
66
- if (!clientTraceConfigFromEnv(taskProjection.client, env).enabled
67
- || !config.ok
68
- || !memoraxWritebackEnabled(env)) {
69
- return emptyReport();
70
- }
71
- const maxTasks = positiveInteger(options.maxTasks, DEFAULT_MAX_TASKS_PER_RUN);
72
- const concurrency = positiveInteger(options.concurrency, DEFAULT_CONCURRENCY);
73
- const pendingWritebacks = await taskProjection.listPending();
74
- policy?.retainPending(pendingWritebacks);
75
- const dueWritebacks = pendingWritebacks.filter((task) => policy?.isDue(task) ?? true);
76
- const candidates = rotatedBatch(dueWritebacks, maxTasks, options.candidateOffset ?? 0);
77
- const queue = [...candidates];
78
- const report = { inspected: 0, persisted: 0, pending: 0, failed: 0 };
79
- const workers = Array.from({ length: Math.min(concurrency, queue.length) }, async () => {
80
- for (;;) {
81
- const task = queue.shift();
82
- if (!task)
83
- return;
84
- report.inspected += 1;
85
- try {
86
- const raw = await callMemoAddStatus(config.config, task.taskId, options.fetchImpl);
87
- const status = normalizeAddStatus(raw);
88
- if (isPendingWritebackStatus(status.status)) {
89
- report.pending += 1;
90
- policy?.recordResult(task, "pending");
91
- continue;
92
- }
93
- await persistTerminalWritebackStatus(options.memoraxCodeHome, env, task, status);
94
- report.persisted += 1;
95
- policy?.recordResult(task, "persisted");
96
- }
97
- catch (error) {
98
- report.failed += 1;
99
- policy?.recordResult(task, "failed");
100
- backendDebug("memory_writeback_reconciler.status_failed", {
101
- taskId: task.taskId,
102
- error: error instanceof Error ? error.message : String(error),
103
- });
104
- }
105
- }
106
- });
107
- await Promise.all(workers);
108
- return report;
109
- }
110
- function resolveTaskProjection(options) {
111
- return options.taskProjection ?? createMemoryWritebackTaskProjection({
112
- memoraxCodeHome: options.memoraxCodeHome,
113
- client: options.client ?? "codex",
114
- });
115
- }
116
- async function persistTerminalWritebackStatus(memoraxCodeHome, env, task, status) {
117
- const completed = completeMemoryWritebackTask({
118
- status: status.status,
119
- ...(status.memoryKnown ? { memory: status.memory, memoryKnown: true } : {}),
120
- error: status.error,
121
- });
122
- const capturedAt = new Date().toISOString();
123
- const recorded = await recordTraceEvent({
124
- memoraxCodeHome,
125
- env,
126
- eventId: terminalEventId(task, completed.status),
127
- traceContext: {
128
- schemaVersion: "1",
129
- client: task.client,
130
- sessionId: task.sessionId,
131
- ...(task.turnId ? { turnId: task.turnId } : {}),
132
- ...(task.memoryProject ? { memoryProject: task.memoryProject } : {}),
133
- contextOrigin: "manual",
134
- capturedAt,
135
- },
136
- type: "memory_writeback_status",
137
- source: "writeback_reconciler",
138
- operation: "writeback",
139
- ok: completed.ok,
140
- request: {
141
- task_id: task.taskId,
142
- original_event_id: task.eventId,
143
- provider: MEMORAX_PROVIDER_ID,
144
- },
145
- response: {
146
- taskId: task.taskId,
147
- status: completed.status,
148
- outcome: completed.outcome,
149
- ...(completed.savedMemoryCount === undefined ? {} : { savedMemoryCount: completed.savedMemoryCount }),
150
- ...(completed.savedMemoryIds?.length ? { savedMemoryIds: completed.savedMemoryIds } : {}),
151
- ...(completed.savedMemories?.length ? { savedMemories: completed.savedMemories } : {}),
152
- },
153
- error: completed.error,
154
- });
155
- if (!recorded.written && recorded.reason !== "duplicate_event") {
156
- throw new Error(`writeback terminal status was not persisted: ${recorded.reason}`);
157
- }
158
- }
159
- function createReconcileBackoffPolicy(options) {
160
- const retries = new Map();
161
- return {
162
- retainPending(tasks) {
163
- const pendingKeys = new Set(tasks.map(reconcileCandidateKey));
164
- for (const key of retries.keys()) {
165
- if (!pendingKeys.has(key))
166
- retries.delete(key);
167
- }
168
- },
169
- isDue(task) {
170
- const key = reconcileCandidateKey(task);
171
- return (retries.get(key)?.nextAttemptAt ?? 0) <= options.now();
172
- },
173
- recordResult(task, result) {
174
- const key = reconcileCandidateKey(task);
175
- if (result === "persisted") {
176
- retries.delete(key);
177
- return;
178
- }
179
- const attempts = (retries.get(key)?.attempts ?? 0) + 1;
180
- const multiplier = 2 ** Math.min(attempts - 1, 30);
181
- const delayMs = Math.min(options.maxDelayMs, options.baseDelayMs * multiplier);
182
- retries.set(key, {
183
- attempts,
184
- nextAttemptAt: options.now() + delayMs,
185
- });
186
- },
187
- };
188
- }
189
- function reconcileCandidateKey(task) {
190
- return JSON.stringify([
191
- task.client,
192
- task.sessionId,
193
- task.eventId,
194
- task.taskId,
195
- ]);
196
- }
197
- function normalizeAddStatus(raw) {
198
- const envelope = record(raw);
199
- const data = record(envelope?.data) ?? envelope;
200
- const status = typeof data?.status === "string" ? data.status.trim().toLowerCase() : "unknown";
201
- const error = typeof data?.error === "string" && data.error.trim() ? data.error.trim() : null;
202
- const hasMemoryField = Boolean(data)
203
- && (Object.prototype.hasOwnProperty.call(data, "memory")
204
- || Object.prototype.hasOwnProperty.call(data, "memories"));
205
- const memory = data?.memory ?? data?.memories;
206
- const memoryKnown = hasMemoryField && memory !== null && memory !== undefined;
207
- return {
208
- status,
209
- ...(memoryKnown ? { memory } : {}),
210
- memoryKnown,
211
- error,
212
- };
213
- }
214
- function isPendingWritebackStatus(status) {
215
- return status === "accepted"
216
- || status === "processing"
217
- || status === "queued"
218
- || status === "pending"
219
- || status === "unknown";
220
- }
221
- function terminalEventId(task, status) {
222
- const digest = createHash("sha256")
223
- .update(`${task.sessionId}\u0000${task.eventId}\u0000${task.taskId}\u0000${status}`)
224
- .digest("hex")
225
- .slice(0, 32);
226
- return `memory-writeback-status-${digest}`;
227
- }
228
- function positiveInteger(value, fallback) {
229
- return typeof value === "number" && Number.isSafeInteger(value) && value > 0 ? value : fallback;
230
- }
231
- function rotatedBatch(items, limit, offset) {
232
- if (items.length <= limit)
233
- return [...items];
234
- const start = Number.isSafeInteger(offset) && offset > 0 ? offset % items.length : 0;
235
- return Array.from({ length: limit }, (_, index) => items[(start + index) % items.length]);
236
- }
237
- function emptyReport() {
238
- return { inspected: 0, persisted: 0, pending: 0, failed: 0 };
239
- }
240
- function record(value) {
241
- return value && typeof value === "object" && !Array.isArray(value)
242
- ? value
243
- : undefined;
244
- }