dsh-memory-engine 0.5.4 → 0.5.5

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 CHANGED
@@ -10,6 +10,8 @@ DSH 插件的本地记忆引擎,带神经网络可视化。它让 agent 保存
10
10
 
11
11
  点击后全屏显示图谱,右上角 **关闭 ×** 或 **Escape** 退出。每次打开都会读取当前记忆库重新生成;大库会先显示“正在生成”。
12
12
 
13
+ **记忆库为空时,这个按钮打开的不是星图,而是一张上手引导页**:说明为什么是空的、给出可一键复制的提示词、列出导入命令。第一次装完点开不会看到一片黑。
14
+
13
15
  拖拽旋转、滚轮缩放、点击神经元看原文。服务默认监听 `127.0.0.1:18886`;端口被占用时记忆工具仍可用,可以运行 `node <插件目录>/scripts/render-map.mjs --open`,或直接打开 `~/.dsh/memory-engine/starmap.html`。修改端口需要同时修改 config.json 的 uiPort 和 client.js 的 VIEW_URL,见后文。
14
16
 
15
17
  也可以让 agent 调用 `memory_map()`,生成可双击、离线打开的单文件 HTML:
package/lib/index.js CHANGED
@@ -26,7 +26,9 @@ export function createTools(defineTool, store = new MemoryStore(), { map = gener
26
26
  return [
27
27
  defineTool({
28
28
  name: 'memory_save',
29
- description: '把值得记住的事保存到跨会话的本地记忆。与 QQ 桥接记忆独立。重复正文自动合并。',
29
+ description: '把值得记住的事保存到跨会话的本地记忆。与 QQ 桥接记忆独立。重复正文自动合并。'
30
+ + '该存:对方的称呼/偏好/忌讳/习惯、答应过的事、情绪特别重的瞬间、新学到的知识、当天小结。'
31
+ + '不该存:寒暄、重复内容、纯事务性对话。一天三五条足够,存前可先用 memory_recall 查重。',
30
32
  parameters: {
31
33
  text: { type: 'string', required: true, description: '记忆正文,最多 2000 字' },
32
34
  kind: { type: 'string', description: '分类,默认对话;可用知识/印象/日记/对话/事件或自定义' },
@@ -135,10 +137,18 @@ export function createTools(defineTool, store = new MemoryStore(), { map = gener
135
137
  async execute() {
136
138
  const stats = store.stats();
137
139
  const kinds = Object.entries(stats.kinds).map(([kind, count]) => `${kind} ${count}`).join(' / ');
138
- return `记忆引擎状态\n- 总条数:${stats.total}${kinds ? `(${kinds})` : ''}\n`
140
+ const body = `记忆引擎状态\n- 总条数:${stats.total}${kinds ? `(${kinds})` : ''}\n`
139
141
  + `- 快忘了的(留存<0.3):${stats.fading} 条\n`
140
142
  + `- 最老:${stats.oldest === null ? '暂无' : `${new Date(stats.oldest).toISOString().slice(0, 10)}(${stats.ageDays} 天前)`}\n`
141
143
  + `- 存储:${stats.file}(${(stats.bytes / 1024).toFixed(1)} KB)`;
144
+ // 库是空的时,替 agent 把「接下来该做什么」说出来(见 README「装完是空库」)。
145
+ if (stats.total === 0) {
146
+ return body + '\n\n记忆库还是空的。插件不会自己读聊天记录,记忆要靠两件事之一进来:\n'
147
+ + '① 在提示词里告诉 agent 该记什么,聊天时就调 memory_save(推荐)\n'
148
+ + '② 用 memory_import 把旧记忆导进来(支持 json / markdown / text)\n'
149
+ + '建议主动把这两条简短告诉主人,并提醒他点开图谱可以看到上手引导页。';
150
+ }
151
+ return body;
142
152
  },
143
153
  }),
144
154
  ];
package/lib/serve.js CHANGED
@@ -8,6 +8,86 @@ export const DEFAULT_UI_PORT = 18886;
8
8
  const pending = '<!doctype html><html lang="zh-CN"><meta charset="utf-8"><meta http-equiv="refresh" content="1"><title>记忆图谱生成中</title><body style="background:#03060c;color:#d8e9f8;font:16px system-ui;padding:40px">正在生成记忆图谱,请稍候……</body></html>';
9
9
  const escapeBridge = '<script>window.addEventListener("keydown",function(e){if(e.key==="Escape")parent.postMessage({type:"dsh-memory-engine-close"},"*")});</script>';
10
10
 
11
+ // 空库时的上手引导页。
12
+ // 为什么要有这一页:新装用户点开图谱看到的是一片黑,只会以为插件坏了。
13
+ // 这个页面在他最想用的那一刻告诉他「为什么是空的、两件事怎么做」。
14
+ const ONBOARD_PROMPT = [
15
+ '聊天时遇到这些,用 memory_save 记下来:',
16
+ ' · 对方的称呼、偏好、忌讳、习惯(kind="印象",who 填对方名字)',
17
+ ' · 答应过的事、约定、承诺(kind="对话")',
18
+ ' · 情绪特别重的瞬间(kind="事件",heat=1)',
19
+ ' · 新学到的知识、名词、梗的解释(kind="知识")',
20
+ ' · 每天结束时,用一两句写当天小结(kind="日记")',
21
+ '',
22
+ '不用每条都记。一天三五条就够,宁可少存也别灌水。',
23
+ '存之前先 memory_recall 查一下,避免重复。',
24
+ ].join('\n');
25
+
26
+ const onboard = `<!doctype html><html lang="zh-CN"><head><meta charset="utf-8">
27
+ <title>还没有记忆</title>
28
+ <style>
29
+ :root{color-scheme:dark}
30
+ *{box-sizing:border-box}
31
+ body{margin:0;min-height:100vh;background:radial-gradient(ellipse at 50% 0%,#0b1a2e 0%,#03060c 60%);
32
+ color:#dbeaf8;font:15px/1.75 "Microsoft YaHei",system-ui,sans-serif;display:flex;align-items:center;justify-content:center;padding:32px}
33
+ .card{width:min(760px,100%)}
34
+ h1{font-size:26px;margin:0 0 6px;font-weight:600;letter-spacing:.5px}
35
+ .sub{color:#7f9dbb;margin:0 0 26px;font-size:14px}
36
+ .step{border-left:3px solid #1f4d73;padding:2px 0 2px 16px;margin:0 0 24px}
37
+ .step h2{font-size:16px;margin:0 0 8px;color:#9fd0f5;font-weight:600}
38
+ .step p{margin:0 0 10px;color:#b6cde2;font-size:14px}
39
+ pre{background:#07121f;border:1px solid #17324b;border-radius:8px;padding:14px;overflow:auto;
40
+ font:13px/1.7 ui-monospace,Consolas,monospace;color:#b9d8f2;margin:0 0 10px;white-space:pre-wrap}
41
+ button{background:#123a5c;color:#cfe9ff;border:1px solid #2a6698;border-radius:7px;
42
+ padding:8px 16px;font:14px "Microsoft YaHei",system-ui;cursor:pointer}
43
+ button:hover{background:#17496f}
44
+ button:active{transform:translateY(1px)}
45
+ code{background:#0d1e30;border-radius:4px;padding:1px 5px;color:#8fd0ff;font-size:13px}
46
+ .foot{color:#5f7a93;font-size:13px;margin-top:8px}
47
+ </style></head><body><div class="card">
48
+ <h1>记忆库还是空的</h1>
49
+ <p class="sub">插件不会自己读你的聊天记录。记忆要靠下面两件事之一进来。</p>
50
+
51
+ <div class="step">
52
+ <h2>① 让它边聊边记(推荐)</h2>
53
+ <p>把这段加进 agent 的提示词或角色卡里:</p>
54
+ <pre id="prompt">${ONBOARD_PROMPT.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')}</pre>
55
+ <button id="copy">复制这段提示词</button>
56
+ </div>
57
+
58
+ <div class="step">
59
+ <h2>② 或者把旧记忆导进来</h2>
60
+ <p>让 agent 调用 <code>memory_import</code>,支持三种格式:</p>
61
+ <pre>memory_import(source="json", path="D:\\\\bot\\\\memories.json")
62
+ memory_import(source="markdown", path="D:\\\\bot\\\\notes")
63
+ memory_import(source="text", path="D:\\\\bot\\\\memories.txt")</pre>
64
+ <p class="foot">会先预览「能新增多少条、多少条重复被跳过」,确认后再写。</p>
65
+ </div>
66
+
67
+ <div class="step">
68
+ <h2>③ 然后就有图了</h2>
69
+ <p>有了几条记忆之后,回到这个按钮,就能看到神经网络。</p>
70
+ <p class="foot">记忆只存在本机 <code>~/.dsh/memory-engine/</code>,不上传、不联网。</p>
71
+ </div>
72
+ </div>
73
+ <script>
74
+ var btn = document.getElementById('copy');
75
+ btn.addEventListener('click', function () {
76
+ var text = document.getElementById('prompt').textContent;
77
+ var done = function () { btn.textContent = '已复制 ✓'; setTimeout(function () { btn.textContent = '复制这段提示词'; }, 1800); };
78
+ if (navigator.clipboard && navigator.clipboard.writeText) {
79
+ navigator.clipboard.writeText(text).then(done, fallback);
80
+ } else { fallback(); }
81
+ function fallback() {
82
+ var area = document.createElement('textarea');
83
+ area.value = text; document.body.appendChild(area); area.select();
84
+ try { document.execCommand('copy'); done(); } catch (e) { btn.textContent = '请手动选中复制'; }
85
+ document.body.removeChild(area);
86
+ }
87
+ });
88
+ </script>
89
+ </body></html>`;
90
+
11
91
  export async function startMapServer({ store, port, warn = () => {} }) {
12
92
  const directory = path.dirname(store.file);
13
93
  const config = readConfig(path.dirname(directory));
@@ -27,6 +107,10 @@ export async function startMapServer({ store, port, warn = () => {} }) {
27
107
  const rootPage = ['/', '/index.html', '/starmap.html'].includes(url.pathname);
28
108
  if (!rootPage && !/^\/pages\/[1-9]\d*\.html$/u.test(url.pathname)) return reply(404);
29
109
  if (rootPage) {
110
+ // 空库直接返回上手引导页:不要让人对着黑屏猜「是不是坏了」
111
+ let rows = [];
112
+ try { rows = store.read(); } catch (error) { warn(`记忆库读取失败:${error.message}`); }
113
+ if (!Array.isArray(rows) || rows.length === 0) return reply(200, onboard + escapeBridge);
30
114
  let signature = 'empty';
31
115
  try { const info = fs.statSync(store.file); signature = `${info.mtimeMs}:${info.size}`; }
32
116
  catch (error) { if (error.code !== 'ENOENT') throw error; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-memory-engine",
3
- "version": "0.5.4",
3
+ "version": "0.5.5",
4
4
  "description": "Local DSH memory engine with neuron network visualization and private file import",
5
5
  "keywords": [
6
6
  "dsh",