@memorax/memorax-code 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/memorax-code-plugin-postinstall.mjs +88 -21
- package/docs/configuration.md +15 -0
- package/docs/troubleshooting.md +26 -0
- package/lib/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +1 -1
- package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs +38 -0
- package/lib/memorax-code-backend/dist/automatic-memory-writeback.js +7 -0
- package/lib/memorax-code-backend/dist/claude-memory-hook-runtime.js +10 -3
- package/lib/memorax-code-backend/dist/codex-memory-hook-runtime.js +10 -3
- package/lib/memorax-code-backend/dist/memorax-cli.js +2 -0
- package/lib/memorax-code-backend/dist/memory-cli.js +24 -2
- package/lib/memorax-code-backend/dist/memory-service.js +3 -1
- package/lib/memorax-code-backend/dist/memory-turn-coordinator.js +6 -3
- package/lib/memorax-code-backend/dist/memory-viewer-icon.js +1 -0
- package/lib/memorax-code-backend/dist/memory-viewer-user-html.js +6 -5
- package/lib/memorax-code-backend/dist/memory-writeback-buffer.js +29 -0
- package/lib/memorax-code-backend/dist/repository-memory-context.js +24 -3
- package/lib/memorax-code-backend/dist/repository-memory-scope.js +81 -33
- package/lib/memorax-code-backend/dist/server-cli.js +30 -7
- package/lib/memorax-code-backend/dist/server-memory-viewer.js +11 -0
- package/lib/memorax-code-backend/package.json +1 -1
- package/lib/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -0
- package/lib/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +12 -13
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +4 -0
- package/lib/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +3 -1
- package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/.claude-plugin/plugin.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-auto-build.mjs +38 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/package.json +1 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/runtime-hooks/memory-turn.mjs +5 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/SKILL.md +12 -13
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-add.md +4 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/skills/memorax-code/references/memorax-search.md +3 -1
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +1 -0
- package/lib/memorax-code-codex-adapter/.codex-plugin/plugin.json +1 -1
- package/lib/memorax-code-codex-adapter/hooks/runtime-shell.json +1 -1
- package/lib/memorax-code-codex-adapter/package.json +1 -1
- package/lib/memorax-code-codex-adapter/runtime-hooks/memory-skill-reminder.mjs +6 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/SKILL.md +12 -13
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-add.md +4 -0
- package/lib/memorax-code-codex-adapter/skills/memorax-code/references/memorax-search.md +3 -1
- package/lib/resolve-claude-command.mjs +154 -1
- package/lib/resolve-codex-command.mjs +56 -1
- package/package.json +1 -1
|
@@ -48,7 +48,7 @@ const codexCommand = codexRuntime.command;
|
|
|
48
48
|
const claudeRuntime = ensureClaudeCommandEnv();
|
|
49
49
|
const claudeCommand = claudeRuntime.command;
|
|
50
50
|
const updatePostinstall = postinstallUpdateMode();
|
|
51
|
-
const scriptedAnswers = (canPrompt() ||
|
|
51
|
+
const scriptedAnswers = (canPrompt() || canPromptForUpdate()) && process.stdin.isTTY !== true
|
|
52
52
|
? parseScriptedAnswers(readFileSync(0, "utf8"))
|
|
53
53
|
: undefined;
|
|
54
54
|
const previousClients = readPersistedClientSelection();
|
|
@@ -70,20 +70,31 @@ try {
|
|
|
70
70
|
process.exit(0);
|
|
71
71
|
}
|
|
72
72
|
runCommonPreflight();
|
|
73
|
-
const requestedClients =
|
|
73
|
+
const requestedClients = ["codex", "claude"];
|
|
74
74
|
const codexPreflight = requestedClients.includes("codex") && !skipCodexPluginInstall
|
|
75
|
-
? runCodexPreflight(
|
|
75
|
+
? runCodexPreflight({
|
|
76
|
+
integrationSelected: !updatePostinstall
|
|
77
|
+
|| previousClients === undefined
|
|
78
|
+
|| previousClients.includes("codex"),
|
|
79
|
+
})
|
|
76
80
|
: { ok: true, pluginCache: { marketplaceName: CLI_MARKETPLACE_NAME, versions: [] } };
|
|
77
81
|
const claudePreflight = requestedClients.includes("claude") && !skipClaudeAdapterInstall
|
|
78
|
-
? runClaudePreflight(
|
|
82
|
+
? runClaudePreflight({
|
|
83
|
+
integrationSelected: !updatePostinstall
|
|
84
|
+
|| previousClients === undefined
|
|
85
|
+
|| previousClients.includes("claude"),
|
|
86
|
+
})
|
|
79
87
|
: { ok: true };
|
|
80
|
-
const
|
|
88
|
+
const detectedClients = requestedClients.filter((client) => {
|
|
81
89
|
if (client === "codex") return !skipCodexPluginInstall && codexPreflight.ok;
|
|
82
90
|
return !skipClaudeAdapterInstall && claudePreflight.ok;
|
|
83
91
|
});
|
|
84
|
-
const selectedClients = previousClients
|
|
85
|
-
|
|
86
|
-
|
|
92
|
+
const selectedClients = updatePostinstall && previousClients !== undefined
|
|
93
|
+
? await chooseUpdateClients(previousClients, detectedClients, scriptedAnswers)
|
|
94
|
+
: detectedClients;
|
|
95
|
+
const installClients = detectedClients.filter((client) => selectedClients.includes(client));
|
|
96
|
+
if (updatePostinstall && previousClients !== undefined) {
|
|
97
|
+
log(clientSelectionMessage(selectedClients));
|
|
87
98
|
} else {
|
|
88
99
|
log(detectedClientMessage(installClients));
|
|
89
100
|
}
|
|
@@ -110,7 +121,11 @@ if (memoraxConfigResult === "configured") {
|
|
|
110
121
|
}
|
|
111
122
|
const codexClientEnabled = installClients.includes("codex");
|
|
112
123
|
const claudeClientEnabled = installClients.includes("claude");
|
|
113
|
-
const
|
|
124
|
+
const codexClientNewlyEnabled = codexClientEnabled
|
|
125
|
+
&& updatePostinstall
|
|
126
|
+
&& previousClients !== undefined
|
|
127
|
+
&& !previousClients.includes("codex");
|
|
128
|
+
const codexHooksBeforeUpdate = codexClientEnabled && updatePostinstall && !codexClientNewlyEnabled
|
|
114
129
|
? inspectCodexPluginHooksForUpdate()
|
|
115
130
|
: undefined;
|
|
116
131
|
const result = codexClientEnabled
|
|
@@ -122,8 +137,13 @@ if (codexClientEnabled && result.status !== 0 && process.env.npm_lifecycle_event
|
|
|
122
137
|
}
|
|
123
138
|
|
|
124
139
|
if (codexClientEnabled && result.status === 0) {
|
|
125
|
-
if (
|
|
126
|
-
|
|
140
|
+
if (codexClientNewlyEnabled) {
|
|
141
|
+
await maybeActivateCodexPluginHooks(scriptedAnswers, { updatePrompt: true });
|
|
142
|
+
} else if (updatePostinstall) {
|
|
143
|
+
await maybeTrustUpdatedCodexPluginHooks(scriptedAnswers, codexHooksBeforeUpdate);
|
|
144
|
+
} else {
|
|
145
|
+
await maybeActivateCodexPluginHooks(scriptedAnswers);
|
|
146
|
+
}
|
|
127
147
|
}
|
|
128
148
|
const skipCodexAdapter = !codexClientEnabled;
|
|
129
149
|
const skipClaudeAdapter = !claudeClientEnabled;
|
|
@@ -194,6 +214,45 @@ async function maybeConfigureMemoraxMemory(scriptedAnswers) {
|
|
|
194
214
|
}
|
|
195
215
|
}
|
|
196
216
|
|
|
217
|
+
async function chooseUpdateClients(previousClients, detectedClients, scriptedAnswers) {
|
|
218
|
+
const selected = new Set(previousClients);
|
|
219
|
+
const availableDisabledClients = detectedClients.filter((client) => !selected.has(client));
|
|
220
|
+
if (availableDisabledClients.length === 0) return [...previousClients];
|
|
221
|
+
|
|
222
|
+
if (!canPromptForUpdate()) {
|
|
223
|
+
for (const client of availableDisabledClients) {
|
|
224
|
+
const label = client === "codex" ? "Codex" : "Claude Code";
|
|
225
|
+
log(`${label} runtime is available, but its integration remains disabled because this update cannot prompt. Rerun \`memorax-code update\` from an interactive terminal to choose whether to enable it.`);
|
|
226
|
+
}
|
|
227
|
+
return [...previousClients];
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let rl;
|
|
231
|
+
try {
|
|
232
|
+
for (const client of availableDisabledClients) {
|
|
233
|
+
const label = client === "codex" ? "Codex" : "Claude Code";
|
|
234
|
+
const question = `${label} runtime is available, but its integration is disabled in [clients]. Enable it now? [Y/n]`;
|
|
235
|
+
let answer;
|
|
236
|
+
if (scriptedAnswers) {
|
|
237
|
+
log(question);
|
|
238
|
+
answer = String(scriptedAnswers.shift() ?? "").trim();
|
|
239
|
+
} else {
|
|
240
|
+
rl ??= createInterface({ input: process.stdin, output: process.stderr });
|
|
241
|
+
answer = (await rl.question(`${PREFIX} ${question} `)).trim();
|
|
242
|
+
}
|
|
243
|
+
if (/^n(?:o)?$/i.test(answer)) {
|
|
244
|
+
log(`Keeping the ${label} integration disabled.`);
|
|
245
|
+
} else {
|
|
246
|
+
selected.add(client);
|
|
247
|
+
logGreen(`Enabling the ${label} integration.`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
} finally {
|
|
251
|
+
rl?.close();
|
|
252
|
+
}
|
|
253
|
+
return ["codex", "claude"].filter((client) => selected.has(client));
|
|
254
|
+
}
|
|
255
|
+
|
|
197
256
|
async function configureMemoraxMemoryFromAnswers(answers) {
|
|
198
257
|
if (answers.length === 0) {
|
|
199
258
|
log(`No MemoraX connection response was received. Register at ${MEMORAX_ACCOUNT_URL}, then edit \`~/.memorax-code/config.toml\` or rerun interactively with \`--foreground-scripts\`.`);
|
|
@@ -350,7 +409,7 @@ async function maybeTrustUpdatedCodexPluginHooks(scriptedAnswers, previousHooks)
|
|
|
350
409
|
if (report.hooks.length === 0) return "unchanged";
|
|
351
410
|
|
|
352
411
|
printUpdatedCodexHooks(report.hooks, previousHooks);
|
|
353
|
-
if (!
|
|
412
|
+
if (!canPromptForUpdate()) {
|
|
354
413
|
warnUpdatedHookTrustSkipped("This update is running without an interactive terminal, so the new or changed Hooks remain untrusted.");
|
|
355
414
|
return "skipped";
|
|
356
415
|
}
|
|
@@ -470,8 +529,8 @@ function warnUpdatedHookTrustSkipped(message) {
|
|
|
470
529
|
logRed("Review and authorize the current MemoraX Code Codex Hooks with `memorax-code codex-plugin trust-hooks`.");
|
|
471
530
|
}
|
|
472
531
|
|
|
473
|
-
async function maybeActivateCodexPluginHooks(scriptedAnswers) {
|
|
474
|
-
if (!canPrompt()) {
|
|
532
|
+
async function maybeActivateCodexPluginHooks(scriptedAnswers, { updatePrompt = false } = {}) {
|
|
533
|
+
if (!(updatePrompt ? canPromptForUpdate() : canPrompt())) {
|
|
475
534
|
log("Codex hook activation was not prompted. Run `memorax-code codex-plugin activate --yes` later to activate and trust MemoraX Code Codex Adapter hooks.");
|
|
476
535
|
return "skipped";
|
|
477
536
|
}
|
|
@@ -512,7 +571,7 @@ function canPrompt() {
|
|
|
512
571
|
return canPromptOnStderr();
|
|
513
572
|
}
|
|
514
573
|
|
|
515
|
-
function
|
|
574
|
+
function canPromptForUpdate() {
|
|
516
575
|
return updatePostinstall && canPromptOnStderr();
|
|
517
576
|
}
|
|
518
577
|
|
|
@@ -705,7 +764,7 @@ function tomlString(value) {
|
|
|
705
764
|
function runCommonPreflight() {
|
|
706
765
|
log("Checking local install state...");
|
|
707
766
|
if (updatePostinstall) {
|
|
708
|
-
log("Package update detected; refreshing MemoraX Code assets
|
|
767
|
+
log("Package update detected; refreshing MemoraX Code assets and checking client availability.");
|
|
709
768
|
}
|
|
710
769
|
const memoraxCodeVersion = runNodeMemoraxCodeCommand(["--version"], { print: false });
|
|
711
770
|
log(`MemoraX Code backend package: ${commandSummary(memoraxCodeVersion) ?? packageVersionSummary()}`);
|
|
@@ -718,7 +777,7 @@ function runCommonPreflight() {
|
|
|
718
777
|
return {};
|
|
719
778
|
}
|
|
720
779
|
|
|
721
|
-
function runCodexPreflight() {
|
|
780
|
+
function runCodexPreflight({ integrationSelected = true } = {}) {
|
|
722
781
|
const version = runExternalCommand(codexCommand, ["--version"], { print: false, timeout: 10_000 });
|
|
723
782
|
const runtimeLabel = codexRuntime.source === "app-bundled"
|
|
724
783
|
? "Codex App runtime"
|
|
@@ -732,16 +791,24 @@ function runCodexPreflight() {
|
|
|
732
791
|
const pluginCache = installedPluginCache();
|
|
733
792
|
log(`Existing Codex plugin cache: ${pluginCache.versions.length > 0 ? `found (${pluginCache.versions.join(", ")})` : "not installed"}`);
|
|
734
793
|
log(`Codex client process: ${codexClientRunning() ? "running" : "not detected"}`);
|
|
735
|
-
log(
|
|
794
|
+
log(integrationSelected
|
|
795
|
+
? "Keeping Codex provider config unchanged and enabling the shared memory hook integration."
|
|
796
|
+
: "Keeping Codex provider config unchanged while checking whether to enable its integration.");
|
|
736
797
|
return { ok: true, pluginCache };
|
|
737
798
|
}
|
|
738
799
|
|
|
739
|
-
function runClaudePreflight() {
|
|
800
|
+
function runClaudePreflight({ integrationSelected = true } = {}) {
|
|
740
801
|
const version = runExternalCommand(claudeCommand, ["--version"], { print: false, timeout: 10_000 });
|
|
741
|
-
const runtimeLabel = claudeRuntime.source === "
|
|
802
|
+
const runtimeLabel = claudeRuntime.source === "app-bundled"
|
|
803
|
+
? "Claude Code App runtime"
|
|
804
|
+
: claudeRuntime.source === "vscode-bundled"
|
|
805
|
+
? "Claude VS Code runtime"
|
|
806
|
+
: "Claude CLI";
|
|
742
807
|
log(`${runtimeLabel}: ${commandSummary(version) ?? "not runnable"}`);
|
|
743
808
|
if (version.status !== 0) return { ok: false };
|
|
744
|
-
log(
|
|
809
|
+
log(integrationSelected
|
|
810
|
+
? "Keeping Claude Code provider config unchanged and enabling the shared memory Hook integration."
|
|
811
|
+
: "Keeping Claude Code provider config unchanged while checking whether to enable its integration.");
|
|
745
812
|
return { ok: true };
|
|
746
813
|
}
|
|
747
814
|
|
package/docs/configuration.md
CHANGED
|
@@ -59,6 +59,16 @@ disabled. The command-line override is:
|
|
|
59
59
|
--clients codex|claude|codex,claude|all|none
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
A normal npm install or reinstall refreshes `[clients]` from the runnable
|
|
63
|
+
clients detected at that time. Update-mode postinstall runs preserve enabled
|
|
64
|
+
clients and also probe each disabled client. An interactive update offers each
|
|
65
|
+
runnable disabled integration for activation with a default of yes. Declining
|
|
66
|
+
the prompt, or running non-interactively, keeps that integration disabled. A
|
|
67
|
+
selected client that is temporarily unavailable also remains selected in the
|
|
68
|
+
configuration instead of being permanently disabled. When an update newly
|
|
69
|
+
enables Codex, it requests initial Hook activation after the client-selection
|
|
70
|
+
prompt.
|
|
71
|
+
|
|
62
72
|
Client selection controls plugin and Hook lifecycle only. It does not change
|
|
63
73
|
Codex or Claude Code provider settings. `--clients none` runs the Backend
|
|
64
74
|
without managing either client integration.
|
|
@@ -167,6 +177,11 @@ Supported policies are `every-commit`, `commit-count`, `daily`,
|
|
|
167
177
|
`pull-request`, `pull-request-or-daily`, and `adaptive`. Invalid policy values
|
|
168
178
|
fall back to `adaptive`.
|
|
169
179
|
|
|
180
|
+
The first eligible prompt starts a background build only when the Backend has
|
|
181
|
+
authorized a Git worktree and that worktree has no `.repo_memory/PROFILE.md`.
|
|
182
|
+
If the Backend or workspace authority is unavailable, the Hook skips the
|
|
183
|
+
initial build instead of falling back to its local `cwd`.
|
|
184
|
+
|
|
170
185
|
## Local traces
|
|
171
186
|
|
|
172
187
|
`[trace.codex]` and `[trace.claude]` support the same fields:
|
package/docs/troubleshooting.md
CHANGED
|
@@ -147,6 +147,32 @@ MemoraX Code reads filesystem Git metadata without executing Git. Linked
|
|
|
147
147
|
worktrees share the remote repository identity; non-Git workspaces use the
|
|
148
148
|
normalized folder name. Resolution never falls back to the bare base user ID.
|
|
149
149
|
|
|
150
|
+
A live Codex or Claude Code session remains pinned to the repository or local
|
|
151
|
+
workspace resolved at the start of the session. Starting the client from a
|
|
152
|
+
parent workspace and then entering a nested Git repository does not rebind the
|
|
153
|
+
session. The only in-session scope upgrade is from a direct `.git` directory
|
|
154
|
+
whose internal metadata was malformed or incomplete to a verified Git
|
|
155
|
+
repository at the same canonical workspace root and for the same Base User ID.
|
|
156
|
+
|
|
157
|
+
During that degraded state, MemoraX Code reports
|
|
158
|
+
`workspaceScopeFallbackReason: git_metadata_invalid` for manual CLI operations
|
|
159
|
+
and continues Search and Add with the normalized local folder name. Automatic
|
|
160
|
+
writeback also continues under that fallback scope without interrupting the
|
|
161
|
+
client task. The reported `effectiveUserId` identifies the fallback namespace
|
|
162
|
+
and may differ from the restored Git repository namespace. Repair the
|
|
163
|
+
repository or restore valid `.git` metadata, and later Search, Add, and
|
|
164
|
+
automatic writeback in the same client session automatically use the verified
|
|
165
|
+
Git repository scope. Memories already accepted under the fallback namespace
|
|
166
|
+
are not migrated. Any unsent automatic-writeback buffer for the fallback scope
|
|
167
|
+
is discarded instead of being flushed into either namespace.
|
|
168
|
+
|
|
169
|
+
Invalid or unreadable Git marker files, symlinked markers, and session scope
|
|
170
|
+
conflicts do not use this fallback. If Search or Add reports
|
|
171
|
+
`workspace_scope_mismatch` or `workspace_scope_unavailable`, start a new
|
|
172
|
+
session from the target repository or local workspace and verify that its
|
|
173
|
+
`.git` metadata is readable and valid. These failures stop Search or Add before
|
|
174
|
+
any request is sent to MemoraX.
|
|
175
|
+
|
|
150
176
|
Codex projectless tasks under its canonical dated-task location intentionally
|
|
151
177
|
use the shared `Codex-General` memory name. Open a task in a real workspace
|
|
152
178
|
when repository isolation matters.
|
|
@@ -182,7 +182,7 @@ function combinedReminderContext(options, due, cadenceReminderContext, personalM
|
|
|
182
182
|
|
|
183
183
|
function memoryReminderContext(options) {
|
|
184
184
|
const invocation = stringOption(options.memorySkillInvocation) ?? DEFAULT_MEMORY_SKILL_INVOCATION;
|
|
185
|
-
return `MemoraX Code reminder: proactively invoke ${invocation} whenever coding memory might help, even when uncertain;
|
|
185
|
+
return `MemoraX Code reminder: proactively invoke ${invocation} whenever coding memory might help, even when uncertain; follow the skill's router to decide whether any memory operation is needed. Also use ${invocation} for repository-scoped personal memory, and classify the authority before reading or writing.`;
|
|
186
186
|
}
|
|
187
187
|
|
|
188
188
|
function defaultMemoraxCodeHome() {
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { spawn } from "node:child_process";
|
|
2
|
+
import { existsSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
|
|
5
|
+
export function scheduleMissingRepoMemoryBuild(repo, options = {}) {
|
|
6
|
+
try {
|
|
7
|
+
const repoPath = nonEmptyString(repo);
|
|
8
|
+
const pluginRoot = nonEmptyString(options.pluginRoot);
|
|
9
|
+
if (!repoPath || !pluginRoot) return false;
|
|
10
|
+
if (existsSync(join(repoPath, ".repo_memory", "PROFILE.md"))) return false;
|
|
11
|
+
|
|
12
|
+
const jobHookPath = join(pluginRoot, "hooks", "repo-memory-job.mjs");
|
|
13
|
+
if (!existsSync(jobHookPath)) return false;
|
|
14
|
+
const child = spawn(process.execPath, [jobHookPath, "maintain", "--repo", repoPath], {
|
|
15
|
+
cwd: repoPath,
|
|
16
|
+
detached: true,
|
|
17
|
+
env: process.env,
|
|
18
|
+
stdio: "ignore",
|
|
19
|
+
windowsHide: true,
|
|
20
|
+
});
|
|
21
|
+
child.once("error", (error) => debug(options, error));
|
|
22
|
+
child.unref();
|
|
23
|
+
return true;
|
|
24
|
+
} catch (error) {
|
|
25
|
+
debug(options, error);
|
|
26
|
+
return false;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function nonEmptyString(value) {
|
|
31
|
+
return typeof value === "string" && value.trim() ? value.trim() : undefined;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function debug(options, error) {
|
|
35
|
+
if (process.env[options.debugEnv] === "1") {
|
|
36
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -20,6 +20,13 @@ export function createAutomaticMemoryWritebackRuntime(options = {}) {
|
|
|
20
20
|
enqueue(options) {
|
|
21
21
|
return enqueueAutomaticMemoryWritebackForRuntime(state, options);
|
|
22
22
|
},
|
|
23
|
+
discardForScopeUpgrade(upgrade) {
|
|
24
|
+
return state.writebackBuffer.discardForScopeUpgrade({
|
|
25
|
+
client: upgrade.client,
|
|
26
|
+
sessionKey: upgrade.sessionId,
|
|
27
|
+
currentScope: upgrade.currentScope,
|
|
28
|
+
});
|
|
29
|
+
},
|
|
23
30
|
drain() {
|
|
24
31
|
if (state.drainPromise)
|
|
25
32
|
return state.drainPromise;
|
|
@@ -2,7 +2,7 @@ import { readClaudeInterruptedTranscriptTurn, readClaudeTranscriptTurn, } from "
|
|
|
2
2
|
import { retrieveAutomaticMemoryContext } from "./automatic-memory-retrieval.js";
|
|
3
3
|
import { createAutomaticMemoryWritebackRuntime, } from "./automatic-memory-writeback.js";
|
|
4
4
|
import { createMemoryTurnCoordinator, } from "./memory-turn-coordinator.js";
|
|
5
|
-
import { createRepositoryMemorySessionRuntime, } from "./repository-memory-context.js";
|
|
5
|
+
import { createRepositoryMemorySessionRuntime, resolvedRepoMemoryWorktree, } from "./repository-memory-context.js";
|
|
6
6
|
import { traceContextFromClaudeHookBody } from "./trace-context.js";
|
|
7
7
|
import { markCurrentClaudeTurnOutcome, readOpenClaudeTurn, recordClaudeTraceEvent, traceTurnEventId, writeCurrentClaudeTurn, } from "./trace-store.js";
|
|
8
8
|
const CLAUDE_MEMORY_TURN_CLIENT = "claude-code";
|
|
@@ -23,7 +23,9 @@ export function createClaudeMemoryHookRuntime(options = {}) {
|
|
|
23
23
|
cleanupIntervalMs: options.cleanupIntervalMs,
|
|
24
24
|
});
|
|
25
25
|
const ownsTurnCoordinator = options.turnCoordinator === undefined;
|
|
26
|
-
const repositoryMemorySession = options.repositoryMemorySession ?? createRepositoryMemorySessionRuntime(
|
|
26
|
+
const repositoryMemorySession = options.repositoryMemorySession ?? createRepositoryMemorySessionRuntime({
|
|
27
|
+
onScopeUpgrade: automaticWritebackRuntime?.discardForScopeUpgrade,
|
|
28
|
+
});
|
|
27
29
|
const ownsRepositoryMemorySession = options.repositoryMemorySession === undefined;
|
|
28
30
|
const automaticRetrievalPrompts = new Set();
|
|
29
31
|
const automaticRetrievalPromptLimit = positiveInteger(options.maxEntries, 256);
|
|
@@ -45,6 +47,7 @@ export function createClaudeMemoryHookRuntime(options = {}) {
|
|
|
45
47
|
await reconcilePreviousInterruptedTurn(turnCoordinator, turn, options, now);
|
|
46
48
|
turnCoordinator.pruneExpired();
|
|
47
49
|
const repositoryMemory = await resolveHookRepositoryMemory(turn, options, repositoryMemorySession);
|
|
50
|
+
const repoMemoryWorktree = resolvedRepoMemoryWorktree(repositoryMemory);
|
|
48
51
|
turnCoordinator.recordTurnStart({
|
|
49
52
|
client: CLAUDE_MEMORY_TURN_CLIENT,
|
|
50
53
|
sessionId: turn.sessionId,
|
|
@@ -84,7 +87,10 @@ export function createClaudeMemoryHookRuntime(options = {}) {
|
|
|
84
87
|
now: () => new Date(now()),
|
|
85
88
|
}), options.diagnosticLogger);
|
|
86
89
|
if (!claimAutomaticRetrievalPrompt(automaticRetrievalPrompts, automaticRetrievalPromptLimit, turn.sessionId, turn.promptId)) {
|
|
87
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
ok: true,
|
|
92
|
+
...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
|
|
93
|
+
};
|
|
88
94
|
}
|
|
89
95
|
const retrieval = await retrieveAutomaticMemoryContext({
|
|
90
96
|
diagnosticLogger: options.diagnosticLogger,
|
|
@@ -99,6 +105,7 @@ export function createClaudeMemoryHookRuntime(options = {}) {
|
|
|
99
105
|
});
|
|
100
106
|
return {
|
|
101
107
|
ok: true,
|
|
108
|
+
...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
|
|
102
109
|
...(retrieval.context ? { additionalContext: retrieval.context } : {}),
|
|
103
110
|
};
|
|
104
111
|
},
|
|
@@ -4,7 +4,7 @@ import { retrieveAutomaticMemoryContext } from "./automatic-memory-retrieval.js"
|
|
|
4
4
|
import { readCodexSessionTurnIndex } from "./codex-session-turn-index.js";
|
|
5
5
|
import { resolveCodexWorkspaceRoot } from "./codex-workspace-links.js";
|
|
6
6
|
import { createMemoryTurnCoordinator, } from "./memory-turn-coordinator.js";
|
|
7
|
-
import { createRepositoryMemorySessionRuntime, } from "./repository-memory-context.js";
|
|
7
|
+
import { createRepositoryMemorySessionRuntime, resolvedRepoMemoryWorktree, } from "./repository-memory-context.js";
|
|
8
8
|
import { traceContextFromHookBody } from "./trace-context.js";
|
|
9
9
|
import { markCurrentCodexTurnOutcome, readCurrentCodexTurn, readOpenCodexTurn, recordCodexTraceEvent, traceTurnEventId, writeCurrentCodexTurn, } from "./trace-store.js";
|
|
10
10
|
const CODEX_MEMORY_TURN_CLIENT = "codex";
|
|
@@ -23,7 +23,9 @@ export function createCodexMemoryHookRuntime(options = {}) {
|
|
|
23
23
|
cleanupIntervalMs: options.cleanupIntervalMs,
|
|
24
24
|
});
|
|
25
25
|
const ownsTurnCoordinator = options.turnCoordinator === undefined;
|
|
26
|
-
const repositoryMemorySession = options.repositoryMemorySession ?? createRepositoryMemorySessionRuntime(
|
|
26
|
+
const repositoryMemorySession = options.repositoryMemorySession ?? createRepositoryMemorySessionRuntime({
|
|
27
|
+
onScopeUpgrade: automaticWritebackRuntime?.discardForScopeUpgrade,
|
|
28
|
+
});
|
|
27
29
|
const ownsRepositoryMemorySession = options.repositoryMemorySession === undefined;
|
|
28
30
|
const automaticRetrievalTurns = new Set();
|
|
29
31
|
const automaticRetrievalTurnLimit = positiveInteger(options.maxEntries, 256);
|
|
@@ -44,6 +46,7 @@ export function createCodexMemoryHookRuntime(options = {}) {
|
|
|
44
46
|
resolveHookRepositoryMemory(turn, options, repositoryMemorySession),
|
|
45
47
|
resolveSessionTurnIndex(turn, options.diagnosticLogger),
|
|
46
48
|
]);
|
|
49
|
+
const repoMemoryWorktree = resolvedRepoMemoryWorktree(repositoryMemory);
|
|
47
50
|
if (turn.turnId) {
|
|
48
51
|
turnCoordinator.recordTurnStart({
|
|
49
52
|
client: CODEX_MEMORY_TURN_CLIENT,
|
|
@@ -90,7 +93,10 @@ export function createCodexMemoryHookRuntime(options = {}) {
|
|
|
90
93
|
}), options.diagnosticLogger);
|
|
91
94
|
if (!turn.turnId
|
|
92
95
|
|| !claimAutomaticRetrievalTurn(automaticRetrievalTurns, automaticRetrievalTurnLimit, turn.sessionId, turn.turnId)) {
|
|
93
|
-
return {
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
|
|
99
|
+
};
|
|
94
100
|
}
|
|
95
101
|
const retrieval = await retrieveAutomaticMemoryContext({
|
|
96
102
|
diagnosticLogger: options.diagnosticLogger,
|
|
@@ -105,6 +111,7 @@ export function createCodexMemoryHookRuntime(options = {}) {
|
|
|
105
111
|
});
|
|
106
112
|
return {
|
|
107
113
|
ok: true,
|
|
114
|
+
...(repoMemoryWorktree ? { repoMemoryWorktree } : {}),
|
|
108
115
|
...(retrieval.context ? { additionalContext: retrieval.context } : {}),
|
|
109
116
|
};
|
|
110
117
|
},
|
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { runMemoryCli } from "./memory-cli.js";
|
|
3
3
|
runMemoryCli(process.argv.slice(2)).then((result) => {
|
|
4
4
|
if (!process.argv.includes("--json") && result.ok && result.action === "memory.search") {
|
|
5
|
+
if (result.userNotice)
|
|
6
|
+
console.warn(`Warning: ${result.userNotice}`);
|
|
5
7
|
console.log(result.answer?.trim() || "No memory context returned.");
|
|
6
8
|
}
|
|
7
9
|
else {
|
|
@@ -72,7 +72,7 @@ async function memorySearch(args, options) {
|
|
|
72
72
|
const query = queryResult.text;
|
|
73
73
|
const repositoryMemory = await resolveMemoryCliRepositoryMemory(options);
|
|
74
74
|
if (!repositoryMemory.ok) {
|
|
75
|
-
return
|
|
75
|
+
return memoryCliRepositoryFailure("memory.search", repositoryMemory, { query });
|
|
76
76
|
}
|
|
77
77
|
const observability = await memoryCliObservability(options.env);
|
|
78
78
|
const response = await invokeMemoraxMemoryProvider({ sessionId: memoryCliSessionId(args, options.env), prompt: query }, {
|
|
@@ -135,7 +135,7 @@ async function memoryAdd(args, options) {
|
|
|
135
135
|
return { ok: false, action: "memory.add", error: contentOptions.error };
|
|
136
136
|
const repositoryMemory = await resolveMemoryCliRepositoryMemory(options);
|
|
137
137
|
if (!repositoryMemory.ok)
|
|
138
|
-
return
|
|
138
|
+
return memoryCliRepositoryFailure("memory.add", repositoryMemory);
|
|
139
139
|
const sessionId = memoryCliSessionId(args, env);
|
|
140
140
|
const observability = await memoryCliObservability(env);
|
|
141
141
|
const response = await invokeMemoraxMemoryProvider({ sessionId, prompt: memory }, {
|
|
@@ -226,6 +226,24 @@ async function resolveMemoryCliRepositoryMemory(options) {
|
|
|
226
226
|
}
|
|
227
227
|
return commandMemory;
|
|
228
228
|
}
|
|
229
|
+
function memoryCliRepositoryFailure(action, failure, fields = {}) {
|
|
230
|
+
const userAction = failure.reason === "workspace_scope_mismatch"
|
|
231
|
+
? "Start a new Codex or Claude Code session from the target repository or local workspace."
|
|
232
|
+
: failure.reason === "workspace_scope_unavailable"
|
|
233
|
+
? "Start a new Codex or Claude Code session from the target repository or local workspace. If the problem continues, make sure its .git metadata is readable and valid."
|
|
234
|
+
: undefined;
|
|
235
|
+
return {
|
|
236
|
+
ok: false,
|
|
237
|
+
action,
|
|
238
|
+
...fields,
|
|
239
|
+
...(userAction ? {
|
|
240
|
+
workspaceScope: "unavailable",
|
|
241
|
+
workspaceScopeReason: failure.reason,
|
|
242
|
+
userAction,
|
|
243
|
+
} : {}),
|
|
244
|
+
error: failure.error,
|
|
245
|
+
};
|
|
246
|
+
}
|
|
229
247
|
function memoryCliIdentityFields(memory) {
|
|
230
248
|
return {
|
|
231
249
|
baseUserId: memory.config.userId,
|
|
@@ -234,6 +252,10 @@ function memoryCliIdentityFields(memory) {
|
|
|
234
252
|
scopeKind: repositoryMemoryScopeKind(memory.scope),
|
|
235
253
|
effectiveUserId: memory.scope.effectiveUserId,
|
|
236
254
|
workspaceScope: "bound",
|
|
255
|
+
...(memory.scope.fallbackReason === "git_metadata_invalid" ? {
|
|
256
|
+
workspaceScopeFallbackReason: memory.scope.fallbackReason,
|
|
257
|
+
userNotice: `Git repository metadata is invalid or incomplete. MemoraX Code is using the local folder name "${memory.scope.repositorySlug}" for memory scope, so Search and Add use "${memory.scope.effectiveUserId}". Repair the repository or restore valid .git metadata. Later Search, Add, and automatic writeback in the same client session will automatically use the restored Git repository scope.`,
|
|
258
|
+
} : {}),
|
|
237
259
|
} : {
|
|
238
260
|
workspaceScope: "unavailable",
|
|
239
261
|
}),
|
|
@@ -7,7 +7,9 @@ export function createMemoryService(options = {}) {
|
|
|
7
7
|
const automaticWriteback = createAutomaticMemoryWritebackRuntime({
|
|
8
8
|
diagnosticLogger: options.diagnosticLogger,
|
|
9
9
|
});
|
|
10
|
-
const repositoryMemorySession = createRepositoryMemorySessionRuntime(
|
|
10
|
+
const repositoryMemorySession = createRepositoryMemorySessionRuntime({
|
|
11
|
+
onScopeUpgrade: automaticWriteback.discardForScopeUpgrade,
|
|
12
|
+
});
|
|
11
13
|
const turnCoordinator = createMemoryTurnCoordinator({
|
|
12
14
|
automaticWriteback: automaticWriteback.enqueue,
|
|
13
15
|
now: options.now,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { repositoryMemoryScopesMatch, } from "./repository-memory-scope.js";
|
|
1
|
+
import { repositoryMemoryScopeCanUpgradeFromDegradedGit, repositoryMemoryScopesMatch, } from "./repository-memory-scope.js";
|
|
2
2
|
const DEFAULT_TTL_MS = 5 * 60 * 1000;
|
|
3
3
|
const DEFAULT_MAX_ENTRIES = 256;
|
|
4
4
|
const DEFAULT_CLEANUP_INTERVAL_MS = 60 * 1000;
|
|
@@ -72,10 +72,13 @@ export function createMemoryTurnCoordinator(options) {
|
|
|
72
72
|
if (!currentScope) {
|
|
73
73
|
return reject(input.metadata ? "workspace_scope_mismatch" : "workspace_scope_unavailable");
|
|
74
74
|
}
|
|
75
|
-
|
|
75
|
+
let repositoryScope = input.metadata?.repositoryScope ?? currentScope;
|
|
76
76
|
if (input.metadata?.repositoryScope
|
|
77
77
|
&& !repositoryMemoryScopesMatch(input.metadata.repositoryScope, currentScope)) {
|
|
78
|
-
|
|
78
|
+
if (!repositoryMemoryScopeCanUpgradeFromDegradedGit(input.metadata.repositoryScope, currentScope)) {
|
|
79
|
+
return reject("workspace_scope_mismatch");
|
|
80
|
+
}
|
|
81
|
+
repositoryScope = currentScope;
|
|
79
82
|
}
|
|
80
83
|
const acceptance = options.automaticWriteback({
|
|
81
84
|
...input.writeback,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MEMORAX_ICON_DATA_URL = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAACXBIWXMAAAsTAAALEwEAmpwYAAALEElEQVR4nO2be1BU1xnAgdhM22Q0exdRdu9dYPeeuw9YWFxYssgrCPhAwSj4QlREUaKCiCiCsqJREkxQwRh8YCQRoqAoic34NjGTzDRp2tpJ/+hMm3Ym08bpTKepk0br3Xu/zrkPuCq6CyKPzH4zh72ze853v+93v3se3zn4+fnEJz7xiU984hOFPIP/ECRyEBS6QJDof4QWfUdoUPWECZHPKeuMMBFswjZiWwkSfSfYLvrgUNbxKCoSVagphlNTDBAk4nFR60z4+jdBOqQXayWP8RsxItqCbcM2SraKdlMMYF+wT16pUpPMbLXOiBVwUpGVsdL336hIo1V542GVZNEGbJOaRN9INrKSzT1+4O+xbx71ERRzXVLilhQoi1tN4d+YWyqNwSm2sP/Mb5jEbhfvrTFEOAN1pluibY+wG/tEMdc9KiVI9EMfCh6CoCaZ7wmKzpCaDXkkJEtPng63Z4SYo7+foA8HlZbuy3ll+cGjYqI3dB5XOELoH5g7hI7OkZpig/yftuP4HrLzVntCjinaeQdFxUGwIZxTaWlPdvMetROenVdC4AmKcatIZsUQQehx3pGYXhgdP8VtdSTx5knxHMlE4gjwaPdgAgAhWigkjBYq0lAuash95ilB8M/NFXT7JaVnbUhInwmOpAwu2vkSHxWXDDqjbVgAgPTKCJ2MSot2KCAEDKLzAbLzU7NzazOy50HS1Cx3fOoM3pGcATEJaRBqscMLGsOwAIDeYdKEn0KjbPRgQHC5XAG44OvsBfn7Xl5YANNmz2fTMufyKdNmw+S0TIhPnQ4Ga+ywAgAlBDXJtErvqv+TQJAc93e5XGMWLS06nre8GF5esJSdlZPHT395PqTPmgsYQtLULMAdIQagpoYPAIiFZtUhJiAodDY42P7LgU6d5ZAvKyv7RWFxadfKteWwqGAVOy+/EOYsXAZZuYthxpwFkD4rB9Iy54Ap2gnjgjEAZrgBIJAjgSDR1bGkhegvhI6ODqFuZWWdal1Z5ZWSimooLC5llxathbzlq2H+kpUwd1EBZM8XIUyfPR/CYxJgXLB+yADw3kEQOsYvAnW6YG+nzjjcxc/6iZu31v66smYnrNmwiV1dWgEr1pTBsqJ1sLiwGBYsXQk5eQWQPS8fsublg9WRyA81AM5bCGqS+eN40kB7giA7v3NnvWH7rj1f1+7eAxVV29myzdugZOMWKF5fASvXlEHBqnWQv+IVWLisCHLylsPcRcs4mzOFHztxyAAwd9V4EuQdBHEeTjJ/e0GLoh4FwXX9uvDdrvp9kfUNTX99vaEJana+5q7evgs2ba2FDZU1ULKxCl5ZXwFFazfA8tUlsGTlGhwJXF5hMT8pfsrdsRPDnjoAt7Dg0NJ71CTqCMSdHUmzXkHA7SjmnyqKSXhwEXXo0CHhev9bh+KbmltuNR48DHVv7HO/+noDuHbVQ3Xtbti8bQeUb6mB0ooqWFO2GVatK8cQ2JUYRlFJJ2WK2iNOz/tcDA0WAFpcElOoCtdVa5kuqbPzHgKJbqu0+ukyBNn5Y61tU48eb/vP4WPvwr4Dze43Gw/C6w2NgCFs310PW3fUAe4PNm5xwfpNGMImtnRTNRSsLjmH2weFmatEWx7/QAYHAMlsw3UtFsuzKi06qoDAe7mIuqsi0QL5nu3tnfNOdnTdaW07BYdaWrm3j7wDOAoaGt+G+r1NsKt+L+D+oGZnHWyp2clvrNrOVrpehbVllS0WS+6zWMdEOmKbIhfw1COgWqxtEW5OUMwbihyCRwi4/wjUGeHnhGbpF199lX/2g4/4jjPn+PfeP8UdP/E+HHnnPcAQmpqPwN4DzVC/9wDs3rMPdtTt4XG/UFv3Jmysqm2QH4IAwBBeLdpADyWA5DHSvN+PIGmXIpPkCQI/PsSEV5L3NlTW3Lt87RO+s6ubP9nZBW0nOwFDOHr8BDQfPQ5NzUfxK4EjgX/tzf1uHBE1O16rlecL8uqwF8CQRoCfvPITIWhRidALU4wynfZQwXUmhFn4YNrKBepMXNG6cv7i5Wtwpvs8nDp9FtpPnobWtpPQ0noCvxJwoPko1/j2EW7/W4dhd/3e9bLzANBzbw0aPgBYsCHCkyAotISgGFZ812muL+eDQi2goa1AGW0QGh4DwXQkFBSvh48uXoazH/wKOs6cg/ZTZ+Dd9lMYAoej4fCxd9m9jYeXKYZMecktAbAOKwBJxHDEyUeCZP4rjcnc/c6bIdgQAaTRBiEWOxgi48A0KQF0lhgoKqkAHAlnu89DZ1c3tJ06zbV3YBAdP7515NgcrNvlEucLChlJAHrH93FafSpBMf8Shz6a63GetgLJRAnO661xgGzxYI5JAuuLqcJnU3MLXL72MZw59yHXff4CdHZ9+O+W1rY0rFMeMkc4gPsiIZYgmb/jNuNDTG7hyTPik9dbHYBsTjDZEyEiLgVsCelgjk2G1es3w41PP3NfuvoxnL9w6R/tnZ1xWNd1aaY4SgD0QiAmInNQqOnPGhSJnzzbl/NR8WlgT54GVmca5K1Yw/72dzfhyvVP/tLd3R3uwfkeG6iRB6AXwoSQqFDSaLsZFuGAsIhYFoc9dj7ckQKR8WkQnTgV4qbMhIgXp7CbXXXw+5t/+LqjoztMuUbwDCBqJALAIs4T9JGRQXqr41OjPQGM9oR74Y4UPtI5hY9OnMrHpszg41Jn3kueOQ+aDrd8BgATcBsA8CaHIAIwRlVLU+2RBqAXAk07xlodKZdskzPA6kzlbAkZfEzKdD5uyiwueeZ8mL2o8AoAjFMmRX4iAAQRcoOLy8ufi0/L3h+TMuOOI3UmONOzIXFGzt1pc/IPuFyu53EdAOhPHnHUAMASIEdDflGJLiUrNzMta0FmUUmJTvjVL/cZOQPcDxH0hYwSAPdtcChF+m4gGyoSgOhRA+C+zQ7J8SfZPxABmEcfgMGS0QggecwDTz3gCQ5bCDaEjRIAAR7CfSDbaaMGgL+8VSYcwyHpMwSF/oQLvpaOrwhbYf3sDCUA9qECYIJAythfAIJD4y2W51UkOi3sFeiM8gEm8RpvopDoNK6jbOM1gPChAKCl2fGhZggKMw8IAEHR7YpUOi5y5ojF3wm/UXT7QAAYhgKAmmLYYIMVJhqsEoCHx/NHGajSokwPiVPlOYNMZdsRASBQZ4SgMAtLGqMBTzv7C0BNMu8I6XPtY7K2WvEVw3VHFIBA0XmczWFDLDGAx1zRf48A5DD2J0j0+WOOsT24efK5su1QA+D7yOAKSUzSaGNxGktvjd3aTwB+BMnc8B4Ac6MvHY8DgG3yAoBXp8RuK52X83g4gxtitrMoejIYbc5SLwH0vCYEiQ4Gety6olmxDjqobOsNgFCzrdSLjZHbngFQxqviQUjkxs5LTx5CLDG8PsLhNk5KBKM96SXvAYgTm0AKJSp2lPvaM+B6do0olKhs6w0Atdb4kpSF7quTlU6KGq961KbW0LPwcBQUauE0hgiOEpOYvD4i7p45Nglnbi96YVSfENQk2icOg4KRSgj42o1/w3X64fz9tpPMReF4Donu9XVW+AUNPcsrRUEhlvJg2spTpmjBeYM1jjPFJOI83pfhMRmUVK2/S1d/HNIqDWoQT6IbFSe6jeKJbg1qGOA5Q6G+SmOgCBJ9KW3WClt00mSLV5FIOsfoWQLwHy2KjNOZ7Rf0EbF3UJTzW5M9sTIyPV3+f4GBrNt7Tozh8/t41kdo0Y+4CNe9Z/oDnkC/8P8Cai1TqSbRtwSJ7hAUfYHQIiG1PshnF33iE5/4xCc+8Ynf6Jb/A/TbJ89/tXn2AAAAAElFTkSuQmCC";
|
|
@@ -5,6 +5,7 @@ export function memoryViewerUserHtml() {
|
|
|
5
5
|
<meta charset="utf-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
7
7
|
<meta name="theme-color" content="#f7f8fc">
|
|
8
|
+
<link rel="icon" type="image/png" sizes="64x64" href="/memory-viewer/favicon.png">
|
|
8
9
|
<title>MemoraX Code Memory Viewer</title>
|
|
9
10
|
<script>
|
|
10
11
|
const THEME_STORAGE_KEY='memorax-code-memory-viewer-theme',LANGUAGE_STORAGE_KEY='memorax-code-memory-viewer-language';
|
|
@@ -27,8 +28,8 @@ export function memoryViewerUserHtml() {
|
|
|
27
28
|
cardTurns:'会话',cardSearch:'搜索',cardAdd:'写入',cardRepo:'仓库记忆',loading:'正在加载',checking:'正在检查',repoLoading:'仓库知识状态',recentActivity:'最近活动',
|
|
28
29
|
empty:'当前范围还没有会话、搜索或写入活动',error:'暂时无法加载记忆活动,请稍后重试。',selectProject:'选择仓库',selectProjectForStatus:'选择仓库后查看状态',noProjects:'暂无可识别仓库',unassignedProject:'未归属仓库',systemActivity:'系统活动',
|
|
29
30
|
turnNote:'轮客户端会话',searchUnit:'次搜索',addUnit:'次写入',processingUnit:'个处理中',unknownUnit:'个数量未知',activityUnit:'条',
|
|
30
|
-
repoStates:{ready:'可用',preparing:'
|
|
31
|
-
reasons:{usable:'仓库知识可以使用',active_job:'正在生成或更新',bundle_missing:'
|
|
31
|
+
repoStates:{ready:'可用',preparing:'生成中',not_ready:'未就绪',unknown:'未知'},
|
|
32
|
+
reasons:{usable:'仓库知识可以使用',active_job:'正在生成或更新',bundle_missing:'正在生成仓库知识',bundle_invalid:'仓库知识需要重新生成',project_unresolved:'无法定位仓库',validator_unavailable:'暂时无法检查'},
|
|
32
33
|
statuses:{completed:'完成',saved:'已保存',skipped:'未写入',processing:'进行中',interrupted:'已中断',failed:'失败',unknown:'数量未知'},
|
|
33
34
|
sources:{client:'客户端会话',assistant:'助手主动',automatic:'系统自动'}
|
|
34
35
|
},
|
|
@@ -39,8 +40,8 @@ export function memoryViewerUserHtml() {
|
|
|
39
40
|
cardTurns:'Turns',cardSearch:'Search',cardAdd:'Add',cardRepo:'Repo Memory',loading:'Loading',checking:'Checking',repoLoading:'Repository knowledge status',recentActivity:'Recent activity',
|
|
40
41
|
empty:'No turn, Search, or Add activity in the current scope',error:'Memory activity is temporarily unavailable. Please try again later.',selectProject:'Select a repository',selectProjectForStatus:'Select a repository to view its status',noProjects:'No repositories found',unassignedProject:'Unassigned repository',systemActivity:'System activity',
|
|
41
42
|
turnNote:'client turns',searchUnit:'searches',addUnit:'adds',processingUnit:'processing',unknownUnit:'with unknown counts',activityUnit:'items',
|
|
42
|
-
repoStates:{ready:'Ready',preparing:'
|
|
43
|
-
reasons:{usable:'Repository knowledge is ready',active_job:'Generating or updating repository knowledge',bundle_missing:'
|
|
43
|
+
repoStates:{ready:'Ready',preparing:'Generating',not_ready:'Not Ready',unknown:'Unknown'},
|
|
44
|
+
reasons:{usable:'Repository knowledge is ready',active_job:'Generating or updating repository knowledge',bundle_missing:'Generating repository knowledge',bundle_invalid:'Repository knowledge needs to be regenerated',project_unresolved:'Repository could not be resolved',validator_unavailable:'Status is temporarily unavailable'},
|
|
44
45
|
statuses:{completed:'Completed',saved:'Saved',skipped:'No Add',processing:'Processing',interrupted:'Interrupted',failed:'Failed',unknown:'Count unknown'},
|
|
45
46
|
sources:{client:'Client session',assistant:'Assistant initiated',automatic:'Automatic'}
|
|
46
47
|
}
|
|
@@ -54,7 +55,7 @@ export function memoryViewerUserHtml() {
|
|
|
54
55
|
function setTheme(next,persist=true){theme=next;document.documentElement.dataset.theme=theme;document.querySelector('meta[name="theme-color"]').content=theme==='dark'?'#10121a':'#f7f8fc';if(persist)writePreference(THEME_STORAGE_KEY,theme);syncThemeButton()}
|
|
55
56
|
function setLanguage(next,persist=true){language=next;if(persist)writePreference(LANGUAGE_STORAGE_KEY,language);syncStaticCopy();if(lastBody)render(lastBody);if(connectionState!==null)connection(connectionState)}
|
|
56
57
|
function syncProjects(items){const current=selected;project.innerHTML='<option value="">'+esc(copy().allProjects)+'</option>'+items.map(item=>'<option value="'+esc(item.projectId)+'">'+esc(item.projectLabel)+'</option>').join('');project.value=items.some(item=>item.projectId===current)?current:'';selected=project.value}
|
|
57
|
-
function repoState(items){const current=copy(),target=selected?items.find(item=>item.projectId===selected):items.length===1?items[0]:null,state=target?.repoMemory||{status:'unknown',reason:'project_unresolved'};E('repo-state').className='repo-state';E('repo-dot').className='state-dot '+
|
|
58
|
+
function repoState(items){const current=copy(),target=selected?items.find(item=>item.projectId===selected):items.length===1?items[0]:null,state=target?.repoMemory||{status:'unknown',reason:'project_unresolved'},displayStatus=state.reason==='bundle_missing'?'preparing':state.status;E('repo-state').className='repo-state';E('repo-dot').className='state-dot '+displayStatus;E('repo-label').textContent=target?(current.repoStates[displayStatus]||current.repoStates.unknown):current.selectProject;E('repo-note').classList.remove('skeleton','loading-copy');E('repo-note').textContent=target?(target.projectLabel+' · '+(current.reasons[state.reason]||current.reasons.validator_unavailable)):items.length?current.selectProjectForStatus:current.noProjects}
|
|
58
59
|
function memoryCount(value){if(language==='zh')return value+' 条记忆';return value+' '+(value===1?'memory':'memories')}
|
|
59
60
|
function activityTitle(item){if(language==='zh'){if(item.kind==='turn')return item.status==='processing'?'会话进行中':item.status==='interrupted'?'会话已中断':'完成一轮会话';if(item.kind==='search')return item.status==='failed'?'搜索失败':'搜索 · '+memoryCount(item.count??0);if(item.status==='saved')return item.count===null?'写入 · 数量未知':'写入 · '+memoryCount(item.count);if(item.status==='skipped')return '写入 · 没有新记忆';if(item.status==='processing')return '写入处理中';if(item.status==='failed')return '写入失败';return '写入完成 · 数量未知'}if(item.kind==='turn')return item.status==='processing'?'Session in progress':item.status==='interrupted'?'Session interrupted':'Session turn completed';if(item.kind==='search')return item.status==='failed'?'Search failed':'Search · '+memoryCount(item.count??0);if(item.status==='saved')return item.count===null?'Add · Count unknown':'Add · '+memoryCount(item.count);if(item.status==='skipped')return 'Add · No new memory';if(item.status==='processing')return 'Add in progress';if(item.status==='failed')return 'Add failed';return 'Add completed · Count unknown'}
|
|
60
61
|
function metaItem(value){return '<span class="meta-item">'+esc(value)+'</span>'}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { writebackMessagesContentChars } from "./memory-writeback-chunk.js";
|
|
2
2
|
import { memoryWritebackBufferConfig } from "./memorax-config.js";
|
|
3
|
+
import { repositoryMemoryScopeCanUpgradeFromDegradedGit, } from "./repository-memory-scope.js";
|
|
3
4
|
const SYSTEM_CLOCK = {
|
|
4
5
|
now: () => Date.now(),
|
|
5
6
|
setTimeout: (callback, delayMs) => setTimeout(callback, delayMs),
|
|
@@ -11,6 +12,9 @@ export function createMemoryWritebackBufferRuntime() {
|
|
|
11
12
|
enqueue(decision, options, deps) {
|
|
12
13
|
enqueueMemoryWritebackBufferForRuntime(writebackBuffers, decision, options, deps);
|
|
13
14
|
},
|
|
15
|
+
discardForScopeUpgrade(upgrade) {
|
|
16
|
+
return discardDegradedGitBuffersForUpgrade(writebackBuffers, upgrade);
|
|
17
|
+
},
|
|
14
18
|
flushAll(flushReason) {
|
|
15
19
|
let flushed = 0;
|
|
16
20
|
for (const [bufferKey, buffer] of [...writebackBuffers.entries()]) {
|
|
@@ -34,6 +38,11 @@ function enqueueMemoryWritebackBufferForRuntime(writebackBuffers, decision, opti
|
|
|
34
38
|
const config = memoryWritebackBufferConfig(env);
|
|
35
39
|
if (!options.repositoryScope)
|
|
36
40
|
return;
|
|
41
|
+
discardDegradedGitBuffersForUpgrade(writebackBuffers, {
|
|
42
|
+
client: decision.client,
|
|
43
|
+
sessionKey: decision.sessionKey,
|
|
44
|
+
currentScope: options.repositoryScope,
|
|
45
|
+
});
|
|
37
46
|
const bufferKey = memoryWritebackBufferKey(decision.client, decision.sessionKey, options.repositoryScope);
|
|
38
47
|
let buffer = writebackBuffers.get(bufferKey);
|
|
39
48
|
if (buffer?.turnKeys.has(decision.idempotencyKey)) {
|
|
@@ -85,6 +94,26 @@ function enqueueMemoryWritebackBufferForRuntime(writebackBuffers, decision, opti
|
|
|
85
94
|
resetMemoryWritebackIdleTimer(writebackBuffers, buffer, config.maxAgeMs, deps);
|
|
86
95
|
}
|
|
87
96
|
}
|
|
97
|
+
function discardDegradedGitBuffersForUpgrade(writebackBuffers, upgrade) {
|
|
98
|
+
let discarded = 0;
|
|
99
|
+
for (const [bufferKey, buffer] of writebackBuffers.entries()) {
|
|
100
|
+
if (buffer.client !== upgrade.client
|
|
101
|
+
|| buffer.sessionKey !== upgrade.sessionKey
|
|
102
|
+
|| !repositoryMemoryScopeCanUpgradeFromDegradedGit(buffer.repositoryScope, upgrade.currentScope))
|
|
103
|
+
continue;
|
|
104
|
+
writebackBuffers.delete(bufferKey);
|
|
105
|
+
if (buffer.timer)
|
|
106
|
+
buffer.clock.clearTimeout(buffer.timer);
|
|
107
|
+
buffer.deps.debug("memory.automatic_writeback", {
|
|
108
|
+
scheduled: false,
|
|
109
|
+
skipReason: "buffer_scope_upgraded",
|
|
110
|
+
sessionKey: upgrade.sessionKey,
|
|
111
|
+
discardedTurnCount: buffer.turns.length,
|
|
112
|
+
});
|
|
113
|
+
discarded += 1;
|
|
114
|
+
}
|
|
115
|
+
return discarded;
|
|
116
|
+
}
|
|
88
117
|
function createMemoryWritebackBuffer(bufferKey, client, sessionKey, env, options, repositoryScope, clock, deps) {
|
|
89
118
|
const now = clock.now();
|
|
90
119
|
const buffer = {
|