memory-eternal 0.9.11 → 0.9.13
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/.claude-plugin/plugin.json +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/.cursor-plugin/plugin.json +1 -1
- package/README.en.md +37 -0
- package/README.md +48 -0
- package/lib/api.js +76 -0
- package/lib/client.js +17 -11
- package/lib/feedback.js +205 -0
- package/package.json +2 -2
- package/web/app.js +20 -14
package/lib/feedback.js
ADDED
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
// 记忆核心 · 「反馈异常」支撑层(纯函数,便于单测)
|
|
2
|
+
//
|
|
3
|
+
// 为什么不做「点一下直接提交」:
|
|
4
|
+
// 1. GitHub 不允许匿名创建 issue(REST 必须带认证);
|
|
5
|
+
// 2. 插件跑在用户机器上,内置 token 等于把 token 公开(asar/JS 反编译即可提取);
|
|
6
|
+
// 3. 走 OAuth 或自建中转需要 client secret / 服务器,成本与风险都不合算。
|
|
7
|
+
// 因此提供两条可用路径:
|
|
8
|
+
// A. 预填 issue 链接:标题/正文/标签/诊断都填好,用户点一次 Submit(等价于一点提交);
|
|
9
|
+
// B. 给 AI 的提示词:用户贴进对话框,让 AI 用 gh 建 issue 并回报链接。
|
|
10
|
+
//
|
|
11
|
+
// 另一件必须做的事:脱敏。issue 是公开的,绝不能把 home 目录、API key、token 带出去。
|
|
12
|
+
|
|
13
|
+
const SECRET_PATTERNS = [
|
|
14
|
+
/sk-[A-Za-z0-9_-]{12,}/g,
|
|
15
|
+
/ghp_[A-Za-z0-9]{20,}/g,
|
|
16
|
+
/github_pat_[A-Za-z0-9_]{20,}/g,
|
|
17
|
+
/(api[_-]?key|token|password|secret|authorization)\s*[:=]\s*["']?[^\s"',}]{8,}/gi,
|
|
18
|
+
/\b[A-Za-z0-9+/]{40,}={0,2}\b/g,
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 脱敏:home 目录 → ~,各类 key/token → ***。
|
|
23
|
+
* @param {string} input
|
|
24
|
+
* @param {string} [home]
|
|
25
|
+
* @returns {string}
|
|
26
|
+
*/
|
|
27
|
+
export function redactText(input, home = '') {
|
|
28
|
+
let s = String(input ?? '')
|
|
29
|
+
if (home && home.length > 3) {
|
|
30
|
+
const variants = [home, home.split('\\').join('/'), home.split('/').join('\\')]
|
|
31
|
+
for (const v of variants) if (v && v.length > 3) s = s.split(v).join('~')
|
|
32
|
+
}
|
|
33
|
+
s = s.replace(/[A-Za-z]:\\Users\\[^\\\s"']+/g, '~')
|
|
34
|
+
s = s.replace(/(\/home\/|\/Users\/)[^/\s"']+/g, '~')
|
|
35
|
+
for (const re of SECRET_PATTERNS) s = s.replace(re, '***')
|
|
36
|
+
return s
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 组装诊断信息(markdown,已脱敏、已限长)。
|
|
41
|
+
* @param {object} input
|
|
42
|
+
* @param {string} [home]
|
|
43
|
+
* @returns {string}
|
|
44
|
+
*/
|
|
45
|
+
export function buildDiagnostics(input = {}, home = '') {
|
|
46
|
+
const rows = []
|
|
47
|
+
const add = (k, v) => {
|
|
48
|
+
if (v === undefined || v === null) return
|
|
49
|
+
const s = String(v).trim()
|
|
50
|
+
if (!s) return
|
|
51
|
+
rows.push('- ' + k + ': ' + redactText(s, home))
|
|
52
|
+
}
|
|
53
|
+
add('memory-eternal', input.version)
|
|
54
|
+
add('DSH', input.dshVersion)
|
|
55
|
+
add('Host', input.host)
|
|
56
|
+
add('OS', input.os)
|
|
57
|
+
add('Node', input.node)
|
|
58
|
+
add('Vault', input.vaultDir)
|
|
59
|
+
add('Vault source', input.vaultSource)
|
|
60
|
+
add('Workspace', input.workspace)
|
|
61
|
+
add('Cards', input.cards)
|
|
62
|
+
add('Graph', input.graph)
|
|
63
|
+
if (input.health && input.health.ok === false) add('Health', 'FAIL: ' + (input.health.reason || ''))
|
|
64
|
+
const log = Array.isArray(input.captureLog) ? input.captureLog.slice(-10) : []
|
|
65
|
+
const out = ['### 诊断信息', '', ...rows]
|
|
66
|
+
if (log.length) {
|
|
67
|
+
out.push('', '最近沉淀日志(最多 10 条):', '`')
|
|
68
|
+
for (const e of log) out.push(redactText(typeof e === 'string' ? e : JSON.stringify(e), home))
|
|
69
|
+
out.push('`')
|
|
70
|
+
}
|
|
71
|
+
let text = out.join('\n')
|
|
72
|
+
if (text.length > 2400) text = text.slice(0, 2400) + '\n…(诊断信息过长已截断)'
|
|
73
|
+
return text
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* 生成 GitHub 预填 issue 链接(带长度保护:URL 过长会被 GitHub/浏览器直接拒绝)。
|
|
78
|
+
* @param {{repo?:string, title?:string, body?:string, labels?:string[], maxLength?:number}} input
|
|
79
|
+
* @returns {string}
|
|
80
|
+
*/
|
|
81
|
+
export function buildIssueUrl(input = {}) {
|
|
82
|
+
const repo = input.repo || 'EternalNight996/memory-eternal'
|
|
83
|
+
const title = String(input.title || '[Bug] ').slice(0, 120)
|
|
84
|
+
const labels = Array.isArray(input.labels) ? input.labels : ['bug']
|
|
85
|
+
const maxLength = Number(input.maxLength) > 500 ? Number(input.maxLength) : 7000
|
|
86
|
+
const build = (body) => {
|
|
87
|
+
const q = new URLSearchParams()
|
|
88
|
+
q.set('title', title)
|
|
89
|
+
q.set('body', body)
|
|
90
|
+
if (labels.length) q.set('labels', labels.join(','))
|
|
91
|
+
return 'https://github.com/' + repo + '/issues/new?' + q.toString()
|
|
92
|
+
}
|
|
93
|
+
let body = String(input.body || '')
|
|
94
|
+
let url = build(body)
|
|
95
|
+
if (url.length > maxLength) {
|
|
96
|
+
// URLSearchParams 会把正文按 percent-encoding 膨胀(中文约 9 倍),用「长度估算」截断会不准。
|
|
97
|
+
// 直接二分:找最大的 k 使 build(body.slice(0,k) + note).length <= maxLength —— 结果可证明在阈值内。
|
|
98
|
+
const note = '\n\n> 内容过长已截断:完整诊断请在插件里点「复制诊断信息」,再补到 issue 评论里。'
|
|
99
|
+
const withNote = (k) => build(body.slice(0, k) + note)
|
|
100
|
+
if (withNote(0).length > maxLength) {
|
|
101
|
+
body = note
|
|
102
|
+
} else {
|
|
103
|
+
let lo = 0
|
|
104
|
+
let hi = body.length
|
|
105
|
+
while (lo < hi) {
|
|
106
|
+
const mid = Math.ceil((lo + hi) / 2)
|
|
107
|
+
if (withNote(mid).length <= maxLength) lo = mid
|
|
108
|
+
else hi = mid - 1
|
|
109
|
+
}
|
|
110
|
+
body = body.slice(0, lo) + note
|
|
111
|
+
}
|
|
112
|
+
url = build(body)
|
|
113
|
+
}
|
|
114
|
+
return url
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 反馈正文模板(预填 issue 与提示词共用)。
|
|
119
|
+
* @param {{description?:string, diagnostics?:string, version?:string}} [input]
|
|
120
|
+
* @returns {string}
|
|
121
|
+
*/
|
|
122
|
+
export function buildIssueBody(input = {}) {
|
|
123
|
+
return [
|
|
124
|
+
'## 现象',
|
|
125
|
+
String(input.description || '').trim() || '(请描述你遇到的问题)',
|
|
126
|
+
'',
|
|
127
|
+
'## 复现步骤',
|
|
128
|
+
'1. ',
|
|
129
|
+
'2. ',
|
|
130
|
+
'',
|
|
131
|
+
'## 期望行为',
|
|
132
|
+
'',
|
|
133
|
+
'## 实际行为',
|
|
134
|
+
'',
|
|
135
|
+
'## 环境',
|
|
136
|
+
'- 插件版本: ' + (input.version || '(见诊断信息)'),
|
|
137
|
+
'',
|
|
138
|
+
input.diagnostics || '',
|
|
139
|
+
'',
|
|
140
|
+
'---',
|
|
141
|
+
'> 由 memory-eternal 插件内「反馈异常」生成(诊断信息已自动脱敏:home 目录与各类 key/token 已替换)。',
|
|
142
|
+
].join('\n')
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const REPO = 'https://github.com/EternalNight996/memory-eternal'
|
|
146
|
+
|
|
147
|
+
const PROMPT_ZH = [
|
|
148
|
+
'我在使用 memory-eternal(DeepSeek Harness 的记忆插件)时遇到了问题,请你帮我把这个问题提交到 GitHub。',
|
|
149
|
+
'',
|
|
150
|
+
'仓库:' + REPO,
|
|
151
|
+
'',
|
|
152
|
+
'请按这个流程做:',
|
|
153
|
+
'',
|
|
154
|
+
'1. 先跑 `gh issue list --repo EternalNight996/memory-eternal --state open --limit 50`,看是否已有相同问题:',
|
|
155
|
+
' - 已有 → 用 `gh issue comment <编号> --body-file <文件>` 补充现象,不要重复开新 issue;',
|
|
156
|
+
' - 没有 → 继续第 2 步。',
|
|
157
|
+
'2. 把正文写成文件,然后 `gh issue create --repo EternalNight996/memory-eternal --title "[Bug] <一句话概括>" --body-file <文件>`;正文用这个结构:',
|
|
158
|
+
' 现象 / 复现步骤 / 期望行为 / 实际行为 / 环境(插件版本 / DSH 版本 / 操作系统 / Node)/ 诊断信息。',
|
|
159
|
+
'3. 如果 `gh` 没登录或不可用,不要尝试提交,改为输出一条「标题与正文都已预填」的 GitHub 新建 issue 链接给我,我自己点提交。',
|
|
160
|
+
'4. 提交成功后,把 issue 链接发给我。',
|
|
161
|
+
'',
|
|
162
|
+
'注意:我的问题描述请原样照录,不要替我美化或省略细节;诊断信息原样放进「诊断信息」一节。',
|
|
163
|
+
'',
|
|
164
|
+
'我的问题描述:',
|
|
165
|
+
'<在这里写你的问题>',
|
|
166
|
+
'',
|
|
167
|
+
'诊断信息:',
|
|
168
|
+
'<把「反馈异常」弹窗里复制的诊断信息粘到这里>',
|
|
169
|
+
].join('\n')
|
|
170
|
+
|
|
171
|
+
const PROMPT_EN = [
|
|
172
|
+
'I hit a problem while using memory-eternal (a DeepSeek Harness memory plugin). Please file it on GitHub for me.',
|
|
173
|
+
'',
|
|
174
|
+
'Repo: ' + REPO,
|
|
175
|
+
'',
|
|
176
|
+
'Workflow:',
|
|
177
|
+
'',
|
|
178
|
+
'1. Run `gh issue list --repo EternalNight996/memory-eternal --state open --limit 50` first: if a matching issue exists, add a comment with `gh issue comment <n> --body-file <file>` instead of opening a duplicate.',
|
|
179
|
+
'2. Otherwise create it with `gh issue create --repo EternalNight996/memory-eternal --title "[Bug] <one-line summary>" --body-file <file>`, using sections: Symptoms / Steps to reproduce / Expected / Actual / Environment (plugin, DSH, OS, Node) / Diagnostics.',
|
|
180
|
+
'3. If `gh` is not authenticated or unavailable, do NOT try to submit: output a GitHub new-issue link with the title and body pre-filled instead, and I will click submit myself.',
|
|
181
|
+
'4. Report the resulting issue URL back to me.',
|
|
182
|
+
'',
|
|
183
|
+
'Keep my description verbatim - do not rewrite or summarise it. Put the diagnostics under the Diagnostics section as-is.',
|
|
184
|
+
'',
|
|
185
|
+
'My description:',
|
|
186
|
+
'<write your problem here>',
|
|
187
|
+
'',
|
|
188
|
+
'Diagnostics:',
|
|
189
|
+
'<paste the diagnostics copied from the in-app Report-a-bug dialog>',
|
|
190
|
+
].join('\n')
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* 给 AI 的提示词(用户贴进对话框)。
|
|
194
|
+
* @param {{description?:string, diagnostics?:string, repo?:string, lang?:'zh'|'en'}} [input]
|
|
195
|
+
* @returns {string}
|
|
196
|
+
*/
|
|
197
|
+
export function buildAgentPrompt(input = {}) {
|
|
198
|
+
const lang = input.lang === 'en' ? 'en' : 'zh'
|
|
199
|
+
let text = lang === 'en' ? PROMPT_EN : PROMPT_ZH
|
|
200
|
+
const repo = input.repo || 'EternalNight996/memory-eternal'
|
|
201
|
+
text = text.split(REPO).join('https://github.com/' + repo)
|
|
202
|
+
if (input.description) text = text.replace(lang === 'en' ? '<write your problem here>' : '<在这里写你的问题>', String(input.description).trim())
|
|
203
|
+
if (input.diagnostics) text = text.replace(lang === 'en' ? '<paste the diagnostics copied from the in-app Report-a-bug dialog>' : '<把「反馈异常」弹窗里复制的诊断信息粘到这里>', String(input.diagnostics).trim())
|
|
204
|
+
return text
|
|
205
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "memory-eternal",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.13",
|
|
4
4
|
"description": "记忆核心(Memory Eternal):自研的 DeepSeek Harness 记忆插件,不移植任何既有记忆框架——对话结束后自动沉淀知识卡到本地 Markdown Vault(自研去重、自研 CJK 检索、可 git 管理),设置页提供图形化知识库(统计 / 搜索 / 知识图谱 + 侧边栏一键弹窗),Agent 通过 memory_recall 工具按需召回历史上下文。零人工干预。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"build": "node build.mjs",
|
|
61
61
|
"build:client": "node build.mjs",
|
|
62
62
|
"prepublishOnly": "node build.mjs",
|
|
63
|
-
"test": "node build.mjs && node tests/vault.test.mjs && node tests/capture.test.mjs && node tests/capture-route.test.mjs && node tests/stall.test.mjs && node tests/vault-path.test.mjs && node tests/vault-resolve.test.mjs && node tests/api.test.mjs && node tests/graph-perf.test.mjs && node tests/settings-compat.test.mjs && node tests/node-bin.test.mjs && node tests/watchdog-lock.test.mjs && node tests/markdown.test.mjs && node tests/ssr.test.mjs"
|
|
63
|
+
"test": "node build.mjs && node tests/vault.test.mjs && node tests/capture.test.mjs && node tests/capture-route.test.mjs && node tests/stall.test.mjs && node tests/vault-path.test.mjs && node tests/vault-resolve.test.mjs && node tests/api.test.mjs && node tests/graph-perf.test.mjs && node tests/settings-compat.test.mjs && node tests/node-bin.test.mjs && node tests/watchdog-lock.test.mjs && node tests/markdown.test.mjs && node tests/feedback.test.mjs && node tests/graph-lod.test.mjs && node tests/ssr.test.mjs"
|
|
64
64
|
},
|
|
65
65
|
"dependencies": {
|
|
66
66
|
"@deepseek-ai/schemastery": "^3.18.1"
|