memory-eternal 0.9.23 → 0.10.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/hooks/capture.mjs CHANGED
@@ -1,77 +1,77 @@
1
- #!/usr/bin/env node
2
- // 记忆核心 · 通用「会话结束自动沉淀」hook(claude-code / codex / cursor 共用)。
3
- //
4
- // 用法:node capture.mjs <agent> agent ∈ claude-code | codex | cursor
5
- // stdin(平台 hook 注入):
6
- // { session_id, transcript_path, hook_event_name, cwd, last_assistant_message, prompt, ... }
7
- //
8
- // 行为:读 transcript(JSONL/JSON 兼容)或显式文本字段 → 调 runStandaloneCapture 蒸馏入库。
9
- // 设计约束:
10
- // - 静默失败(hook 崩溃不能影响宿主 agent)
11
- // - 幂等(去重/追加在 runStandaloneCapture 内处理)
12
- // - 署名 source = agent 名,便于智能体筛选;vault 由 MEMORY_VAULT_DIR 或 ~/.dsh/memory-vault 决定(与全库统一)
13
- //
14
- // Windows 友好:纯 node,无外部依赖(同 claude-session.js)。
15
-
16
- import { promises as fs } from 'node:fs'
17
-
18
- const agent = process.argv[2] || 'agent'
19
-
20
- const readStdin = () => new Promise((resolve) => {
21
- let buf = ''
22
- process.stdin.setEncoding('utf8')
23
- process.stdin.on('data', (c) => { buf += c })
24
- process.stdin.on('end', () => resolve(buf))
25
- setTimeout(() => resolve(buf), 2000)
26
- })
27
-
28
- /** 从 message 对象里抽可读文本(兼容 claude 的 {role,content} 与 codex/cursor 的 {text|content})。 */
29
- function textOf(msg) {
30
- if (!msg) return ''
31
- if (typeof msg === 'string') return msg
32
- const c = msg.content
33
- if (typeof c === 'string') return c
34
- if (Array.isArray(c)) return c.filter((x) => x && x.type === 'text').map((x) => x.text).join('\n')
35
- return msg.text || msg.message || ''
36
- }
37
-
38
- /** 读 transcript 文件并抽取最近对话文本。优先 JSONL 行解析,退回整体文本。 */
39
- async function extractTranscript(transcriptPath) {
40
- let text
41
- try { text = await fs.readFile(transcriptPath, 'utf8') } catch { return '' }
42
- if (!text.trim()) return ''
43
- text = text.replace(/^\uFEFF/, '') // 去掉可能存在的 UTF-8 BOM
44
- const lines = text.split('\n').filter(Boolean)
45
- const parts = []
46
- let structured = 0
47
- for (const line of lines.slice(-400)) {
48
- let rec
49
- try { rec = JSON.parse(line); structured++ } catch { continue }
50
- const t = textOf(rec?.message)
51
- if (t && t.trim().length > 4) {
52
- const role = rec?.message?.role || rec?.role || 'msg'
53
- parts.push(`${role}: ${t.trim()}`)
54
- }
55
- }
56
- if (structured >= 1 && parts.length) return parts.slice(-30).join('\n\n')
57
- // 非结构化:取尾部可读文本(去掉控制字符)
58
- return text.replace(/\r/g, '').replace(/\u0000/g, '').slice(-6000)
59
- }
60
-
61
- async function main() {
62
- let meta = {}
63
- try { meta = JSON.parse((await readStdin()) || '{}') } catch { /* 静默 */ }
64
-
65
- let conversation = ''
66
- if (meta.transcript_path) { try { conversation = await extractTranscript(meta.transcript_path) } catch { /* 静默 */ } }
67
- if (!conversation || conversation.trim().length < 120) {
68
- conversation = meta.last_assistant_message || meta.prompt || meta.context || ''
69
- }
70
- if (!conversation || conversation.trim().length < 120) return
71
-
72
- const { runStandaloneCapture, defaultVaultDir } = await import('../lib/capture-run.js')
73
- const src = agent // 署名 = agent 名(可筛选);若想保留会话粒度可用 `${agent}:${session_id}`
74
- await runStandaloneCapture(defaultVaultDir(), conversation, { source: src })
75
- }
76
-
77
- main().catch(() => { /* hook 静默失败 */ })
1
+ #!/usr/bin/env node
2
+ // 记忆核心 · 通用「会话结束自动沉淀」hook(claude-code / codex / cursor 共用)。
3
+ //
4
+ // 用法:node capture.mjs <agent> agent ∈ claude-code | codex | cursor
5
+ // stdin(平台 hook 注入):
6
+ // { session_id, transcript_path, hook_event_name, cwd, last_assistant_message, prompt, ... }
7
+ //
8
+ // 行为:读 transcript(JSONL/JSON 兼容)或显式文本字段 → 调 runStandaloneCapture 蒸馏入库。
9
+ // 设计约束:
10
+ // - 静默失败(hook 崩溃不能影响宿主 agent)
11
+ // - 幂等(去重/追加在 runStandaloneCapture 内处理)
12
+ // - 署名 source = agent 名,便于智能体筛选;vault 由 MEMORY_VAULT_DIR 或 ~/.dsh/memory-vault 决定(与全库统一)
13
+ //
14
+ // Windows 友好:纯 node,无外部依赖(同 claude-session.js)。
15
+
16
+ import { promises as fs } from 'node:fs'
17
+
18
+ const agent = process.argv[2] || 'agent'
19
+
20
+ const readStdin = () => new Promise((resolve) => {
21
+ let buf = ''
22
+ process.stdin.setEncoding('utf8')
23
+ process.stdin.on('data', (c) => { buf += c })
24
+ process.stdin.on('end', () => resolve(buf))
25
+ setTimeout(() => resolve(buf), 2000)
26
+ })
27
+
28
+ /** 从 message 对象里抽可读文本(兼容 claude 的 {role,content} 与 codex/cursor 的 {text|content})。 */
29
+ function textOf(msg) {
30
+ if (!msg) return ''
31
+ if (typeof msg === 'string') return msg
32
+ const c = msg.content
33
+ if (typeof c === 'string') return c
34
+ if (Array.isArray(c)) return c.filter((x) => x && x.type === 'text').map((x) => x.text).join('\n')
35
+ return msg.text || msg.message || ''
36
+ }
37
+
38
+ /** 读 transcript 文件并抽取最近对话文本。优先 JSONL 行解析,退回整体文本。 */
39
+ async function extractTranscript(transcriptPath) {
40
+ let text
41
+ try { text = await fs.readFile(transcriptPath, 'utf8') } catch { return '' }
42
+ if (!text.trim()) return ''
43
+ text = text.replace(/^\uFEFF/, '') // 去掉可能存在的 UTF-8 BOM
44
+ const lines = text.split('\n').filter(Boolean)
45
+ const parts = []
46
+ let structured = 0
47
+ for (const line of lines.slice(-400)) {
48
+ let rec
49
+ try { rec = JSON.parse(line); structured++ } catch { continue }
50
+ const t = textOf(rec?.message)
51
+ if (t && t.trim().length > 4) {
52
+ const role = rec?.message?.role || rec?.role || 'msg'
53
+ parts.push(`${role}: ${t.trim()}`)
54
+ }
55
+ }
56
+ if (structured >= 1 && parts.length) return parts.slice(-30).join('\n\n')
57
+ // 非结构化:取尾部可读文本(去掉控制字符)
58
+ return text.replace(/\r/g, '').replace(/\u0000/g, '').slice(-6000)
59
+ }
60
+
61
+ async function main() {
62
+ let meta = {}
63
+ try { meta = JSON.parse((await readStdin()) || '{}') } catch { /* 静默 */ }
64
+
65
+ let conversation = ''
66
+ if (meta.transcript_path) { try { conversation = await extractTranscript(meta.transcript_path) } catch { /* 静默 */ } }
67
+ if (!conversation || conversation.trim().length < 120) {
68
+ conversation = meta.last_assistant_message || meta.prompt || meta.context || ''
69
+ }
70
+ if (!conversation || conversation.trim().length < 120) return
71
+
72
+ const { runStandaloneCapture, defaultVaultDir } = await import('../lib/capture-run.js')
73
+ const src = agent // 署名 = agent 名(可筛选);若想保留会话粒度可用 `${agent}:${session_id}`
74
+ await runStandaloneCapture(defaultVaultDir(), conversation, { source: src })
75
+ }
76
+
77
+ main().catch(() => { /* hook 静默失败 */ })
@@ -1,56 +1,56 @@
1
- #!/usr/bin/env node
2
- // 记忆核心 · Claude Code SessionEnd/PreCompact hook(Windows 友好,纯 node)。
3
- //
4
- // Claude Code 在会话结束/压缩前触发,stdin 传入 JSON:
5
- // { session_id, transcript_path, hook_event_name, cwd, ... }
6
- // 本脚本解析 transcript JSONL,提取最近若干轮 user/assistant 文本,蒸馏入库。
7
- // 由 lib/setup.js 自动注册到 ~/.claude/settings.json 的 hooks.SessionEnd。
8
- //
9
- // 特性:静默失败(hook 崩溃不能影响 Claude Code);幂等(重复跑同一段只追加)。
10
-
11
- import { promises as fs } from 'node:fs'
12
- import path from 'node:path'
13
-
14
- async function main() {
15
- let input = {}
16
- try {
17
- const raw = await new Promise((resolve) => {
18
- let buf = ''
19
- process.stdin.setEncoding('utf8')
20
- process.stdin.on('data', (c) => { buf += c })
21
- process.stdin.on('end', () => resolve(buf))
22
- setTimeout(() => resolve(buf), 2000)
23
- })
24
- input = JSON.parse(raw || '{}')
25
- } catch { /* 静默 */ }
26
-
27
- const transcriptPath = input.transcript_path
28
- if (!transcriptPath) return
29
- let lines
30
- try { lines = (await fs.readFile(transcriptPath, 'utf8')).split('\n').filter(Boolean) } catch { return }
31
-
32
- // 提取尾部对话文本(user 与 assistant 的 message.content 文本部分)
33
- const texts = []
34
- for (const line of lines.slice(-400)) {
35
- let rec
36
- try { rec = JSON.parse(line) } catch { continue }
37
- const msg = rec?.message
38
- if (!msg || !msg.role) continue
39
- const content = msg.content
40
- let text = ''
41
- if (typeof content === 'string') text = content
42
- else if (Array.isArray(content)) {
43
- text = content.filter((c) => c?.type === 'text').map((c) => c.text).join('\n')
44
- }
45
- if (text && text.trim().length > 4) texts.push(`${msg.role}: ${text.trim()}`)
46
- }
47
- const conversation = texts.slice(-30).join('\n\n')
48
- if (conversation.trim().length < 120) return
49
-
50
- const { runStandaloneCapture, defaultVaultDir } = await import('../lib/capture-run.js')
51
- await runStandaloneCapture(defaultVaultDir(), conversation, {
52
- source: `claude-code:${input.session_id || path.basename(transcriptPath)}`,
53
- })
54
- }
55
-
56
- main().catch(() => { /* hook 静默失败 */ })
1
+ #!/usr/bin/env node
2
+ // 记忆核心 · Claude Code SessionEnd/PreCompact hook(Windows 友好,纯 node)。
3
+ //
4
+ // Claude Code 在会话结束/压缩前触发,stdin 传入 JSON:
5
+ // { session_id, transcript_path, hook_event_name, cwd, ... }
6
+ // 本脚本解析 transcript JSONL,提取最近若干轮 user/assistant 文本,蒸馏入库。
7
+ // 由 lib/setup.js 自动注册到 ~/.claude/settings.json 的 hooks.SessionEnd。
8
+ //
9
+ // 特性:静默失败(hook 崩溃不能影响 Claude Code);幂等(重复跑同一段只追加)。
10
+
11
+ import { promises as fs } from 'node:fs'
12
+ import path from 'node:path'
13
+
14
+ async function main() {
15
+ let input = {}
16
+ try {
17
+ const raw = await new Promise((resolve) => {
18
+ let buf = ''
19
+ process.stdin.setEncoding('utf8')
20
+ process.stdin.on('data', (c) => { buf += c })
21
+ process.stdin.on('end', () => resolve(buf))
22
+ setTimeout(() => resolve(buf), 2000)
23
+ })
24
+ input = JSON.parse(raw || '{}')
25
+ } catch { /* 静默 */ }
26
+
27
+ const transcriptPath = input.transcript_path
28
+ if (!transcriptPath) return
29
+ let lines
30
+ try { lines = (await fs.readFile(transcriptPath, 'utf8')).split('\n').filter(Boolean) } catch { return }
31
+
32
+ // 提取尾部对话文本(user 与 assistant 的 message.content 文本部分)
33
+ const texts = []
34
+ for (const line of lines.slice(-400)) {
35
+ let rec
36
+ try { rec = JSON.parse(line) } catch { continue }
37
+ const msg = rec?.message
38
+ if (!msg || !msg.role) continue
39
+ const content = msg.content
40
+ let text = ''
41
+ if (typeof content === 'string') text = content
42
+ else if (Array.isArray(content)) {
43
+ text = content.filter((c) => c?.type === 'text').map((c) => c.text).join('\n')
44
+ }
45
+ if (text && text.trim().length > 4) texts.push(`${msg.role}: ${text.trim()}`)
46
+ }
47
+ const conversation = texts.slice(-30).join('\n\n')
48
+ if (conversation.trim().length < 120) return
49
+
50
+ const { runStandaloneCapture, defaultVaultDir } = await import('../lib/capture-run.js')
51
+ await runStandaloneCapture(defaultVaultDir(), conversation, {
52
+ source: `claude-code:${input.session_id || path.basename(transcriptPath)}`,
53
+ })
54
+ }
55
+
56
+ main().catch(() => { /* hook 静默失败 */ })
@@ -1,12 +1,12 @@
1
- {
2
- "SessionEnd": [
3
- {
4
- "hooks": [
5
- {
6
- "type": "command",
7
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" claude-code"
8
- }
9
- ]
10
- }
11
- ]
12
- }
1
+ {
2
+ "SessionEnd": [
3
+ {
4
+ "hooks": [
5
+ {
6
+ "type": "command",
7
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" claude-code"
8
+ }
9
+ ]
10
+ }
11
+ ]
12
+ }
@@ -1,15 +1,15 @@
1
- {
2
- "hooks": {
3
- "Stop": [
4
- {
5
- "hooks": [
6
- {
7
- "type": "command",
8
- "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" codex",
9
- "statusMessage": "memory-eternal: saving session"
10
- }
11
- ]
12
- }
13
- ]
14
- }
15
- }
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" codex",
9
+ "statusMessage": "memory-eternal: saving session"
10
+ }
11
+ ]
12
+ }
13
+ ]
14
+ }
15
+ }
@@ -1,11 +1,11 @@
1
- {
2
- "version": 1,
3
- "hooks": {
4
- "stop": [
5
- { "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" cursor" }
6
- ],
7
- "sessionEnd": [
8
- { "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" cursor" }
9
- ]
10
- }
11
- }
1
+ {
2
+ "version": 1,
3
+ "hooks": {
4
+ "stop": [
5
+ { "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" cursor" }
6
+ ],
7
+ "sessionEnd": [
8
+ { "command": "node \"${CLAUDE_PLUGIN_ROOT}/hooks/capture.mjs\" cursor" }
9
+ ]
10
+ }
11
+ }