@memorax/memorax-code 0.1.5 → 0.1.7

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 (86) hide show
  1. package/README.md +7 -18
  2. package/bin/memorax-code-npm-preinstall.mjs +28 -39
  3. package/bin/memorax-code-plugin-postinstall.mjs +23 -1537
  4. package/bin/memorax-code-setup.mjs +1615 -0
  5. package/bin/memorax-code.mjs +289 -5
  6. package/docs/configuration.md +66 -17
  7. package/docs/troubleshooting.md +123 -7
  8. package/lib/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
  9. package/lib/memorax-code-adapter-common/src/config-utils.mjs +36 -3
  10. package/lib/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
  11. package/lib/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
  12. package/lib/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
  13. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
  14. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
  15. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
  16. package/lib/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
  17. package/lib/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
  18. package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
  19. package/lib/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
  20. package/lib/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
  21. package/lib/memorax-code-backend/dist/clients/claude/memory-hook-runtime.js +21 -2
  22. package/lib/memorax-code-backend/dist/clients/codex/memory-hook-runtime.js +23 -3
  23. package/lib/memorax-code-backend/dist/clients/opencode/memory-hook-runtime.js +19 -1
  24. package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +17 -16
  25. package/lib/memorax-code-backend/dist/lifecycle/backend/service.js +1 -0
  26. package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +81 -22
  27. package/lib/memorax-code-backend/dist/memorax-cli.js +3 -0
  28. package/lib/memorax-code-backend/dist/memory/automatic-retrieval.js +8 -2
  29. package/lib/memorax-code-backend/dist/memory/automatic-writeback.js +13 -1
  30. package/lib/memorax-code-backend/dist/memory/cli.js +10 -0
  31. package/lib/memorax-code-backend/dist/memory/quota-notice.js +208 -0
  32. package/lib/memorax-code-backend/dist/memory/service.js +11 -0
  33. package/lib/memorax-code-backend/dist/provider/memorax/adapter.js +4 -2
  34. package/lib/memorax-code-backend/dist/provider/memorax/http.js +10 -3
  35. package/lib/memorax-code-backend/dist/provider/memorax/quota.js +26 -0
  36. package/lib/memorax-code-backend/package.json +1 -1
  37. package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  38. package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  39. package/lib/memorax-code-claude-adapter/package.json +1 -1
  40. package/lib/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -3
  41. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  42. package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  43. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
  44. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
  45. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +5 -1
  46. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +36 -3
  47. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/linux-secret-service.mjs +110 -0
  48. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/macos-keychain.mjs +224 -0
  49. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/secure-command.mjs +340 -0
  50. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.d.mts +82 -0
  51. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-record.mjs +183 -0
  52. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.d.mts +82 -0
  53. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/trial-credential-store.mjs +271 -0
  54. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/credentials/windows-dpapi.mjs +440 -0
  55. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +7 -4
  56. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.d.mts +81 -0
  57. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/setup-completion.mjs +154 -0
  58. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
  59. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -3
  60. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  61. package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  62. package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
  63. package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
  64. package/lib/memorax-code-codex-adapter/package.json +1 -1
  65. package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +2 -0
  66. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  67. package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  68. package/lib/memorax-code-dsh-adapter/hooks/repo-memory-job.mjs +5 -2
  69. package/lib/memorax-code-dsh-adapter/package.json +1 -1
  70. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  71. package/lib/memorax-code-dsh-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  72. package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +158 -7
  73. package/lib/memorax-code-dsh-adapter/src/runtime-state.mjs +21 -0
  74. package/lib/memorax-code-opencode-adapter/package.json +1 -1
  75. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-add.md +2 -0
  76. package/lib/memorax-code-opencode-adapter/skills/memorax-code/references/memorax-search.md +4 -2
  77. package/lib/memorax-code-opencode-adapter/src/plugin.mjs +20 -0
  78. package/lib/package-transition.mjs +312 -0
  79. package/lib/resolve-codex-command.mjs +32 -6
  80. package/lib/setup-memory-preferences.mjs +113 -0
  81. package/lib/setup-reconcile.mjs +128 -0
  82. package/lib/trial-plugin-mark.mjs +171 -0
  83. package/lib/trial-provision-client.mjs +307 -0
  84. package/lib/trial-provision-flow.mjs +215 -0
  85. package/lib/trial-setup.mjs +53 -0
  86. package/package.json +1 -1
@@ -23,10 +23,11 @@ export async function runMemorySkillReminderHook(options, hookInput) {
23
23
  const result = await evaluateMemorySkillReminder(options, input);
24
24
  if (!result) return;
25
25
  process.stdout.write(`${JSON.stringify({
26
- hookSpecificOutput: {
26
+ ...(result.systemMessage ? { systemMessage: result.systemMessage } : {}),
27
+ ...(result.additionalContext ? { hookSpecificOutput: {
27
28
  hookEventName: "UserPromptSubmit",
28
29
  additionalContext: result.additionalContext,
29
- },
30
+ } } : {}),
30
31
  })}\n`);
31
32
  if (result.reminder) await notifyReminder(options, result.reminder);
32
33
  } catch (error) {
@@ -80,7 +81,8 @@ export async function evaluateMemorySkillReminder(options, input) {
80
81
  const { memoryReminderDue, supplementalReminderDue } = update;
81
82
 
82
83
  const baseAdditionalContext = stringOption(options.baseAdditionalContext);
83
- if (!baseAdditionalContext && !memoryReminderDue && !supplementalReminderDue) return undefined;
84
+ const systemMessage = stringOption(options.systemMessage);
85
+ if (!baseAdditionalContext && !memoryReminderDue && !supplementalReminderDue && !systemMessage) return undefined;
84
86
  const cadenceReminderContext = memoryReminderDue
85
87
  ? await buildCadenceReminderContext(options, input)
86
88
  : undefined;
@@ -97,7 +99,8 @@ export async function evaluateMemorySkillReminder(options, input) {
97
99
  ...(supplementalReminderDue ? ["post_compaction"] : []),
98
100
  ];
99
101
  return {
100
- additionalContext,
102
+ ...(systemMessage ? { systemMessage } : {}),
103
+ ...(additionalContext ? { additionalContext } : {}),
101
104
  ...(reminderContext ? {
102
105
  reminder: {
103
106
  sessionId,
@@ -0,0 +1,81 @@
1
+ import type { JsonFileLockOptions } from "./config-utils.mjs";
2
+ import {
3
+ RuntimeRecordError,
4
+ type RuntimeRecordWriteResult,
5
+ type RuntimeRecordWriteRuntime,
6
+ } from "./runtime-record.mjs";
7
+
8
+ export type SetupCompletionRecord = Readonly<{
9
+ version: 1;
10
+ state: "complete";
11
+ completedAt: string;
12
+ completedByVersion: string;
13
+ }>;
14
+
15
+ export type SetupCompletionRecordInvalidReason =
16
+ | "unreadable"
17
+ | "malformed_json"
18
+ | "invalid_record"
19
+ | "invalid_version"
20
+ | "unknown_fields"
21
+ | "invalid_state"
22
+ | "invalid_completed_at"
23
+ | "invalid_completed_by_version";
24
+
25
+ export type SetupCompletionRecordState =
26
+ | Readonly<{ status: "absent" }>
27
+ | Readonly<{ status: "valid"; record: SetupCompletionRecord }>
28
+ | Readonly<{ status: "invalid"; reason: SetupCompletionRecordInvalidReason }>
29
+ | Readonly<{ status: "unsupported"; version: number }>;
30
+
31
+ export type WritableSetupCompletionRecordState = Extract<
32
+ SetupCompletionRecordState,
33
+ { status: "absent" | "valid" }
34
+ >;
35
+
36
+ export type SetupCompletionClearResult = Readonly<{
37
+ path: string;
38
+ removed: boolean;
39
+ }>;
40
+
41
+ export type SetupCompletionLockMutation = Readonly<{
42
+ clear(): SetupCompletionClearResult;
43
+ }>;
44
+
45
+ export const SETUP_COMPLETION_RECORD_VERSION: 1;
46
+
47
+ export class SetupCompletionRecordError extends RuntimeRecordError {
48
+ constructor(
49
+ state: Extract<SetupCompletionRecordState, { status: "invalid" | "unsupported" }>,
50
+ path: string,
51
+ );
52
+ }
53
+
54
+ export function setupCompletionPath(memoraxCodeHome?: string): string;
55
+
56
+ export function readSetupCompletionRecord(
57
+ memoraxCodeHome?: string,
58
+ ): SetupCompletionRecordState;
59
+
60
+ export function writeSetupCompletionRecord(
61
+ options: {
62
+ memoraxCodeHome?: string;
63
+ completedAt: string;
64
+ completedByVersion: string;
65
+ },
66
+ runtime?: RuntimeRecordWriteRuntime,
67
+ ): RuntimeRecordWriteResult<SetupCompletionRecord>;
68
+
69
+ export function clearSetupCompletionRecord(
70
+ memoraxCodeHome?: string,
71
+ options?: JsonFileLockOptions,
72
+ ): Promise<SetupCompletionClearResult>;
73
+
74
+ export function withSetupCompletionLock<T>(
75
+ memoraxCodeHome: string,
76
+ operation: (
77
+ state: WritableSetupCompletionRecordState,
78
+ mutation: SetupCompletionLockMutation,
79
+ ) => T | Promise<T>,
80
+ options?: JsonFileLockOptions,
81
+ ): Promise<T>;
@@ -0,0 +1,154 @@
1
+ import { unlinkSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { withJsonFileLockAsync } from "./config-utils.mjs";
5
+ import {
6
+ readJsonRuntimeRecord,
7
+ RuntimeRecordError,
8
+ writePrivateJsonRecord,
9
+ } from "./runtime-record.mjs";
10
+
11
+ export const SETUP_COMPLETION_RECORD_VERSION = 1;
12
+
13
+ const SETUP_COMPLETION_RECORD_KEYS = new Set([
14
+ "version",
15
+ "state",
16
+ "completedAt",
17
+ "completedByVersion",
18
+ ]);
19
+ const ISO_TIMESTAMP_PATTERN = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{1,9})?(?:Z|[+-]\d{2}:\d{2})$/;
20
+
21
+ export class SetupCompletionRecordError extends RuntimeRecordError {
22
+ constructor(state, path) {
23
+ super({
24
+ name: "Setup completion record",
25
+ path,
26
+ state,
27
+ codePrefix: "SETUP_COMPLETION_RECORD",
28
+ recovery: state.status === "unsupported"
29
+ ? "upgrade MemoraX Code or restore a supported setup completion record"
30
+ : "inspect the setup completion record before replacing it",
31
+ });
32
+ this.name = "SetupCompletionRecordError";
33
+ }
34
+ }
35
+
36
+ export function setupCompletionPath(memoraxCodeHome = defaultMemoraxCodeHome()) {
37
+ return join(memoraxCodeHome, "runtime", "setup", "setup-completion.json");
38
+ }
39
+
40
+ export function readSetupCompletionRecord(memoraxCodeHome = defaultMemoraxCodeHome()) {
41
+ const path = setupCompletionPath(memoraxCodeHome);
42
+ const state = readJsonRuntimeRecord(path);
43
+ if (state.status !== "present") return state;
44
+ const value = state.value;
45
+ if (value.version !== SETUP_COMPLETION_RECORD_VERSION) {
46
+ if (Number.isSafeInteger(value.version) && value.version > 0) {
47
+ return { status: "unsupported", version: value.version };
48
+ }
49
+ return { status: "invalid", reason: "invalid_version" };
50
+ }
51
+ if (Object.keys(value).some((key) => !SETUP_COMPLETION_RECORD_KEYS.has(key))) {
52
+ return { status: "invalid", reason: "unknown_fields" };
53
+ }
54
+ if (value.state !== "complete") {
55
+ return { status: "invalid", reason: "invalid_state" };
56
+ }
57
+ const completedAt = isoTimestamp(value.completedAt);
58
+ if (!completedAt) return { status: "invalid", reason: "invalid_completed_at" };
59
+ const completedByVersion = nonEmptyString(value.completedByVersion);
60
+ if (!completedByVersion) {
61
+ return { status: "invalid", reason: "invalid_completed_by_version" };
62
+ }
63
+ return {
64
+ status: "valid",
65
+ record: {
66
+ version: SETUP_COMPLETION_RECORD_VERSION,
67
+ state: "complete",
68
+ completedAt,
69
+ completedByVersion,
70
+ },
71
+ };
72
+ }
73
+
74
+ export function writeSetupCompletionRecord(options, runtime) {
75
+ const memoraxCodeHome = nonEmptyString(options?.memoraxCodeHome) ?? defaultMemoraxCodeHome();
76
+ const completedAt = isoTimestamp(options?.completedAt);
77
+ if (!completedAt) {
78
+ throw new TypeError("Setup completion record requires a valid ISO completedAt timestamp");
79
+ }
80
+ const completedByVersion = nonEmptyString(options?.completedByVersion);
81
+ if (!completedByVersion) {
82
+ throw new TypeError("Setup completion record requires a non-empty completedByVersion");
83
+ }
84
+ const path = setupCompletionPath(memoraxCodeHome);
85
+ const existing = readSetupCompletionRecord(memoraxCodeHome);
86
+ if (existing.status === "invalid" || existing.status === "unsupported") {
87
+ throw new SetupCompletionRecordError(existing, path);
88
+ }
89
+ return writePrivateJsonRecord(path, {
90
+ version: SETUP_COMPLETION_RECORD_VERSION,
91
+ state: "complete",
92
+ completedAt,
93
+ completedByVersion,
94
+ }, {
95
+ ...runtime,
96
+ durableBoundary: memoraxCodeHome,
97
+ });
98
+ }
99
+
100
+ export async function clearSetupCompletionRecord(
101
+ memoraxCodeHome = defaultMemoraxCodeHome(),
102
+ options,
103
+ ) {
104
+ const home = nonEmptyString(memoraxCodeHome) ?? defaultMemoraxCodeHome();
105
+ return await withSetupCompletionLock(
106
+ home,
107
+ (_state, mutation) => mutation.clear(),
108
+ options,
109
+ );
110
+ }
111
+
112
+ export async function withSetupCompletionLock(
113
+ memoraxCodeHome,
114
+ operation,
115
+ options,
116
+ ) {
117
+ const home = nonEmptyString(memoraxCodeHome);
118
+ if (!home) throw new TypeError("Setup completion lock requires a MemoraX Code home");
119
+ if (typeof operation !== "function") {
120
+ throw new TypeError("Setup completion lock requires an operation");
121
+ }
122
+ const path = setupCompletionPath(home);
123
+ return await withJsonFileLockAsync(path, async () => {
124
+ let state = readSetupCompletionRecord(home);
125
+ if (state.status === "invalid" || state.status === "unsupported") {
126
+ throw new SetupCompletionRecordError(state, path);
127
+ }
128
+ return await operation(state, {
129
+ clear() {
130
+ if (state.status === "absent") return { path, removed: false };
131
+ unlinkSync(path);
132
+ state = { status: "absent" };
133
+ return { path, removed: true };
134
+ },
135
+ });
136
+ }, options);
137
+ }
138
+
139
+ function defaultMemoraxCodeHome() {
140
+ return join(homedir(), ".memorax-code");
141
+ }
142
+
143
+ function nonEmptyString(value) {
144
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
145
+ }
146
+
147
+ function isoTimestamp(value) {
148
+ const candidate = nonEmptyString(value);
149
+ return candidate
150
+ && ISO_TIMESTAMP_PATTERN.test(candidate)
151
+ && Number.isFinite(Date.parse(candidate))
152
+ ? candidate
153
+ : undefined;
154
+ }
@@ -1,5 +1,6 @@
1
1
  import { readClaudeInterruptedTranscriptTurn, readClaudeTranscriptTurn, } from "./transcript-turn.js";
2
2
  import { retrieveAutomaticMemoryContext } from "../../memory/automatic-retrieval.js";
3
+ import { claimQuotaNotice, createPendingQuotaNoticeRuntime, } from "../../memory/quota-notice.js";
3
4
  import { createAutomaticMemoryWritebackRuntime, } from "../../memory/automatic-writeback.js";
4
5
  import { createMemoryTurnCoordinator, } from "../../memory/turn-coordinator.js";
5
6
  import { createRepositoryMemorySessionRuntime, resolvedRepoMemoryWorktree, } from "../../memory/repository-session.js";
@@ -10,11 +11,20 @@ const DEFAULT_TRANSCRIPT_READ_ATTEMPTS = 6;
10
11
  const DEFAULT_TRANSCRIPT_RETRY_DELAY_MS = 100;
11
12
  export function createClaudeMemoryHookRuntime(options = {}) {
12
13
  const now = options.now ?? (() => Date.now());
14
+ const pendingQuotaNotice = options.pendingQuotaNotice ?? createPendingQuotaNoticeRuntime({
15
+ claimQuotaNotice: options.claimQuotaNotice,
16
+ diagnosticLogger: options.diagnosticLogger,
17
+ env: options.env,
18
+ });
19
+ const ownsPendingQuotaNotice = options.pendingQuotaNotice === undefined;
13
20
  const automaticWritebackRuntime = options.turnCoordinator
14
21
  ? undefined
15
22
  : options.automaticWriteback
16
23
  ? { enqueue: options.automaticWriteback }
17
- : createAutomaticMemoryWritebackRuntime({ diagnosticLogger: options.diagnosticLogger });
24
+ : createAutomaticMemoryWritebackRuntime({
25
+ diagnosticLogger: options.diagnosticLogger,
26
+ queueQuotaNotice: pendingQuotaNotice.queue,
27
+ });
18
28
  const turnCoordinator = options.turnCoordinator ?? createMemoryTurnCoordinator({
19
29
  automaticWriteback: automaticWritebackRuntime.enqueue,
20
30
  now,
@@ -86,14 +96,19 @@ export function createClaudeMemoryHookRuntime(options = {}) {
86
96
  env: options.env,
87
97
  now: () => new Date(now()),
88
98
  }), options.diagnosticLogger);
89
- if (!claimAutomaticRetrievalPrompt(automaticRetrievalPrompts, automaticRetrievalPromptLimit, turn.sessionId, turn.promptId)) {
99
+ const processTurnStart = claimAutomaticRetrievalPrompt(automaticRetrievalPrompts, automaticRetrievalPromptLimit, turn.sessionId, turn.promptId);
100
+ if (!processTurnStart) {
90
101
  return {
91
102
  ok: true,
92
103
  ...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
93
104
  };
94
105
  }
106
+ const pendingUserNotice = repositoryMemory.ok
107
+ ? await pendingQuotaNotice.claim(repositoryMemory.memory.config)
108
+ : undefined;
95
109
  const retrieval = await retrieveAutomaticMemoryContext({
96
110
  diagnosticLogger: options.diagnosticLogger,
111
+ claimQuotaNotice: options.claimQuotaNotice ?? claimQuotaNotice,
97
112
  env: options.env ?? process.env,
98
113
  fetchImpl: options.fetchImpl,
99
114
  memoryObservability: options.memoryObservability,
@@ -103,10 +118,12 @@ export function createClaudeMemoryHookRuntime(options = {}) {
103
118
  sessionKey: turn.sessionId,
104
119
  traceContext: turn.traceContext,
105
120
  });
121
+ const userNotice = [pendingUserNotice, retrieval.userNotice].filter(Boolean).join("\n");
106
122
  return {
107
123
  ok: true,
108
124
  ...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
109
125
  ...(retrieval.context ? { additionalContext: retrieval.context } : {}),
126
+ ...(userNotice ? { userNotice } : {}),
110
127
  };
111
128
  },
112
129
  async writeback(command) {
@@ -195,6 +212,8 @@ export function createClaudeMemoryHookRuntime(options = {}) {
195
212
  if (ownsRepositoryMemorySession)
196
213
  repositoryMemorySession.close();
197
214
  automaticWritebackRuntime?.close?.();
215
+ if (ownsPendingQuotaNotice)
216
+ pendingQuotaNotice.close();
198
217
  },
199
218
  };
200
219
  }
@@ -1,6 +1,7 @@
1
1
  import { readCodexInterruptedRolloutTurn, readCodexRolloutTurn, } from "./rollout-turn.js";
2
2
  import { createAutomaticMemoryWritebackRuntime, } from "../../memory/automatic-writeback.js";
3
3
  import { retrieveAutomaticMemoryContext } from "../../memory/automatic-retrieval.js";
4
+ import { claimQuotaNotice, createPendingQuotaNoticeRuntime, } from "../../memory/quota-notice.js";
4
5
  import { readCodexSessionTurnIndex } from "./session-turn-index.js";
5
6
  import { resolveCodexWorkspaceRoot } from "./workspace-links.js";
6
7
  import { createMemoryTurnCoordinator, } from "../../memory/turn-coordinator.js";
@@ -10,11 +11,20 @@ import { markCurrentCodexTurnOutcome, readCurrentCodexTurn, readOpenCodexTurn, r
10
11
  const CODEX_MEMORY_TURN_CLIENT = "codex";
11
12
  export function createCodexMemoryHookRuntime(options = {}) {
12
13
  const now = options.now ?? (() => Date.now());
14
+ const pendingQuotaNotice = options.pendingQuotaNotice ?? createPendingQuotaNoticeRuntime({
15
+ claimQuotaNotice: options.claimQuotaNotice,
16
+ diagnosticLogger: options.diagnosticLogger,
17
+ env: options.env,
18
+ });
19
+ const ownsPendingQuotaNotice = options.pendingQuotaNotice === undefined;
13
20
  const automaticWritebackRuntime = options.turnCoordinator
14
21
  ? undefined
15
22
  : options.automaticWriteback
16
23
  ? { enqueue: options.automaticWriteback }
17
- : createAutomaticMemoryWritebackRuntime({ diagnosticLogger: options.diagnosticLogger });
24
+ : createAutomaticMemoryWritebackRuntime({
25
+ diagnosticLogger: options.diagnosticLogger,
26
+ queueQuotaNotice: pendingQuotaNotice.queue,
27
+ });
18
28
  const turnCoordinator = options.turnCoordinator ?? createMemoryTurnCoordinator({
19
29
  automaticWriteback: automaticWritebackRuntime.enqueue,
20
30
  now,
@@ -91,15 +101,21 @@ export function createCodexMemoryHookRuntime(options = {}) {
91
101
  env: options.env,
92
102
  now: () => new Date(now()),
93
103
  }), options.diagnosticLogger);
94
- if (!turn.turnId
95
- || !claimAutomaticRetrievalTurn(automaticRetrievalTurns, automaticRetrievalTurnLimit, turn.sessionId, turn.turnId)) {
104
+ const processTurnStart = turn.turnId
105
+ ? claimAutomaticRetrievalTurn(automaticRetrievalTurns, automaticRetrievalTurnLimit, turn.sessionId, turn.turnId)
106
+ : false;
107
+ if (!processTurnStart) {
96
108
  return {
97
109
  ok: true,
98
110
  ...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
99
111
  };
100
112
  }
113
+ const pendingUserNotice = repositoryMemory.ok
114
+ ? await pendingQuotaNotice.claim(repositoryMemory.memory.config)
115
+ : undefined;
101
116
  const retrieval = await retrieveAutomaticMemoryContext({
102
117
  diagnosticLogger: options.diagnosticLogger,
118
+ claimQuotaNotice: options.claimQuotaNotice ?? claimQuotaNotice,
103
119
  env: options.env ?? process.env,
104
120
  fetchImpl: options.fetchImpl,
105
121
  memoryObservability: options.memoryObservability,
@@ -109,10 +125,12 @@ export function createCodexMemoryHookRuntime(options = {}) {
109
125
  sessionKey: turn.sessionId,
110
126
  traceContext: turn.traceContext,
111
127
  });
128
+ const userNotice = [pendingUserNotice, retrieval.userNotice].filter(Boolean).join("\n");
112
129
  return {
113
130
  ok: true,
114
131
  ...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
115
132
  ...(retrieval.context ? { additionalContext: retrieval.context } : {}),
133
+ ...(userNotice ? { userNotice } : {}),
116
134
  };
117
135
  },
118
136
  async writeback(command) {
@@ -222,6 +240,8 @@ export function createCodexMemoryHookRuntime(options = {}) {
222
240
  if (ownsRepositoryMemorySession)
223
241
  repositoryMemorySession.close();
224
242
  automaticWritebackRuntime?.close?.();
243
+ if (ownsPendingQuotaNotice)
244
+ pendingQuotaNotice.close();
225
245
  },
226
246
  };
227
247
  }
@@ -1,5 +1,6 @@
1
1
  import { createAutomaticMemoryWritebackRuntime, } from "../../memory/automatic-writeback.js";
2
2
  import { retrieveAutomaticMemoryContext } from "../../memory/automatic-retrieval.js";
3
+ import { claimQuotaNotice, createPendingQuotaNoticeRuntime, } from "../../memory/quota-notice.js";
3
4
  import { createMemoryTurnCoordinator, } from "../../memory/turn-coordinator.js";
4
5
  import { createRepositoryMemorySessionRuntime, resolvedRepoMemoryWorktree, } from "../../memory/repository-session.js";
5
6
  import { traceContextFromOpenCodeHookBody } from "../../trace/context.js";
@@ -8,11 +9,20 @@ import { openCodeMessageTurn, } from "./message-turn.js";
8
9
  const OPENCODE_MEMORY_TURN_CLIENT = "opencode";
9
10
  export function createOpenCodeMemoryHookRuntime(options = {}) {
10
11
  const now = options.now ?? (() => Date.now());
12
+ const pendingQuotaNotice = options.pendingQuotaNotice ?? createPendingQuotaNoticeRuntime({
13
+ claimQuotaNotice: options.claimQuotaNotice,
14
+ diagnosticLogger: options.diagnosticLogger,
15
+ env: options.env,
16
+ });
17
+ const ownsPendingQuotaNotice = options.pendingQuotaNotice === undefined;
11
18
  const automaticWritebackRuntime = options.turnCoordinator
12
19
  ? undefined
13
20
  : options.automaticWriteback
14
21
  ? { enqueue: options.automaticWriteback }
15
- : createAutomaticMemoryWritebackRuntime({ diagnosticLogger: options.diagnosticLogger });
22
+ : createAutomaticMemoryWritebackRuntime({
23
+ diagnosticLogger: options.diagnosticLogger,
24
+ queueQuotaNotice: pendingQuotaNotice.queue,
25
+ });
16
26
  const turnCoordinator = options.turnCoordinator ?? createMemoryTurnCoordinator({
17
27
  automaticWriteback: automaticWritebackRuntime.enqueue,
18
28
  now,
@@ -85,8 +95,12 @@ export function createOpenCodeMemoryHookRuntime(options = {}) {
85
95
  break;
86
96
  retrievalTurns.delete(oldest);
87
97
  }
98
+ const pendingUserNotice = repositoryMemory.ok
99
+ ? await pendingQuotaNotice.claim(repositoryMemory.memory.config)
100
+ : undefined;
88
101
  const retrieval = await retrieveAutomaticMemoryContext({
89
102
  diagnosticLogger: options.diagnosticLogger,
103
+ claimQuotaNotice: options.claimQuotaNotice ?? claimQuotaNotice,
90
104
  env: options.env ?? process.env,
91
105
  fetchImpl: options.fetchImpl,
92
106
  memoryObservability: options.memoryObservability,
@@ -96,10 +110,12 @@ export function createOpenCodeMemoryHookRuntime(options = {}) {
96
110
  sessionKey: command.sessionId,
97
111
  traceContext,
98
112
  });
113
+ const userNotice = [pendingUserNotice, retrieval.userNotice].filter(Boolean).join("\n");
99
114
  return {
100
115
  ok: true,
101
116
  ...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
102
117
  ...(retrieval.context ? { additionalContext: retrieval.context } : {}),
118
+ ...(userNotice ? { userNotice } : {}),
103
119
  };
104
120
  },
105
121
  async writeback(command) {
@@ -229,6 +245,8 @@ export function createOpenCodeMemoryHookRuntime(options = {}) {
229
245
  if (ownsRepositoryMemorySession)
230
246
  repositoryMemorySession.close();
231
247
  automaticWritebackRuntime?.close?.();
248
+ if (ownsPendingQuotaNotice)
249
+ pendingQuotaNotice.close();
232
250
  },
233
251
  };
234
252
  }
@@ -471,7 +471,8 @@ function isRecord(value) {
471
471
  }
472
472
  function printMemoraxCodeStatus(report) {
473
473
  const backend = report.backend;
474
- backendLog(`MemoraX Code Backend status: ${report.ok ? blueBold("Enabled") : redBold("Unavailable")}`);
474
+ const degraded = report.degraded === true;
475
+ backendLog(`MemoraX Code Backend status: ${report.ok ? blueBold(degraded ? "Enabled (degraded)" : "Enabled") : redBold("Unavailable")}`);
475
476
  backendLog(report.ok
476
477
  ? green("MemoraX Code is ready for new client sessions.")
477
478
  : red("MemoraX Code is not ready for new client sessions."));
@@ -567,7 +568,7 @@ export async function runBackendTokenCommand(serviceOptions, argv, recordWriteRu
567
568
  }
568
569
  }
569
570
  function printLifecycleResult(report) {
570
- const degraded = report.backend?.degraded === true;
571
+ const degraded = report.degraded === true || report.backend?.degraded === true;
571
572
  backendLog(`${lifecycleActionLabel(report.action)}: ${report.ok ? green(degraded ? "ok (degraded)" : "ok") : red("needs attention")}`);
572
573
  if (report.message)
573
574
  backendLog(report.message);
@@ -651,29 +652,29 @@ function lifecycleGuidance(report) {
651
652
  "Adapters were not changed for this command.",
652
653
  ];
653
654
  }
654
- const optionalDshSkipped = isOptionalUnavailableDshAdapter(report.dshAdapter);
655
+ const optionalDshUnavailable = isOptionalUnavailableDshAdapter(report.dshAdapter);
655
656
  if ((!report.codexAdapter || isAdapterReady(report.codexAdapter))
656
657
  && (!report.claudeAdapter || isAdapterReady(report.claudeAdapter))
657
658
  && (!report.dshAdapter
658
659
  || isAdapterReady(report.dshAdapter)
659
- || optionalDshSkipped)
660
+ || optionalDshUnavailable)
660
661
  && (!report.opencodeAdapter || isAdapterReady(report.opencodeAdapter))) {
661
662
  return [
662
- green(optionalDshSkipped
663
+ green(optionalDshUnavailable
663
664
  && !report.codexAdapter
664
665
  && !report.claudeAdapter
665
666
  && !report.opencodeAdapter
666
- ? "Backend is running; the optional DSH integration was skipped."
667
+ ? "Backend is running; the optional DSH integration is unavailable."
667
668
  : "Backend is running and available client integrations are enabled."),
668
- ...(optionalDshSkipped
669
- ? [`DSH integration was skipped: ${report.dshAdapter?.reason ?? "not available"}.`]
669
+ ...(optionalDshUnavailable
670
+ ? [`DSH integration is unavailable: ${report.dshAdapter?.reason ?? "not available"}.`]
670
671
  : []),
671
672
  ...(report.codexAdapter || report.claudeAdapter || report.opencodeAdapter
672
673
  ? [
673
674
  green("Existing sessions with the stable plugin shell select the active runtime on their next user prompt."),
674
675
  "Restart or refresh a client only if its plugin shell changed or MemoraX Code is not active on the next prompt.",
675
676
  ]
676
- : optionalDshSkipped
677
+ : optionalDshUnavailable
677
678
  ? []
678
679
  : [green("New DSH sessions can use MemoraX Code through the active Profile integration.")]),
679
680
  ];
@@ -741,15 +742,15 @@ function lifecycleGuidance(report) {
741
742
  }
742
743
  function statusGuidance(report) {
743
744
  if (report.ok) {
744
- const optionalDshSkipped = isOptionalUnavailableDshAdapter(report.dshAdapter);
745
+ const optionalDshUnavailable = isOptionalUnavailableDshAdapter(report.dshAdapter);
745
746
  if (report.dshAdapter
746
747
  && !report.codexAdapter
747
748
  && !report.claudeAdapter
748
749
  && !report.opencodeAdapter) {
749
- if (optionalDshSkipped) {
750
+ if (optionalDshUnavailable) {
750
751
  return [
751
- green("Backend is running; the optional DSH integration was skipped."),
752
- `DSH integration was skipped: ${report.dshAdapter.reason ?? "not available"}.`,
752
+ green("Backend is running; the optional DSH integration is unavailable."),
753
+ `DSH integration is unavailable: ${report.dshAdapter.reason ?? "not available"}.`,
753
754
  ];
754
755
  }
755
756
  return [
@@ -759,8 +760,8 @@ function statusGuidance(report) {
759
760
  }
760
761
  return [
761
762
  green("MemoraX Code is ready; sessions with the stable plugin shell use the active Hook runtime on their next user prompt."),
762
- ...(optionalDshSkipped
763
- ? [`DSH integration was skipped: ${report.dshAdapter?.reason ?? "not available"}.`]
763
+ ...(optionalDshUnavailable
764
+ ? [`DSH integration is unavailable: ${report.dshAdapter?.reason ?? "not available"}.`]
764
765
  : []),
765
766
  "Restart or refresh a client only if its plugin shell was installed, changed, or newly enabled, or MemoraX Code is not active on the next prompt.",
766
767
  ];
@@ -877,7 +878,7 @@ function claudeAdapterStatusLine(report, codexAdapter) {
877
878
  }
878
879
  function dshAdapterStatusLine(report) {
879
880
  if (isOptionalUnavailableDshAdapter(report)) {
880
- return `skipped ${report.reason ?? "not-detected"}`;
881
+ return `unavailable ${report.reason ?? "not-detected"}`;
881
882
  }
882
883
  const version = report.version ?? report.dshVersion;
883
884
  const tested = version && report.dshVersionTested !== undefined
@@ -136,6 +136,7 @@ export async function startBackendService(options = {}, runtime = {}) {
136
136
  let child;
137
137
  try {
138
138
  child = (runtime.spawnProcess ?? spawn)(process.execPath, [serverPath, "--memorax-code-backend-instance", instanceId], {
139
+ cwd: serviceDir(options),
139
140
  detached: true,
140
141
  env: withLoopbackProxyBypass({
141
142
  ...process.env,