@memorax/memorax-code 0.1.9 → 0.1.11
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/README.md +10 -0
- package/bin/memorax-code-codebuddy.mjs +4 -0
- package/bin/memorax-code-setup.mjs +182 -65
- package/bin/memorax-code.mjs +74 -45
- package/docs/configuration.md +85 -22
- package/docs/troubleshooting.md +72 -5
- package/lib/automatic-update.mjs +233 -0
- package/lib/memorax-code-adapter-common/src/automatic-update-state.d.mts +24 -0
- package/lib/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
- package/lib/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
- package/lib/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
- package/lib/memorax-code-adapter-common/src/config-utils.mjs +11 -0
- package/lib/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
- package/lib/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
- package/lib/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
- package/lib/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
- package/lib/memorax-code-backend/dist/app/backend-server.js +2 -2
- package/lib/memorax-code-backend/dist/app/memory-observability.js +2 -2
- package/lib/memorax-code-backend/dist/clients/codebuddy/jsonl-history.js +208 -0
- package/lib/memorax-code-backend/dist/clients/codebuddy/lifecycle.js +44 -0
- package/lib/memorax-code-backend/dist/clients/codebuddy/memory-hook-runtime.js +250 -0
- package/lib/memorax-code-backend/dist/clients/codebuddy/turn-id.js +16 -0
- package/lib/memorax-code-backend/dist/clients/codex/plugin-install.js +129 -7
- package/lib/memorax-code-backend/dist/config/memorax-code.js +14 -1
- package/lib/memorax-code-backend/dist/entrypoints/backend-cli.js +43 -6
- package/lib/memorax-code-backend/dist/lifecycle/active-clients.js +3 -0
- package/lib/memorax-code-backend/dist/lifecycle/automatic-update-scheduler.js +133 -0
- package/lib/memorax-code-backend/dist/lifecycle/client-plugin-removal.js +14 -2
- package/lib/memorax-code-backend/dist/lifecycle/client-selection.js +6 -4
- package/lib/memorax-code-backend/dist/lifecycle/orchestrator.js +48 -9
- package/lib/memorax-code-backend/dist/memory/cli.js +4 -2
- package/lib/memorax-code-backend/dist/memory/hook-command.js +31 -1
- package/lib/memorax-code-backend/dist/memory/reminder-trace-recorder.js +5 -1
- package/lib/memorax-code-backend/dist/memory/service.js +12 -0
- package/lib/memorax-code-backend/dist/shared/windows-cli-invocation.js +10 -1
- package/lib/memorax-code-backend/dist/trace/config.js +12 -0
- package/lib/memorax-code-backend/dist/trace/context.js +21 -1
- package/lib/memorax-code-backend/dist/trace/store.js +12 -1
- 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/ensure-backend.mjs +2 -0
- package/lib/memorax-code-claude-adapter/src/plugin-install.mjs +3 -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/automatic-update-state.d.mts +24 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/automatic-update-state.mjs +73 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/clients/codebuddy-command.mjs +82 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.d.mts +2 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/config-utils.mjs +11 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs +3 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs +45 -10
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/repo-memory/repo-memory-job-worker.mjs +88 -3
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/memorax-code-adapter-common/src/windows-cli-invocation.mjs +11 -1
- 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/ensure-backend.mjs +2 -0
- package/lib/memorax-code-claude-marketplace/plugins/memorax-code-claude-adapter/src/plugin-install.mjs +3 -0
- package/lib/memorax-code-codebuddy-adapter/.codebuddy-plugin/plugin.json +7 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/common-runtime.mjs +13 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/hooks.json +38 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/repo-memory-job.mjs +40 -0
- package/lib/memorax-code-codebuddy-adapter/hooks/runtime-hook.mjs +289 -0
- package/lib/memorax-code-codebuddy-adapter/package.json +9 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/SKILL.md +85 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/claude.yaml +10 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/agents/openai.yaml +7 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/defaults.json +12 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-add.md +88 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/memorax-search.md +93 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-read.md +46 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/personal-write.md +120 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-build.md +319 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-read.md +103 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-templates.md +390 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/references/repo-update.md +127 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/collect_all.py +579 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/detect_updates.py +919 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/git_commit_facets.py +222 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/github_resource_facets.py +512 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/gitlab_resource_facets.py +517 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/prepare_repo_memory.py +411 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/user_profile_memory.py +528 -0
- package/lib/memorax-code-codebuddy-adapter/skills/memorax-code/scripts/validate_memory.py +248 -0
- package/lib/memorax-code-codebuddy-adapter/src/cli.mjs +58 -0
- package/lib/memorax-code-codebuddy-adapter/src/config.mjs +340 -0
- package/lib/memorax-code-codebuddy-adapter/src/hook-manifest.mjs +47 -0
- package/lib/memorax-code-codebuddy-adapter/src/runtime-observation.mjs +65 -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/ensure-backend.mjs +4 -1
- package/lib/memorax-code-dsh-adapter/package.json +2 -1
- package/lib/memorax-code-dsh-adapter/src/profile-lifecycle.mjs +1 -0
- package/lib/memorax-code-opencode-adapter/package.json +1 -1
- package/lib/npm-invocation.mjs +51 -8
- package/lib/resolve-codebuddy-command.mjs +123 -0
- package/lib/run-entrypoint.mjs +25 -1
- package/lib/windows-cli-invocation.mjs +11 -1
- package/lib/windows-user-path.mjs +218 -0
- package/package.json +5 -3
|
@@ -21,7 +21,7 @@ let requestedSignal;
|
|
|
21
21
|
for (const signal of ["SIGINT", "SIGTERM"]) {
|
|
22
22
|
process.on(signal, () => {
|
|
23
23
|
requestedSignal = signal;
|
|
24
|
-
activeChild
|
|
24
|
+
if (activeChild) terminateClient(activeChild, signal);
|
|
25
25
|
});
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -56,12 +56,16 @@ async function main(args) {
|
|
|
56
56
|
runId: workerContext.runId,
|
|
57
57
|
});
|
|
58
58
|
if (initial.status !== "started") throw new Error(`repo memory job cannot start from status ${initial.status}`);
|
|
59
|
+
const clientTimeoutMs = repoMemoryClientTimeoutMs();
|
|
60
|
+
const clientKillGraceMs = repoMemoryClientKillGraceMs();
|
|
59
61
|
let state = writeJobState(request.jobPath, {
|
|
60
62
|
...initial,
|
|
61
63
|
status: "running",
|
|
62
64
|
pid: process.pid,
|
|
63
65
|
workerPid: process.pid,
|
|
64
66
|
workerStartedAt: new Date().toISOString(),
|
|
67
|
+
clientTimeoutMs,
|
|
68
|
+
clientKillGraceMs,
|
|
65
69
|
});
|
|
66
70
|
|
|
67
71
|
if (requestedSignal) {
|
|
@@ -85,9 +89,18 @@ async function main(args) {
|
|
|
85
89
|
});
|
|
86
90
|
},
|
|
87
91
|
finalMessagePath: state.finalMessagePath,
|
|
92
|
+
timeoutMs: clientTimeoutMs,
|
|
93
|
+
killGraceMs: clientKillGraceMs,
|
|
88
94
|
});
|
|
89
95
|
state = readJobState(request.jobPath);
|
|
90
96
|
|
|
97
|
+
if (childResult.timedOut) {
|
|
98
|
+
return finishFailed(request, state, workerContext, `${runner}_timeout`, {
|
|
99
|
+
timeoutMs: childResult.timeoutMs,
|
|
100
|
+
elapsedMs: childResult.elapsedMs,
|
|
101
|
+
signal: childResult.signal,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
91
104
|
if (childResult.error) {
|
|
92
105
|
return finishFailed(request, state, workerContext, `${runner}_spawn_failed`, {
|
|
93
106
|
error: childResult.error.message,
|
|
@@ -213,6 +226,11 @@ async function waitForOwnedMarker(input) {
|
|
|
213
226
|
function runClient(command, options) {
|
|
214
227
|
return new Promise((resolveResult) => {
|
|
215
228
|
let settled = false;
|
|
229
|
+
let timeoutHandle;
|
|
230
|
+
let killHandle;
|
|
231
|
+
let timedOut = false;
|
|
232
|
+
let timeoutMs;
|
|
233
|
+
const startedAt = Date.now();
|
|
216
234
|
let invocation;
|
|
217
235
|
try {
|
|
218
236
|
invocation = process.platform === "win32" && /\.[cm]?js$/i.test(command[0])
|
|
@@ -228,6 +246,7 @@ function runClient(command, options) {
|
|
|
228
246
|
const child = spawn(invocation.command, invocation.args, {
|
|
229
247
|
cwd: options.cwd,
|
|
230
248
|
env: options.env,
|
|
249
|
+
detached: process.platform !== "win32",
|
|
231
250
|
stdio: options.captureStdout ? ["ignore", "pipe", "inherit"] : "inherit",
|
|
232
251
|
});
|
|
233
252
|
activeChild = child;
|
|
@@ -243,8 +262,10 @@ function runClient(command, options) {
|
|
|
243
262
|
const finish = (result) => {
|
|
244
263
|
if (settled) return;
|
|
245
264
|
settled = true;
|
|
265
|
+
if (timeoutHandle) clearTimeout(timeoutHandle);
|
|
266
|
+
if (killHandle) clearTimeout(killHandle);
|
|
246
267
|
activeChild = undefined;
|
|
247
|
-
resolveResult(result);
|
|
268
|
+
resolveResult({ ...result, timedOut, timeoutMs, elapsedMs: Date.now() - startedAt });
|
|
248
269
|
};
|
|
249
270
|
child.once("error", (error) => finish({ code: undefined, signal: undefined, error }));
|
|
250
271
|
child.once("close", (code, signal) => {
|
|
@@ -258,10 +279,74 @@ function runClient(command, options) {
|
|
|
258
279
|
}
|
|
259
280
|
finish({ code, signal, error: undefined });
|
|
260
281
|
});
|
|
261
|
-
if (
|
|
282
|
+
if (Number.isFinite(options.timeoutMs) && options.timeoutMs > 0) {
|
|
283
|
+
timeoutHandle = setTimeout(() => {
|
|
284
|
+
timeoutMs = options.timeoutMs;
|
|
285
|
+
timedOut = true;
|
|
286
|
+
const terminated = terminateClient(child, "SIGTERM");
|
|
287
|
+
// A broken or ignored CLI must not leave a detached job behind forever.
|
|
288
|
+
// The grace period is deliberately short and only targets this child.
|
|
289
|
+
killHandle = setTimeout(() => {
|
|
290
|
+
if (!settled) terminateClient(child, "SIGKILL");
|
|
291
|
+
}, options.killGraceMs);
|
|
292
|
+
if (!terminated && !settled) {
|
|
293
|
+
finish({
|
|
294
|
+
code: undefined,
|
|
295
|
+
signal: "SIGTERM",
|
|
296
|
+
error: undefined,
|
|
297
|
+
timedOut: true,
|
|
298
|
+
timeoutMs,
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}, options.timeoutMs);
|
|
302
|
+
}
|
|
303
|
+
if (requestedSignal) terminateClient(child, requestedSignal);
|
|
262
304
|
});
|
|
263
305
|
}
|
|
264
306
|
|
|
307
|
+
function terminateClient(child, signal) {
|
|
308
|
+
if (!child || !Number.isInteger(child.pid) || child.pid <= 0) return false;
|
|
309
|
+
if (process.platform !== "win32") {
|
|
310
|
+
try {
|
|
311
|
+
process.kill(-child.pid, signal);
|
|
312
|
+
return true;
|
|
313
|
+
} catch {
|
|
314
|
+
// The process group may have exited between the timeout and this call.
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
if (process.platform === "win32") {
|
|
318
|
+
try {
|
|
319
|
+
const result = spawnSync("taskkill", ["/PID", String(child.pid), "/T", "/F"], {
|
|
320
|
+
encoding: "utf8",
|
|
321
|
+
windowsHide: true,
|
|
322
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
323
|
+
});
|
|
324
|
+
if (result.status === 0) return true;
|
|
325
|
+
} catch {
|
|
326
|
+
// Fall through to the direct child termination below.
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
try {
|
|
330
|
+
return child.kill(signal);
|
|
331
|
+
} catch {
|
|
332
|
+
return false;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function repoMemoryClientTimeoutMs(env = process.env) {
|
|
337
|
+
return positiveEnvInteger(env.MEMORAX_CODE_REPO_MEMORY_JOB_TIMEOUT_MS, 10 * 60 * 1000);
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
function repoMemoryClientKillGraceMs(env = process.env) {
|
|
341
|
+
return positiveEnvInteger(env.MEMORAX_CODE_REPO_MEMORY_JOB_KILL_GRACE_MS, 5000);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function positiveEnvInteger(value, fallback) {
|
|
345
|
+
if (typeof value !== "string" || !/^\d+$/.test(value.trim())) return fallback;
|
|
346
|
+
const parsed = Number(value.trim());
|
|
347
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
348
|
+
}
|
|
349
|
+
|
|
265
350
|
function validateBundle(repo, validator) {
|
|
266
351
|
const python = process.env.MEMORAX_CODE_REPO_MEMORY_PYTHON_COMMAND || "python3";
|
|
267
352
|
process.stdout.write("\n[repo-memory-worker] validating generated repo memory\n");
|
|
@@ -24,6 +24,9 @@ export function resolveWindowsCliInvocation(command, args, options = {}) {
|
|
|
24
24
|
}
|
|
25
25
|
resolvedCommand ??= command;
|
|
26
26
|
if (/\.(?:exe|com)$/i.test(resolvedCommand)) return { command: resolvedCommand, args };
|
|
27
|
+
if (isBareCodeBuddyEntrypoint(resolvedCommand, pathApi) && fileExists(resolvedCommand)) {
|
|
28
|
+
return { command: options.nodePath ?? process.execPath, args: [resolvedCommand, ...args] };
|
|
29
|
+
}
|
|
27
30
|
if (!/\.(?:cmd|bat)$/i.test(resolvedCommand)) {
|
|
28
31
|
throw new Error(`refusing to execute unsupported Windows command shim: ${resolvedCommand}`);
|
|
29
32
|
}
|
|
@@ -104,9 +107,16 @@ export function selectWindowsCommandCandidate(command, output) {
|
|
|
104
107
|
.map((value) => value.trim())
|
|
105
108
|
.filter(Boolean);
|
|
106
109
|
const selected = candidates.find((value) => /\.(?:cmd|bat)$/i.test(value))
|
|
107
|
-
?? candidates.find((value) => /\.(?:exe|com)$/i.test(value))
|
|
110
|
+
?? candidates.find((value) => /\.(?:exe|com)$/i.test(value))
|
|
111
|
+
?? (path.win32.basename(command).toLowerCase() === "codebuddy"
|
|
112
|
+
? candidates.find((value) => isBareCodeBuddyEntrypoint(value, path.win32))
|
|
113
|
+
: undefined);
|
|
108
114
|
if (!selected) {
|
|
109
115
|
throw new Error(`where.exe did not return a safe executable or command shim for ${command}`);
|
|
110
116
|
}
|
|
111
117
|
return selected;
|
|
112
118
|
}
|
|
119
|
+
|
|
120
|
+
function isBareCodeBuddyEntrypoint(value, pathApi) {
|
|
121
|
+
return pathApi.basename(value).toLowerCase() === "codebuddy" && pathApi.extname(value) === "";
|
|
122
|
+
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
4
|
import { fileURLToPath } from "node:url";
|
|
5
|
+
import { readClientHookPluginRoot } from "../memorax-code-adapter-common/src/config-utils.mjs";
|
|
5
6
|
import {
|
|
6
7
|
runEnsureBackendHook,
|
|
7
8
|
stringValue,
|
|
@@ -31,6 +32,7 @@ process.exit(0);
|
|
|
31
32
|
function pluginRoot() {
|
|
32
33
|
return stringValue(process.env.CLAUDE_PLUGIN_ROOT)
|
|
33
34
|
?? stringValue(process.env.PLUGIN_ROOT)
|
|
35
|
+
?? readClientHookPluginRoot()
|
|
34
36
|
?? dirname(dirname(fileURLToPath(import.meta.url)));
|
|
35
37
|
}
|
|
36
38
|
|
|
@@ -32,6 +32,7 @@ const STABLE_SHELL_REQUIRED_FILES = Object.freeze([
|
|
|
32
32
|
"memorax-code-adapter-common/src/hooks/capture-cwd-hook.mjs",
|
|
33
33
|
"memorax-code-adapter-common/src/hooks/client-hook-launcher.mjs",
|
|
34
34
|
"memorax-code-adapter-common/src/config-utils.mjs",
|
|
35
|
+
"memorax-code-adapter-common/src/setup-completion.mjs",
|
|
35
36
|
"memorax-code-adapter-common/src/hooks/ensure-backend-runner.mjs",
|
|
36
37
|
"memorax-code-adapter-common/src/hooks/hook-runtime-generation.mjs",
|
|
37
38
|
"memorax-code-adapter-common/src/hooks/memory-skill-reminder-hook.mjs",
|
|
@@ -226,10 +227,12 @@ export function ensureClaudePluginInstalled(options = {}) {
|
|
|
226
227
|
}
|
|
227
228
|
|
|
228
229
|
function writeInstalledPluginMetadata(installPath, claudeCommand) {
|
|
230
|
+
const npmExecPath = stringOption(process.env.MEMORAX_CODE_NPM_EXEC_PATH);
|
|
229
231
|
atomicWriteJson(join(installPath, ".memorax-code-package.json"), {
|
|
230
232
|
version: 1,
|
|
231
233
|
memoraxCodeCommand: process.argv[1],
|
|
232
234
|
claudeCommand,
|
|
235
|
+
...(npmExecPath ? { npmExecPath } : {}),
|
|
233
236
|
writtenAt: new Date().toISOString(),
|
|
234
237
|
});
|
|
235
238
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export function resolveCommonSourceRoot(pluginRoot) {
|
|
5
|
+
const candidates = [
|
|
6
|
+
join(pluginRoot, "memorax-code-adapter-common", "src"),
|
|
7
|
+
join(pluginRoot, "..", "memorax-code-adapter-common", "src"),
|
|
8
|
+
join(pluginRoot, "..", "..", "memorax-code-adapter-common", "src"),
|
|
9
|
+
];
|
|
10
|
+
const root = candidates.find((candidate) => existsSync(join(candidate, "repo-memory", "repo-memory-job-supervisor.mjs")));
|
|
11
|
+
if (!root) throw new Error(`MemoraX Code shared runtime is unavailable for CodeBuddy plugin: ${pluginRoot}`);
|
|
12
|
+
return root;
|
|
13
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"SessionStart": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "startup|resume|clear|compact",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/hooks/runtime-hook.mjs\" turn",
|
|
10
|
+
"timeout": 35
|
|
11
|
+
}
|
|
12
|
+
]
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"UserPromptSubmit": [
|
|
16
|
+
{
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/hooks/runtime-hook.mjs\" turn",
|
|
21
|
+
"timeout": 15
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
],
|
|
26
|
+
"Stop": [
|
|
27
|
+
{
|
|
28
|
+
"hooks": [
|
|
29
|
+
{
|
|
30
|
+
"type": "command",
|
|
31
|
+
"command": "node \"${CODEBUDDY_PLUGIN_ROOT}/hooks/runtime-hook.mjs\" turn",
|
|
32
|
+
"timeout": 15
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { dirname, join, resolve } from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { resolveCommonSourceRoot } from "./common-runtime.mjs";
|
|
6
|
+
|
|
7
|
+
const hookDir = dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const pluginRoot = dirname(hookDir);
|
|
9
|
+
const commonRoot = resolveCommonSourceRoot(pluginRoot);
|
|
10
|
+
const { resolveHookCodeBuddyCommand } = await import(pathToFileURL(join(commonRoot, "clients", "codebuddy-command.mjs")).href);
|
|
11
|
+
const { runRepoMemoryJob } = await import(pathToFileURL(join(commonRoot, "repo-memory", "repo-memory-job-supervisor.mjs")).href);
|
|
12
|
+
const { evaluateRepository } = await import(pathToFileURL(join(commonRoot, "repo-memory", "repo-memory-update-policy-evaluator.mjs")).href);
|
|
13
|
+
|
|
14
|
+
try {
|
|
15
|
+
const payload = runRepoMemoryJob(process.argv.slice(2), {
|
|
16
|
+
runner: "codebuddy",
|
|
17
|
+
finalMessageSource: "stdout",
|
|
18
|
+
memorySkillInvocation: "the `memorax-code` skill",
|
|
19
|
+
validatorPath: resolve(pluginRoot, "skills/memorax-code/scripts/validate_memory.py"),
|
|
20
|
+
evaluateRepository,
|
|
21
|
+
createCommand({ prompt }) {
|
|
22
|
+
const codeBuddy = resolveHookCodeBuddyCommand({
|
|
23
|
+
pluginRoot,
|
|
24
|
+
});
|
|
25
|
+
return [
|
|
26
|
+
codeBuddy,
|
|
27
|
+
"--print",
|
|
28
|
+
"--output-format",
|
|
29
|
+
"text",
|
|
30
|
+
"--dangerously-skip-permissions",
|
|
31
|
+
"--no-session-persistence",
|
|
32
|
+
prompt,
|
|
33
|
+
];
|
|
34
|
+
},
|
|
35
|
+
});
|
|
36
|
+
process.stdout.write(`${JSON.stringify(payload)}\n`);
|
|
37
|
+
} catch (error) {
|
|
38
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}\n`);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync } from "node:fs";
|
|
4
|
+
import { appendFile, mkdir, readFile, rm, stat, writeFile } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
import { pathToFileURL } from "node:url";
|
|
10
|
+
import { resolveCommonSourceRoot } from "./common-runtime.mjs";
|
|
11
|
+
import { writeCodeBuddyRuntimeObservation } from "../src/runtime-observation.mjs";
|
|
12
|
+
|
|
13
|
+
const pluginRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
14
|
+
const commonRoot = resolveCommonSourceRoot(pluginRoot);
|
|
15
|
+
const { scheduleMissingRepoMemoryBuild } = await import(pathToFileURL(join(commonRoot, "repo-memory", "repo-memory-auto-build.mjs")).href);
|
|
16
|
+
const { isRepoMemoryJobWorker } = await import(pathToFileURL(join(commonRoot, "repo-memory", "repo-memory-job-context.mjs")).href);
|
|
17
|
+
const { buildRepoProcedureMemoryContext } = await import(pathToFileURL(join(commonRoot, "repo-memory", "repo-procedure-memory-context.mjs")).href);
|
|
18
|
+
const { buildRepoUserProfilePreferencesContext } = await import(pathToFileURL(join(commonRoot, "repo-memory", "repo-user-profile-context.mjs")).href);
|
|
19
|
+
const { resolveBackendConnection } = await import(pathToFileURL(join(commonRoot, "backend-connection.mjs")).href);
|
|
20
|
+
const { ensureBackendAvailable, stringValue: commonStringValue } = await import(pathToFileURL(join(commonRoot, "hooks", "ensure-backend-runner.mjs")).href);
|
|
21
|
+
const {
|
|
22
|
+
evaluateMemorySkillReminder,
|
|
23
|
+
markSupplementalReminderAfterCompact,
|
|
24
|
+
personalMemoryReminderContext,
|
|
25
|
+
} = await import(pathToFileURL(join(commonRoot, "hooks", "memory-skill-reminder-hook.mjs")).href);
|
|
26
|
+
|
|
27
|
+
if (isRepoMemoryJobWorker()) process.exit(0);
|
|
28
|
+
|
|
29
|
+
const MEMORY_SKILL_INVOCATION = "the `memorax-code` skill";
|
|
30
|
+
const REMINDER_TRACE_TIMEOUT_MS = 1000;
|
|
31
|
+
|
|
32
|
+
const input = await readJsonStdin();
|
|
33
|
+
const event = stringValue(input?.hook_event_name) ?? stringValue(input?.hookEventName);
|
|
34
|
+
const sessionId = stringValue(input?.session_id) ?? stringValue(input?.sessionId);
|
|
35
|
+
const transcriptPath = stringValue(input?.transcript_path) ?? stringValue(input?.transcriptPath);
|
|
36
|
+
if (!event || !sessionId || !transcriptPath) process.exit(0);
|
|
37
|
+
const home = process.env.MEMORAX_CODE_HOME?.trim() || join(homedir(), ".memorax-code");
|
|
38
|
+
const packageMetadata = await readRecord(join(pluginRoot, ".memorax-code-package.json"));
|
|
39
|
+
const codeBuddyHome = commonStringValue(process.env.CODEBUDDY_HOME)
|
|
40
|
+
?? commonStringValue(process.env.WORKBUDDY_HOME)
|
|
41
|
+
?? commonStringValue(packageMetadata.codeBuddyHome)
|
|
42
|
+
?? commonStringValue(input?.codebuddy_home)
|
|
43
|
+
?? commonStringValue(input?.codeBuddyHome)
|
|
44
|
+
?? defaultCodeBuddyHome();
|
|
45
|
+
try {
|
|
46
|
+
await writeCodeBuddyRuntimeObservation({ memoraxCodeHome: home, codeBuddyHome, pluginRoot });
|
|
47
|
+
} catch (error) {
|
|
48
|
+
if (process.env.MEMORAX_CODE_CODEBUDDY_HOOK_DEBUG === "1") {
|
|
49
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const pendingPath = join(home, "adapters", "codebuddy", "pending.json");
|
|
53
|
+
const reminderOptions = {
|
|
54
|
+
adapterDir: "codebuddy",
|
|
55
|
+
debugEnv: "MEMORAX_CODE_CODEBUDDY_HOOK_DEBUG",
|
|
56
|
+
memoraxCodeHome: home,
|
|
57
|
+
runtime: "codebuddy",
|
|
58
|
+
supplementalReminderAfterCompact: true,
|
|
59
|
+
};
|
|
60
|
+
const personalMemoryContextOptions = {
|
|
61
|
+
adapterDir: "codebuddy",
|
|
62
|
+
debugEnv: "MEMORAX_CODE_CODEBUDDY_HOOK_DEBUG",
|
|
63
|
+
sessionKeyPrefix: "codebuddy",
|
|
64
|
+
};
|
|
65
|
+
if (event === "SessionStart") await bindMemoryCliTraceSession(sessionId);
|
|
66
|
+
await ensureBackendAvailable({
|
|
67
|
+
ensureBackendValue: process.env.MEMORAX_CODE_CODEBUDDY_ENSURE_BACKEND
|
|
68
|
+
?? process.env.MEMORAX_CODE_CODEBUDDY_HOOK_ENSURE_BACKEND,
|
|
69
|
+
healthTimeoutValue: process.env.MEMORAX_CODE_CODEBUDDY_ENSURE_TIMEOUT_MS,
|
|
70
|
+
startTimeoutValue: process.env.MEMORAX_CODE_CODEBUDDY_START_TIMEOUT_MS,
|
|
71
|
+
memoraxCodeCommand: commonStringValue(process.env.MEMORAX_CODE_CODEBUDDY_LIFECYCLE_COMMAND)
|
|
72
|
+
?? commonStringValue(process.env.MEMORAX_CODE_COMMAND),
|
|
73
|
+
pluginRoot,
|
|
74
|
+
resolveHomes: () => ({
|
|
75
|
+
memoraxCodeHome: home,
|
|
76
|
+
codeBuddyHome,
|
|
77
|
+
}),
|
|
78
|
+
buildStartArgs: (homes, recoveryArguments) => [
|
|
79
|
+
"start",
|
|
80
|
+
"--home", homes.memoraxCodeHome,
|
|
81
|
+
"--clients", "codebuddy",
|
|
82
|
+
"--codebuddy-home", homes.codeBuddyHome,
|
|
83
|
+
...recoveryArguments,
|
|
84
|
+
],
|
|
85
|
+
debug: (message) => { if (process.env.MEMORAX_CODE_CODEBUDDY_HOOK_DEBUG === "1") console.error(message); },
|
|
86
|
+
}, input);
|
|
87
|
+
if (event === "SessionStart") {
|
|
88
|
+
markSupplementalReminderAfterCompact(reminderOptions, input);
|
|
89
|
+
} else if (event === "UserPromptSubmit") {
|
|
90
|
+
const prompt = stringValue(input.prompt);
|
|
91
|
+
if (!prompt) process.exit(0);
|
|
92
|
+
const boundary = await fileBoundary(transcriptPath);
|
|
93
|
+
const turnId = provisionalTurnId(sessionId, boundary, prompt);
|
|
94
|
+
const workspaceKind = stringValue(input.workspace_kind) ?? stringValue(input.workspaceKind);
|
|
95
|
+
await updatePending(pendingPath, (state) => {
|
|
96
|
+
const now = Date.now();
|
|
97
|
+
const existing = state[sessionId];
|
|
98
|
+
state[sessionId] = {
|
|
99
|
+
version: 1,
|
|
100
|
+
turnId,
|
|
101
|
+
transcriptPath,
|
|
102
|
+
cwd: stringValue(input.cwd),
|
|
103
|
+
workspaceKind,
|
|
104
|
+
createdAt: existing?.turnId === turnId ? existing.createdAt : now,
|
|
105
|
+
updatedAt: now,
|
|
106
|
+
};
|
|
107
|
+
});
|
|
108
|
+
const response = await post("/memory/turn-start", { version: 1, client: "codebuddy", sessionId, turnId, transcriptPath, prompt, cwd: stringValue(input.cwd), workspaceKind });
|
|
109
|
+
const repoMemoryWorktree = stringValue(response?.repoMemoryWorktree);
|
|
110
|
+
scheduleMissingRepoMemoryBuild(repoMemoryWorktree, {
|
|
111
|
+
debugEnv: "MEMORAX_CODE_CODEBUDDY_HOOK_DEBUG",
|
|
112
|
+
pluginRoot,
|
|
113
|
+
});
|
|
114
|
+
const reminderResult = await evaluateMemorySkillReminder({
|
|
115
|
+
...reminderOptions,
|
|
116
|
+
additionalReminderContext: personalMemoryReminderContext(MEMORY_SKILL_INVOCATION),
|
|
117
|
+
memorySkillInvocation: MEMORY_SKILL_INVOCATION,
|
|
118
|
+
remindOnFirstTurn: true,
|
|
119
|
+
requireTranscriptPath: true,
|
|
120
|
+
...(repoMemoryWorktree ? {
|
|
121
|
+
buildCadenceReminderContext: (hookInput) => buildRepoProcedureMemoryContext({
|
|
122
|
+
...hookInput,
|
|
123
|
+
cwd: repoMemoryWorktree,
|
|
124
|
+
}, personalMemoryContextOptions),
|
|
125
|
+
buildPersonalMemoryContext: (hookInput) => buildRepoUserProfilePreferencesContext({
|
|
126
|
+
...hookInput,
|
|
127
|
+
cwd: repoMemoryWorktree,
|
|
128
|
+
}, personalMemoryContextOptions),
|
|
129
|
+
} : {}),
|
|
130
|
+
}, { ...input, turnId, workspaceKind });
|
|
131
|
+
const context = [
|
|
132
|
+
stringValue(response?.additionalContext),
|
|
133
|
+
stringValue(reminderResult?.additionalContext),
|
|
134
|
+
].filter(Boolean).join("\n\n");
|
|
135
|
+
const systemMessage = stringValue(response?.userNotice);
|
|
136
|
+
if (context || systemMessage) process.stdout.write(`${JSON.stringify({
|
|
137
|
+
...(systemMessage ? { systemMessage } : {}),
|
|
138
|
+
...(context ? { hookSpecificOutput: { hookEventName: "UserPromptSubmit", additionalContext: context } } : {}),
|
|
139
|
+
})}\n`);
|
|
140
|
+
if (response !== undefined && reminderResult?.reminder) await recordReminder(reminderResult.reminder);
|
|
141
|
+
} else if (event === "Stop") {
|
|
142
|
+
const record = (await readPending(pendingPath))[sessionId];
|
|
143
|
+
if (!record || record.transcriptPath !== transcriptPath) process.exit(0);
|
|
144
|
+
const response = await post("/memory/writeback", { version: 1, client: "codebuddy", sessionId, turnId: record.turnId, transcriptPath, cwd: record.cwd ?? stringValue(input.cwd), workspaceKind: record.workspaceKind ?? stringValue(input.workspaceKind) });
|
|
145
|
+
if (response?.ok === true && response?.scheduled === true) {
|
|
146
|
+
await updatePending(pendingPath, (state) => {
|
|
147
|
+
if (state[sessionId]?.turnId === record.turnId) delete state[sessionId];
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async function recordReminder(reminder) {
|
|
153
|
+
if (!reminder.turnId || !reminder.transcriptPath) return;
|
|
154
|
+
await post("/memory/skill-reminder", {
|
|
155
|
+
version: 1,
|
|
156
|
+
client: "codebuddy",
|
|
157
|
+
sessionId: reminder.sessionId,
|
|
158
|
+
turnId: reminder.turnId,
|
|
159
|
+
transcriptPath: reminder.transcriptPath,
|
|
160
|
+
cwd: reminder.cwd,
|
|
161
|
+
workspaceKind: reminder.workspaceKind,
|
|
162
|
+
content: reminder.content,
|
|
163
|
+
triggers: reminder.triggers,
|
|
164
|
+
}, REMINDER_TRACE_TIMEOUT_MS);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
async function post(path, body, timeoutMs = 12_000) {
|
|
168
|
+
let connection;
|
|
169
|
+
try {
|
|
170
|
+
connection = resolveBackendConnection({ memoraxCodeHome: home });
|
|
171
|
+
} catch (error) {
|
|
172
|
+
if (process.env.MEMORAX_CODE_CODEBUDDY_HOOK_DEBUG === "1") console.error(error instanceof Error ? error.message : String(error));
|
|
173
|
+
return undefined;
|
|
174
|
+
}
|
|
175
|
+
const headers = { "content-type": "application/json", connection: "close" };
|
|
176
|
+
if (connection.token) headers["x-memorax-code-backend-token"] = connection.token;
|
|
177
|
+
try { const response = await fetch(new URL(path, connection.url), { method: "POST", headers, body: JSON.stringify(body), signal: AbortSignal.timeout(timeoutMs) }); return response.ok ? await response.json().catch(() => undefined) : undefined; } catch { return undefined; }
|
|
178
|
+
}
|
|
179
|
+
async function fileBoundary(path) { try { return (await stat(path)).size; } catch { return 0; } }
|
|
180
|
+
async function readJsonStdin() { try { let text = ""; for await (const chunk of process.stdin) text += chunk; return JSON.parse(text); } catch { return {}; } }
|
|
181
|
+
async function readRecord(path) { try { const value = JSON.parse(await readFile(path, "utf8")); return value && typeof value === "object" && !Array.isArray(value) ? value : {}; } catch { return {}; } }
|
|
182
|
+
async function writeRecord(path, value) { await mkdir(dirname(path), { recursive: true }); await writeFile(path, `${JSON.stringify(value)}\n`, { mode: 0o600 }); }
|
|
183
|
+
async function readPending(path) {
|
|
184
|
+
return await withJsonFileLockAsync(path, async () => {
|
|
185
|
+
const state = await readRecord(path);
|
|
186
|
+
prunePendingState(state);
|
|
187
|
+
await writeRecord(path, state);
|
|
188
|
+
return state;
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
async function updatePending(path, mutate) {
|
|
192
|
+
await withJsonFileLockAsync(path, async () => {
|
|
193
|
+
const state = await readRecord(path);
|
|
194
|
+
prunePendingState(state);
|
|
195
|
+
mutate(state);
|
|
196
|
+
prunePendingState(state);
|
|
197
|
+
await writeRecord(path, state);
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
async function withJsonFileLockAsync(path, operation) {
|
|
201
|
+
const lockPath = `${path}.lock`;
|
|
202
|
+
const deadline = Date.now() + 1500;
|
|
203
|
+
let acquired = false;
|
|
204
|
+
await mkdir(dirname(path), { recursive: true, mode: 0o700 });
|
|
205
|
+
while (!acquired) {
|
|
206
|
+
try {
|
|
207
|
+
await mkdir(lockPath, { recursive: false, mode: 0o700 });
|
|
208
|
+
acquired = true;
|
|
209
|
+
} catch (error) {
|
|
210
|
+
if (error?.code !== "EEXIST") throw error;
|
|
211
|
+
try {
|
|
212
|
+
const lock = await stat(lockPath);
|
|
213
|
+
if (Date.now() - lock.mtimeMs > 30_000) await rm(lockPath, { recursive: true, force: true });
|
|
214
|
+
} catch {}
|
|
215
|
+
if (Date.now() >= deadline) {
|
|
216
|
+
const timeout = new Error(`timed out waiting for CodeBuddy Hook state lock: ${lockPath}`);
|
|
217
|
+
timeout.code = "JSON_FILE_LOCK_TIMEOUT";
|
|
218
|
+
throw timeout;
|
|
219
|
+
}
|
|
220
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
try { return await operation(); } finally { await rm(lockPath, { recursive: true, force: true }); }
|
|
224
|
+
}
|
|
225
|
+
async function bindMemoryCliTraceSession(sessionId) {
|
|
226
|
+
const envFile = stringValue(process.env.CODEBUDDY_ENV_FILE);
|
|
227
|
+
if (!envFile) return;
|
|
228
|
+
try {
|
|
229
|
+
await appendFile(envFile, [
|
|
230
|
+
"export MEMORAX_CODE_MEMORY_CLI_TRACE_CLIENT='codebuddy'",
|
|
231
|
+
`export MEMORAX_CODE_MEMORY_CLI_TRACE_SESSION_ID=${shellSingleQuote(sessionId)}`,
|
|
232
|
+
"",
|
|
233
|
+
].join("\n"), "utf8");
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (process.env.MEMORAX_CODE_CODEBUDDY_HOOK_DEBUG === "1") {
|
|
236
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
function shellSingleQuote(value) { return `'${value.replaceAll("'", "'\"'\"'")}'`; }
|
|
241
|
+
function stringValue(value) { return typeof value === "string" && value.trim() ? value.trim() : undefined; }
|
|
242
|
+
|
|
243
|
+
function defaultCodeBuddyHome() {
|
|
244
|
+
const workBuddyHome = join(homedir(), ".workbuddy");
|
|
245
|
+
if (process.platform !== "win32") return workBuddyHome;
|
|
246
|
+
const legacyCodeBuddyHome = join(homedir(), ".codebuddy");
|
|
247
|
+
return existsSync(workBuddyHome) || !existsSync(legacyCodeBuddyHome)
|
|
248
|
+
? workBuddyHome
|
|
249
|
+
: legacyCodeBuddyHome;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function provisionalTurnId(sessionId, boundary, prompt) {
|
|
253
|
+
return `${sessionId}:${boundary}:${createHash("sha256").update(prompt.trim()).digest("hex")}`;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function validProvisionalTurnId(sessionId, turnId) {
|
|
257
|
+
const prefix = `${sessionId}:`;
|
|
258
|
+
if (typeof turnId !== "string" || !turnId.startsWith(prefix)) return false;
|
|
259
|
+
const match = /^(0|[1-9]\d*):[0-9a-f]{64}$/.exec(turnId.slice(prefix.length));
|
|
260
|
+
return Boolean(match && Number.isSafeInteger(Number(match[1])));
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function prunePendingState(state) {
|
|
264
|
+
const now = Date.now();
|
|
265
|
+
const ttl = positiveInteger(process.env.MEMORAX_CODE_CODEBUDDY_PENDING_TTL_MS, 24 * 60 * 60 * 1000);
|
|
266
|
+
const maxEntries = positiveInteger(process.env.MEMORAX_CODE_CODEBUDDY_PENDING_MAX_ENTRIES, 200);
|
|
267
|
+
for (const [sessionId, record] of Object.entries(state)) {
|
|
268
|
+
if (!record || typeof record !== "object" || Array.isArray(record)) {
|
|
269
|
+
delete state[sessionId];
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
272
|
+
if (record.version !== 1 || !validProvisionalTurnId(sessionId, record.turnId) || !stringValue(record.transcriptPath)) {
|
|
273
|
+
delete state[sessionId];
|
|
274
|
+
continue;
|
|
275
|
+
}
|
|
276
|
+
if (!Number.isFinite(record.createdAt) || !Number.isFinite(record.updatedAt) || now - record.updatedAt > ttl) {
|
|
277
|
+
delete state[sessionId];
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
const entries = Object.entries(state).sort(([, left], [, right]) => Number(left.updatedAt ?? left.createdAt ?? 0) - Number(right.updatedAt ?? right.createdAt ?? 0));
|
|
281
|
+
while (entries.length > maxEntries) {
|
|
282
|
+
const [sessionId] = entries.shift();
|
|
283
|
+
delete state[sessionId];
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
function positiveInteger(value, fallback) {
|
|
287
|
+
const parsed = Number(value);
|
|
288
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : fallback;
|
|
289
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@memorax-code/codebuddy-adapter",
|
|
3
|
+
"version": "0.1.11",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"description": "CodeBuddy and WorkBuddy Hook adapter for MemoraX Code memory services.",
|
|
7
|
+
"bin": { "memorax-code-codebuddy": "src/cli.mjs" },
|
|
8
|
+
"scripts": { "test": "node --test test/*.test.mjs" }
|
|
9
|
+
}
|