claude-memory-layer 2.2.5 → 2.2.6
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 +30 -4
- package/dist/cli/index.js +391 -147
- package/dist/cli/index.js.map +4 -4
- package/dist/hooks/codex-session-end.js +124 -0
- package/dist/hooks/codex-session-end.js.map +7 -0
- package/dist/hooks/codex-session-import-worker.js +16798 -0
- package/dist/hooks/codex-session-import-worker.js.map +7 -0
- package/dist/hooks/codex-session-start.js +16660 -0
- package/dist/hooks/codex-session-start.js.map +7 -0
- package/package.json +1 -1
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'module';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// src/adapters/codex/hooks/session-end.ts
|
|
10
|
+
import { spawn } from "node:child_process";
|
|
11
|
+
import * as nodeUrl from "node:url";
|
|
12
|
+
import * as path from "node:path";
|
|
13
|
+
|
|
14
|
+
// src/adapters/claude/hooks/hook-runtime.ts
|
|
15
|
+
var DEFAULT_STDIN_TIMEOUT_MS = 1e4;
|
|
16
|
+
var DEFAULT_OVERALL_TIMEOUT_MS = 3e4;
|
|
17
|
+
function readNumberEnv(name, fallback, options = {}) {
|
|
18
|
+
const raw = process.env[name];
|
|
19
|
+
if (raw === void 0 || raw.trim() === "") return fallback;
|
|
20
|
+
const parsed = options.integer ? Number.parseInt(raw, 10) : Number.parseFloat(raw);
|
|
21
|
+
if (!Number.isFinite(parsed)) return fallback;
|
|
22
|
+
let value = parsed;
|
|
23
|
+
if (options.min !== void 0) value = Math.max(options.min, value);
|
|
24
|
+
if (options.max !== void 0) value = Math.min(options.max, value);
|
|
25
|
+
return value;
|
|
26
|
+
}
|
|
27
|
+
function readPositiveIntEnv(name, fallback) {
|
|
28
|
+
return readNumberEnv(name, fallback, { integer: true, min: 1 });
|
|
29
|
+
}
|
|
30
|
+
function readStdin(options = {}) {
|
|
31
|
+
const timeoutMs = options.timeoutMs ?? readPositiveIntEnv("CLAUDE_MEMORY_HOOK_STDIN_TIMEOUT_MS", DEFAULT_STDIN_TIMEOUT_MS);
|
|
32
|
+
return new Promise((resolve) => {
|
|
33
|
+
const stdin = process.stdin;
|
|
34
|
+
let data = "";
|
|
35
|
+
let settled = false;
|
|
36
|
+
const onData = (chunk) => {
|
|
37
|
+
data += chunk;
|
|
38
|
+
};
|
|
39
|
+
const finish = () => {
|
|
40
|
+
if (settled) return;
|
|
41
|
+
settled = true;
|
|
42
|
+
clearTimeout(timer);
|
|
43
|
+
stdin.off("data", onData);
|
|
44
|
+
stdin.off("end", finish);
|
|
45
|
+
stdin.off("error", finish);
|
|
46
|
+
resolve(data);
|
|
47
|
+
};
|
|
48
|
+
const timer = setTimeout(finish, timeoutMs);
|
|
49
|
+
timer.unref?.();
|
|
50
|
+
stdin.setEncoding("utf8");
|
|
51
|
+
stdin.on("data", onData);
|
|
52
|
+
stdin.on("end", finish);
|
|
53
|
+
stdin.on("error", finish);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
function emitHookOutput(json) {
|
|
57
|
+
process.stdout.write(json.endsWith("\n") ? json : `${json}
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
async function runHook(options, run) {
|
|
61
|
+
const debug = Boolean(process.env.CLAUDE_MEMORY_DEBUG);
|
|
62
|
+
const timeoutMs = options.timeoutMs ?? readPositiveIntEnv("CLAUDE_MEMORY_HOOK_TIMEOUT_MS", DEFAULT_OVERALL_TIMEOUT_MS);
|
|
63
|
+
let emitted = false;
|
|
64
|
+
const emit = (json) => {
|
|
65
|
+
if (emitted) return;
|
|
66
|
+
emitted = true;
|
|
67
|
+
emitHookOutput(json);
|
|
68
|
+
};
|
|
69
|
+
const watchdog = setTimeout(() => {
|
|
70
|
+
if (debug) console.error(`[${options.name}] hook timed out after ${timeoutMs}ms; forcing exit`);
|
|
71
|
+
emit(options.fallbackOutput);
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}, timeoutMs);
|
|
74
|
+
watchdog.unref?.();
|
|
75
|
+
try {
|
|
76
|
+
emit(await run());
|
|
77
|
+
} catch (error) {
|
|
78
|
+
if (debug) console.error(`[${options.name}] hook error:`, error);
|
|
79
|
+
emit(options.fallbackOutput);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// src/adapters/codex/hooks/session-end.ts
|
|
84
|
+
function defaultWorkerPath() {
|
|
85
|
+
return path.join(path.dirname(nodeUrl.fileURLToPath(import.meta.url)), "codex-session-import-worker.js");
|
|
86
|
+
}
|
|
87
|
+
var realDeps = {
|
|
88
|
+
spawnWorker: spawn,
|
|
89
|
+
workerPath: defaultWorkerPath()
|
|
90
|
+
};
|
|
91
|
+
function normalizeCodexSessionImportJob(input) {
|
|
92
|
+
const transcriptPath = typeof input.transcript_path === "string" ? input.transcript_path.trim() : "";
|
|
93
|
+
const projectPath = typeof input.cwd === "string" ? input.cwd.trim() : "";
|
|
94
|
+
if (!transcriptPath || !projectPath || !path.isAbsolute(projectPath)) return null;
|
|
95
|
+
return { transcriptPath, projectPath };
|
|
96
|
+
}
|
|
97
|
+
function launchCodexSessionImport(input, deps = realDeps) {
|
|
98
|
+
const job = normalizeCodexSessionImportJob(input);
|
|
99
|
+
if (!job) return false;
|
|
100
|
+
const child = deps.spawnWorker(process.execPath, [deps.workerPath], {
|
|
101
|
+
detached: process.platform !== "win32",
|
|
102
|
+
stdio: ["pipe", "ignore", "ignore"]
|
|
103
|
+
});
|
|
104
|
+
child.stdin?.on("error", () => {
|
|
105
|
+
});
|
|
106
|
+
child.stdin?.end(JSON.stringify(job));
|
|
107
|
+
child.unref();
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
async function main() {
|
|
111
|
+
try {
|
|
112
|
+
const input = JSON.parse(await readStdin());
|
|
113
|
+
launchCodexSessionImport(input);
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (process.env.CLAUDE_MEMORY_DEBUG) {
|
|
116
|
+
console.error("Codex memory hook error:", error);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
return JSON.stringify({});
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// src/hooks/codex-session-end.ts
|
|
123
|
+
void runHook({ name: "codex-session-end", fallbackOutput: "{}", timeoutMs: 2500 }, main);
|
|
124
|
+
//# sourceMappingURL=codex-session-end.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../../src/adapters/codex/hooks/session-end.ts", "../../src/adapters/claude/hooks/hook-runtime.ts", "../../src/hooks/codex-session-end.ts"],
|
|
4
|
+
"sourcesContent": ["import { spawn, type ChildProcess } from 'node:child_process';\nimport * as nodeUrl from 'node:url';\nimport * as path from 'node:path';\n\nimport { readStdin } from '../../claude/hooks/hook-runtime.js';\n\nexport interface CodexSessionEndHookInput {\n session_id: string;\n transcript_path: string | null;\n cwd: string;\n hook_event_name?: string;\n}\n\nexport interface CodexSessionImportJob {\n transcriptPath: string;\n projectPath: string;\n}\n\nexport interface CodexSessionEndHookDeps {\n spawnWorker: (\n executable: string,\n args: string[],\n options: {\n detached: boolean;\n stdio: ['pipe', 'ignore', 'ignore'];\n }\n ) => Pick<ChildProcess, 'stdin' | 'unref'>;\n workerPath: string;\n}\n\nfunction defaultWorkerPath(): string {\n return path.join(path.dirname(nodeUrl.fileURLToPath(import.meta.url)), 'codex-session-import-worker.js');\n}\n\nconst realDeps: CodexSessionEndHookDeps = {\n spawnWorker: spawn,\n workerPath: defaultWorkerPath()\n};\n\nexport function normalizeCodexSessionImportJob(input: CodexSessionEndHookInput): CodexSessionImportJob | null {\n const transcriptPath = typeof input.transcript_path === 'string' ? input.transcript_path.trim() : '';\n const projectPath = typeof input.cwd === 'string' ? input.cwd.trim() : '';\n if (!transcriptPath || !projectPath || !path.isAbsolute(projectPath)) return null;\n return { transcriptPath, projectPath };\n}\n\nexport function launchCodexSessionImport(\n input: CodexSessionEndHookInput,\n deps: CodexSessionEndHookDeps = realDeps\n): boolean {\n const job = normalizeCodexSessionImportJob(input);\n if (!job) return false;\n\n const child = deps.spawnWorker(process.execPath, [deps.workerPath], {\n detached: process.platform !== 'win32',\n stdio: ['pipe', 'ignore', 'ignore']\n });\n child.stdin?.on('error', () => {\n // Codex must not fail its lifecycle when a detached worker exits early.\n });\n child.stdin?.end(JSON.stringify(job));\n child.unref();\n return true;\n}\n\nexport async function main(): Promise<string> {\n try {\n const input = JSON.parse(await readStdin()) as CodexSessionEndHookInput;\n launchCodexSessionImport(input);\n } catch (error) {\n if (process.env.CLAUDE_MEMORY_DEBUG) {\n console.error('Codex memory hook error:', error);\n }\n }\n return JSON.stringify({});\n}\n", "/**\n * Shared runtime for Claude Code hook processes.\n *\n * Hooks are short-lived processes that Claude Code spawns per lifecycle event.\n * They MUST fail safe: on any error (including malformed stdin) they still need\n * to emit a single valid JSON envelope on stdout, and they must never hang the\n * host indefinitely. This module centralizes those guarantees so each hook body\n * can focus on its logic and simply return the JSON string to emit.\n */\n\nconst DEFAULT_STDIN_TIMEOUT_MS = 10_000;\nconst DEFAULT_OVERALL_TIMEOUT_MS = 30_000;\n\n/**\n * Parse a numeric environment variable with a guaranteed finite fallback.\n *\n * A non-numeric value (typo like `CLAUDE_MEMORY_MAX_COUNT=five`) would otherwise\n * yield NaN and silently poison every downstream comparison (`x >= NaN` is always\n * false), so retrieval/threshold logic must never accept a raw parse result.\n */\nexport function readNumberEnv(\n name: string,\n fallback: number,\n options: { min?: number; max?: number; integer?: boolean } = {}\n): number {\n const raw = process.env[name];\n if (raw === undefined || raw.trim() === '') return fallback;\n const parsed = options.integer ? Number.parseInt(raw, 10) : Number.parseFloat(raw);\n if (!Number.isFinite(parsed)) return fallback;\n let value = parsed;\n if (options.min !== undefined) value = Math.max(options.min, value);\n if (options.max !== undefined) value = Math.min(options.max, value);\n return value;\n}\n\nfunction readPositiveIntEnv(name: string, fallback: number): number {\n return readNumberEnv(name, fallback, { integer: true, min: 1 });\n}\n\n/**\n * Read all of stdin as a string, with a hard timeout and an error handler so a\n * parent that never closes the pipe (or half-closes it) cannot wedge the hook\n * forever. On timeout/error it resolves with whatever was buffered so far\n * (usually empty) rather than rejecting; the caller's JSON.parse will then fail\n * and the hook emits its safe fallback.\n */\nexport function readStdin(options: { timeoutMs?: number } = {}): Promise<string> {\n const timeoutMs = options.timeoutMs ?? readPositiveIntEnv('CLAUDE_MEMORY_HOOK_STDIN_TIMEOUT_MS', DEFAULT_STDIN_TIMEOUT_MS);\n return new Promise((resolve) => {\n const stdin = process.stdin;\n let data = '';\n let settled = false;\n\n const onData = (chunk: string) => {\n data += chunk;\n };\n const finish = () => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n stdin.off('data', onData);\n stdin.off('end', finish);\n stdin.off('error', finish);\n resolve(data);\n };\n\n const timer = setTimeout(finish, timeoutMs);\n timer.unref?.();\n\n stdin.setEncoding('utf8');\n stdin.on('data', onData);\n stdin.on('end', finish);\n stdin.on('error', finish);\n });\n}\n\n/** Write exactly one newline-terminated JSON envelope to stdout. */\nfunction emitHookOutput(json: string): void {\n process.stdout.write(json.endsWith('\\n') ? json : `${json}\\n`);\n}\n\nexport interface RunHookOptions {\n /** Hook name, used only for debug logging. */\n name: string;\n /** JSON string emitted to stdout if the hook throws or times out. */\n fallbackOutput: string;\n /** Hard wall-clock ceiling; on expiry the process is forced to exit. */\n timeoutMs?: number;\n}\n\n/**\n * Execute a hook body and guarantee exactly one stdout emission plus a bounded\n * lifetime.\n *\n * - `run` returns the JSON string to emit on success.\n * - Any thrown error (including a JSON.parse failure on malformed stdin) is\n * caught and the `fallbackOutput` is emitted instead \u2014 so Claude Code always\n * receives a valid envelope.\n * - Output is emitted as soon as `run` settles, so the host is never blocked on\n * best-effort background work. That fire-and-forget work is allowed to finish\n * during natural event-loop drain.\n * - An unref'd watchdog is the hard ceiling: if some in-process async operation\n * hangs past `timeoutMs`, the process force-exits (emitting the fallback first\n * if nothing was emitted yet) instead of lingering with an open DB handle.\n */\nexport async function runHook(options: RunHookOptions, run: () => Promise<string>): Promise<void> {\n const debug = Boolean(process.env.CLAUDE_MEMORY_DEBUG);\n const timeoutMs = options.timeoutMs ?? readPositiveIntEnv('CLAUDE_MEMORY_HOOK_TIMEOUT_MS', DEFAULT_OVERALL_TIMEOUT_MS);\n let emitted = false;\n\n const emit = (json: string) => {\n if (emitted) return;\n emitted = true;\n emitHookOutput(json);\n };\n\n const watchdog = setTimeout(() => {\n if (debug) console.error(`[${options.name}] hook timed out after ${timeoutMs}ms; forcing exit`);\n emit(options.fallbackOutput);\n process.exit(0);\n }, timeoutMs);\n watchdog.unref?.();\n\n try {\n emit(await run());\n } catch (error) {\n if (debug) console.error(`[${options.name}] hook error:`, error);\n emit(options.fallbackOutput);\n }\n // Intentionally no forced exit here: allow best-effort background work\n // (daemon spawn, summary backfill) to complete during natural drain. The\n // unref'd watchdog above is the only hard ceiling.\n}\n", "#!/usr/bin/env node\nimport { main } from '../adapters/codex/hooks/session-end.js';\nimport { runHook } from '../adapters/claude/hooks/hook-runtime.js';\n\nvoid runHook({ name: 'codex-session-end', fallbackOutput: '{}', timeoutMs: 2_500 }, main);\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": []
|
|
7
|
+
}
|