@furongjun1999/dsh-memory 0.2.8 → 0.3.0

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.
@@ -0,0 +1,213 @@
1
+ <!DOCTYPE html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>记忆系统使用性评分 · 五维标尺</title>
7
+ <style>
8
+ :root {
9
+ --gold: linear-gradient(135deg, #f7d774, #e8a33d);
10
+ --silver: linear-gradient(135deg, #e8e8e8, #b8b8b8);
11
+ --bronze: linear-gradient(135deg, #e0a878, #c07840);
12
+ --lingshu: linear-gradient(135deg, #6a5cff, #3d8bfd);
13
+ --danger: #e74c3c; --warn: #e67e22; --good: #27ae60; --excel: #2ecc71; --perf: #9b59b6;
14
+ }
15
+ * { margin:0; padding:0; box-sizing:border-box; }
16
+ body {
17
+ font-family: "Microsoft YaHei", "PingFang SC", sans-serif;
18
+ background: linear-gradient(160deg, #0f1023 0%, #1a1c3a 50%, #0f1023 100%);
19
+ color: #e8eaf6; padding: 40px 24px; min-height: 100vh;
20
+ }
21
+ .container { max-width: 1080px; margin: 0 auto; }
22
+ /* 标题区 */
23
+ .hero {
24
+ text-align:center; margin-bottom: 36px;
25
+ background: rgba(255,255,255,0.04); border: 1px solid rgba(255,255,255,0.1);
26
+ border-radius: 20px; padding: 32px 24px;
27
+ box-shadow: 0 20px 60px rgba(0,0,0,0.4);
28
+ }
29
+ .hero h1 {
30
+ font-size: 34px; font-weight: 800; letter-spacing: 2px;
31
+ background: linear-gradient(135deg, #ffd700, #ff8c00, #ff5e62);
32
+ -webkit-background-clip: text; -webkit-text-fill-color: transparent;
33
+ margin-bottom: 10px;
34
+ }
35
+ .hero .sub { color: #aab; font-size: 15px; line-height: 1.8; }
36
+ .hero .badges { margin-top: 14px; }
37
+ .badge {
38
+ display:inline-block; padding: 5px 14px; border-radius: 20px;
39
+ font-size: 13px; margin: 3px 5px; font-weight: 600;
40
+ background: rgba(255,255,255,0.08); border: 1px solid rgba(255,255,255,0.15);
41
+ }
42
+ .badge.hl { background: rgba(61,139,253,0.2); border-color: #3d8bfd; color: #7fb0ff; }
43
+ /* 五维标尺 */
44
+ .section { margin-bottom: 34px; }
45
+ .section h2 {
46
+ font-size: 20px; margin-bottom: 14px; padding-left: 12px;
47
+ border-left: 4px solid #3d8bfd; color: #dfe3ff;
48
+ }
49
+ .scale-table { width: 100%; border-collapse: separate; border-spacing: 3px; }
50
+ .scale-table th, .scale-table td {
51
+ padding: 10px 8px; text-align: center; font-size: 13px; border-radius: 8px;
52
+ background: rgba(255,255,255,0.04);
53
+ }
54
+ .scale-table th { background: rgba(61,139,253,0.15); color: #9fc0ff; font-weight: 700; }
55
+ .scale-table td.dim { font-weight: 700; color: #ffd700; background: rgba(255,215,0,0.08); white-space: nowrap; }
56
+ .scale-table td.lv0 { background: rgba(231,76,60,0.18); color:#ff8a80; }
57
+ .scale-table td.lv1 { background: rgba(231,76,60,0.12); color:#ffab91; }
58
+ .scale-table td.lv2 { background: rgba(230,126,34,0.16); color:#ffcc80; }
59
+ .scale-table td.lv3 { background: rgba(39,174,96,0.16); color:#a5d6a7; }
60
+ .scale-table td.lv4 { background: rgba(155,89,182,0.18); color:#ce93d8; }
61
+ .formula {
62
+ margin-top: 12px; text-align:center; font-size: 15px; color: #cfd8ff;
63
+ background: rgba(61,139,253,0.08); border:1px dashed #3d8bfd;
64
+ padding: 10px; border-radius: 12px;
65
+ }
66
+ .formula b { color: #ffd700; }
67
+ /* 天梯榜 */
68
+ .rank-list { display: flex; flex-direction: column; gap: 8px; }
69
+ .rank-item {
70
+ display: grid; grid-template-columns: 56px 1.1fr 90px 90px 90px 90px 90px 70px 2.2fr;
71
+ align-items: center; gap: 6px;
72
+ background: rgba(255,255,255,0.045); border: 1px solid rgba(255,255,255,0.08);
73
+ border-radius: 12px; padding: 10px 12px; font-size: 13px;
74
+ transition: transform .15s;
75
+ }
76
+ .rank-item:hover { transform: translateX(4px); }
77
+ .rank-item.top1 { background: linear-gradient(90deg, rgba(255,215,0,0.16), rgba(255,255,255,0.04)); border-color: #ffd700; box-shadow: 0 0 24px rgba(255,215,0,0.15); }
78
+ .rank-item.top2 { background: linear-gradient(90deg, rgba(200,200,200,0.14), rgba(255,255,255,0.04)); border-color: #b8b8b8; }
79
+ .rank-item.top3 { background: linear-gradient(90deg, rgba(224,168,120,0.14), rgba(255,255,255,0.04)); border-color: #c07840; }
80
+ .rank-medal { font-size: 22px; text-align:center; }
81
+ .rank-name { font-weight: 700; color: #fff; }
82
+ .rank-item.top1 .rank-name { color: #ffd700; }
83
+ .cell { text-align:center; font-weight: 600; border-radius: 6px; padding: 4px 0; }
84
+ .cell.high { color: #ffd700; }
85
+ .cell.mid { color: #a5d6a7; }
86
+ .cell.low { color: #ff8a80; }
87
+ .cell.total { font-size: 15px; color: #fff; background: rgba(61,139,253,0.2); border-radius: 8px; }
88
+ .rank-item.top1 .cell.total { background: rgba(255,215,0,0.25); color: #ffd700; font-weight: 800; }
89
+ .rank-comment { font-size: 12px; color: #b0b7d0; line-height: 1.5; }
90
+ /* 灵枢依据 */
91
+ .lingshu-card {
92
+ background: linear-gradient(135deg, rgba(106,92,255,0.12), rgba(61,139,253,0.08));
93
+ border: 1px solid rgba(106,92,255,0.4); border-radius: 16px; padding: 18px 20px;
94
+ margin-top: 14px; box-shadow: 0 10px 40px rgba(61,139,253,0.15);
95
+ }
96
+ .lingshu-card h3 { color: #8fb8ff; font-size: 16px; margin-bottom: 10px; }
97
+ .lingshu-card .dim-row { display: grid; grid-template-columns: 90px 40px 1fr; gap: 8px; margin-bottom: 6px; font-size: 13px; }
98
+ .lingshu-card .dim-row b { color: #ffd700; }
99
+ .lingshu-card .dim-row .score { text-align:center; color: #fff; font-weight: 700; }
100
+ .lingshu-card .dim-row .why { color: #c5cbe8; }
101
+ .disclaimer { margin-top: 20px; font-size: 12px; color: #8a90b0; text-align: center; line-height: 1.7; }
102
+ .footer { text-align:center; margin-top: 30px; color: #6a6f8a; font-size: 12px; }
103
+ </style>
104
+ </head>
105
+ <body>
106
+ <div class="container">
107
+
108
+ <div class="hero">
109
+ <h1>📊 记忆系统使用性评分 · 五维标尺</h1>
110
+ <div class="sub">
111
+ 视角:<b style="color:#ffd700">使用性</b>(普通用户 / 开发者体感)——「存、找、想、准、安」五维<br>
112
+ 评估基准:公开能力 + 设计者校准(2026-08-17)· 灵枢分数经设计者核对(<b style="color:#7fb0ff">不虚高</b>)
113
+ </div>
114
+ <div class="badges">
115
+ <span class="badge hl">🥇 灵枢 AEIS · 7.7 断层第一</span>
116
+ <span class="badge">使用性视角</span>
117
+ <span class="badge">短板效应显著</span>
118
+ <span class="badge">安全性是底线</span>
119
+ </div>
120
+ </div>
121
+
122
+ <div class="section">
123
+ <h2>📐 五维标尺定义</h2>
124
+ <table class="scale-table">
125
+ <tr><th>维度</th><th class="lv0">0-2 危险</th><th class="lv1">3-4 基础</th><th class="lv2">5-6 良好</th><th class="lv3">7-8 优秀</th><th class="lv4">9-10 完美</th></tr>
126
+ <tr><td class="dim">S 存储结构</td><td class="lv0">扁平 KV</td><td class="lv1">简单分层</td><td class="lv2">时序/向量</td><td class="lv3">图结构+衰减</td><td class="lv4">多模态+自动迁移</td></tr>
127
+ <tr><td class="dim">R 检索机制</td><td class="lv0">全量遍历</td><td class="lv1">关键词</td><td class="lv2">向量语义</td><td class="lv3">图遍历+混合</td><td class="lv4">因果推理+意图理解</td></tr>
128
+ <tr><td class="dim">J 判断引擎</td><td class="lv0">只记不忘</td><td class="lv1">规则过滤</td><td class="lv2">LLM 自评估</td><td class="lv3">验证单元+递归反思</td><td class="lv4">独立元认知+主动遗忘</td></tr>
129
+ <tr><td class="dim">C 上下文信噪比</td><td class="lv0">噪声爆炸</td><td class="lv1">Top-K 有噪</td><td class="lv2">时序过滤</td><td class="lv3">重要性+精准注入</td><td class="lv4">零污染</td></tr>
130
+ <tr><td class="dim">Safety</td><td class="lv0">易泄露</td><td class="lv1">基础脱敏</td><td class="lv2">分级权限</td><td class="lv3">隐私树+审计</td><td class="lv4">零信任+端到端加密</td></tr>
131
+ </table>
132
+ <div class="formula">综合分 = <b>min(五维) × 0.4</b> + <b>mean(五维) × 0.6</b> · 安全性是底线,短板效应显著</div>
133
+ </div>
134
+
135
+ <div class="section">
136
+ <h2>🏆 天梯榜</h2>
137
+ <div class="rank-list">
138
+ <div class="rank-item top1">
139
+ <div class="rank-medal">🥇</div><div class="rank-name">灵枢 AEIS</div>
140
+ <div class="cell high">7.5</div><div class="cell high">7.5</div><div class="cell high">8.0</div><div class="cell high">8.0</div><div class="cell high">8.0</div>
141
+ <div class="cell total">7.7</div>
142
+ <div class="rank-comment">五维无短板:图结构 + 三层语义检索 + 验证单元/递归反思 + 重要性评分 + 护栏宪章审计追踪</div>
143
+ </div>
144
+ <div class="rank-item top2">
145
+ <div class="rank-medal">🥈</div><div class="rank-name">Hy-Memory(腾讯)</div>
146
+ <div class="cell mid">7.5</div><div class="cell mid">7.0</div><div class="cell">6.0</div><div class="cell mid">7.0</div><div class="cell high">8.0</div>
147
+ <div class="cell total">6.6</div>
148
+ <div class="rank-comment">企业级安全标杆;缺主动反思,记忆易过时</div>
149
+ </div>
150
+ <div class="rank-item top3">
151
+ <div class="rank-medal">🥉</div><div class="rank-name">Zep / 腾讯云</div>
152
+ <div class="cell mid">7.5</div><div class="cell mid">7.0</div><div class="cell">5.5</div><div class="cell mid">6.5</div><div class="cell mid">7.5</div>
153
+ <div class="cell total">6.3</div>
154
+ <div class="rank-comment">时序图谱+权限控制;不够「聪明」</div>
155
+ </div>
156
+ <div class="rank-item">
157
+ <div class="rank-medal">4</div><div class="rank-name">Letta / MemGPT</div>
158
+ <div class="cell mid">7.5</div><div class="cell high">8.0</div><div class="cell">5.0</div><div class="cell high">8.0</div><div class="cell low">4.0</div>
159
+ <div class="cell total">5.7</div>
160
+ <div class="rank-comment">架构强但易记忆越权——权限模型复杂,风险高</div>
161
+ </div>
162
+ <div class="rank-item">
163
+ <div class="rank-medal">5</div><div class="rank-name">TiMem</div>
164
+ <div class="cell mid">7.0</div><div class="cell mid">6.5</div><div class="cell">5.0</div><div class="cell">6.0</div><div class="cell">5.0</div>
165
+ <div class="cell total">5.4</div>
166
+ <div class="rank-comment">学术派安全,缺工程级隐私治理</div>
167
+ </div>
168
+ <div class="rank-item">
169
+ <div class="rank-medal">6</div><div class="rank-name">Mem0</div>
170
+ <div class="cell">6.0</div><div class="cell">6.0</div><div class="cell">4.0</div><div class="cell">5.0</div><div class="cell low">4.0</div>
171
+ <div class="cell total">4.4</div>
172
+ <div class="rank-comment">记忆碎片化,细粒度权限缺失</div>
173
+ </div>
174
+ <div class="rank-item">
175
+ <div class="rank-medal">7</div><div class="rank-name">LangMem</div>
176
+ <div class="cell">6.0</div><div class="cell">6.0</div><div class="cell">4.0</div><div class="cell">5.0</div><div class="cell low">4.0</div>
177
+ <div class="cell total">4.4</div>
178
+ <div class="rank-comment">生态绑定 LangChain,缺独立安全治理</div>
179
+ </div>
180
+ <div class="rank-item">
181
+ <div class="rank-medal">8</div><div class="rank-name">dsh-memory-evolve</div>
182
+ <div class="cell low">3.0</div><div class="cell low">4.0</div><div class="cell low">2.0</div><div class="cell low">3.0</div><div class="cell low">2.0</div>
183
+ <div class="cell total">2.6</div>
184
+ <div class="rank-comment">无隐私/权限/删除,「只记不忘」= 隐私炸弹</div>
185
+ </div>
186
+ </div>
187
+ </div>
188
+
189
+ <div class="section">
190
+ <h2>🔍 灵枢各维度依据(设计者校准 · 不虚高)</h2>
191
+ <div class="lingshu-card">
192
+ <h3>🧠 灵枢 AEIS · 5.71MB 结构智能</h3>
193
+ <div class="dim-row"><b>S 存储</b><span class="score">7.5</span><span class="why">语义时空图(五层记忆:锚点/结构/知识/情境/自我)+ 条件空间 + 教育层级。无多模态原生(9-10 档)</span></div>
194
+ <div class="dim-row"><b>R 检索</b><span class="score">7.5</span><span class="why">三层语义检索(二元组 + 语义坐标 + bge 神经)+ 图谱遍历 + 翻译体系。无因果推理检索</span></div>
195
+ <div class="dim-row"><b>J 判断</b><span class="score">8.0</span><span class="why">验证单元(P37 三闸门)+ 递归反思(协议 3.12)+ 白箱校验 + 结构排斥。<b style="color:#7fb0ff">无主动遗忘 → 8.0 非 9.0</b></span></div>
196
+ <div class="dim-row"><b>C 信噪比</b><span class="score">8.0</span><span class="why">重要性评分 + 信息分层注入(图谱自处理→LLM 兜底→白箱校验)+ 诚实边界(不瞎编)</span></div>
197
+ <div class="dim-row"><b>Safety</b><span class="score">8.0</span><span class="why">护栏宪章(接入即接受约束)+ 审计追踪(观测层全留痕)+ 物理基底校准。<b style="color:#7fb0ff">无端到端加密 → 8.0 非 8.5</b></span></div>
198
+ </div>
199
+ <p style="text-align:center; margin-top:14px; color:#aab; font-size:14px;">
200
+ 为什么断层领先:<b style="color:#ffd700">五维无短板(min=7.5)</b>——J(验证+反思)与 Safety(宪章+审计)是护城河。<br>
201
+ 使用性视角:重要性评分 → 用户看不到废话;护栏宪章 → 用户敢把私密信息交给它。
202
+ </p>
203
+ </div>
204
+
205
+ <div class="disclaimer">
206
+ 免责声明:他系统评分基于公开能力评估(2026-08-17),非官方基准;灵枢评分经设计者校准(不虚高)。<br>
207
+ 综合分 = min × 0.4 + mean × 0.6 —— 安全性是底线,短板效应显著。
208
+ </div>
209
+ <div class="footer">灵枢 AEIS · 共同信任协议(Common Trust Protocol)· 智能论 v3.2 工程实现</div>
210
+
211
+ </div>
212
+ </body>
213
+ </html>
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
Binary file
package/lib/bridge.d.ts CHANGED
@@ -57,11 +57,13 @@ export declare class LingshuBridge {
57
57
  /** 拉取灵枢的全部工具清单(每次实时请求,不缓存)。 */
58
58
  listTools(): Promise<McpTool[]>;
59
59
  /** 调用灵枢的一个工具,返回标准化 MCP 结果。 */
60
- callTool(name: string, args: Record<string, unknown>): Promise<McpCallResult>;
60
+ callTool(name: string, args: Record<string, unknown>, signal?: AbortSignal): Promise<McpCallResult>;
61
61
  /** 关闭进程并释放资源(写 stdin EOF 优雅退出,超时兜底 kill)。 */
62
62
  dispose(): void;
63
63
  /** 进程是否存活。 */
64
64
  get alive(): boolean;
65
+ /** 桥是否已握手就绪(工具注册用;防止轮询访问 private readyState)。 */
66
+ isReady(): boolean;
65
67
  /** 等待握手完成(用于 apply 阶段同步就绪)。 */
66
68
  waitReady(): Promise<boolean>;
67
69
  private spawnAndHandshake;
package/lib/bridge.js CHANGED
@@ -42,8 +42,8 @@ export class LingshuBridge {
42
42
  return tools.tools ?? [];
43
43
  }
44
44
  /** 调用灵枢的一个工具,返回标准化 MCP 结果。 */
45
- async callTool(name, args) {
46
- const result = await this.request('tools/call', { name, arguments: args });
45
+ async callTool(name, args, signal) {
46
+ const result = await this.request('tools/call', { name, arguments: args }, signal);
47
47
  return result;
48
48
  }
49
49
  /** 关闭进程并释放资源(写 stdin EOF 优雅退出,超时兜底 kill)。 */
@@ -67,6 +67,10 @@ export class LingshuBridge {
67
67
  get alive() {
68
68
  return this.proc !== null && this.proc.exitCode === null;
69
69
  }
70
+ /** 桥是否已握手就绪(工具注册用;防止轮询访问 private readyState)。 */
71
+ isReady() {
72
+ return this.readyState === 'ok';
73
+ }
70
74
  /** 等待握手完成(用于 apply 阶段同步就绪)。 */
71
75
  waitReady() {
72
76
  if (this.readyState === 'ok')
@@ -183,24 +187,39 @@ export class LingshuBridge {
183
187
  }
184
188
  proc.stdin.write(JSON.stringify(msg) + '\n');
185
189
  }
186
- request(method, params) {
190
+ request(method, params, signal) {
187
191
  const id = this.nextId++;
188
192
  const timeout = this.options.timeoutMs;
193
+ // P0 修复(GPT 审查):定时器直接调 settle——settle 内部会 delete + clearTimeout
194
+ // 并 reject。此前先 pending.delete(id) 再 settle(),settle 找不到 entry 直接
195
+ // return,Promise 永不 resolve/reject(超时逻辑完全失效,子进程挂死时插件永久挂住)。
189
196
  const timer = setTimeout(() => {
190
- this.pending.delete(id);
191
197
  this.settle(id, {
192
198
  error: { code: -32000, message: `灵枢调用超时(${timeout}ms):${method}` },
193
199
  });
194
200
  }, timeout);
201
+ if (signal?.aborted) {
202
+ clearTimeout(timer);
203
+ return Promise.reject(new Error(`已取消:${method}`));
204
+ }
205
+ const onAbort = () => {
206
+ this.settle(id, { error: { code: -32800, message: `已取消:${method}` } });
207
+ };
208
+ signal?.addEventListener('abort', onAbort, { once: true });
195
209
  const promise = new Promise((resolve, reject) => {
196
210
  this.pending.set(id, { resolve, reject, timer });
197
211
  });
212
+ // settle 后移除 abort 监听,避免内存积累
213
+ promise.finally(() => {
214
+ signal?.removeEventListener('abort', onAbort);
215
+ }).catch(() => { });
198
216
  try {
199
217
  this.writeRaw({ jsonrpc: '2.0', id, method, params });
200
218
  }
201
219
  catch (err) {
202
220
  clearTimeout(timer);
203
221
  this.pending.delete(id);
222
+ signal?.removeEventListener('abort', onAbort);
204
223
  return Promise.reject(err);
205
224
  }
206
225
  return promise;
package/lib/hooks.d.ts CHANGED
@@ -4,8 +4,14 @@
4
4
  * 与 DSH 的 session-persistence 插件(保存会话日志)不同,这里是"语义沉淀":
5
5
  * 用户消息写入灵枢知识层(带去重与重要性),agent 回复与工具结果可选开启。
6
6
  * 只记忆真实用户消息(source.kind === 'user'),过滤插件注入的噪音。
7
+ *
8
+ * P1 完善(GPT 审查·自动记忆只写不读):新增 autoRecall——通过
9
+ * system-prompt/assemble 事件(waterfall,异步允许)在每次模型请求组装
10
+ * system prompt 时自动注入灵枢最近记忆(timeline),让记忆"自动可用"
11
+ * 而不只依赖 Agent 主动调用 recall/think 工具。失败静默(不影响请求)。
7
12
  */
8
13
  import '@deepseek-ai/dsh-session';
14
+ import '@deepseek-ai/dsh-system-prompt';
9
15
  import type { Context } from '@deepseek-ai/cordis';
10
16
  import type { LingshuBridge } from './bridge.js';
11
17
  /** 自动记忆开关。 */
@@ -18,6 +24,14 @@ export interface MemoryHooksOptions {
18
24
  toolResult: boolean;
19
25
  /** 写入记忆的重要性(0~1),默认 0.6。 */
20
26
  importance: number;
27
+ /** 自动召回注入:模型请求前自动注入灵枢最近记忆(默认 true,失败静默)。 */
28
+ autoRecall: boolean;
29
+ /** 自动召回条数(默认 4)。 */
30
+ autoRecallLimit: number;
31
+ /** 自动记忆脱敏:写入前过滤敏感信息(密钥/密码/令牌/身份证/手机号,默认 true)。 */
32
+ desensitize: boolean;
21
33
  }
34
+ /** 脱敏:替换敏感片段;返回 null 表示整条都是敏感内容(应跳过写入)。 */
35
+ export declare function desensitize(text: string): string | null;
22
36
  /** 安装自动记忆钩子(effect 作用域内,随插件卸载自动移除)。 */
23
37
  export declare function installMemoryHooks(ctx: Context, bridge: LingshuBridge, opts: MemoryHooksOptions): void;
package/lib/hooks.js CHANGED
@@ -4,8 +4,14 @@
4
4
  * 与 DSH 的 session-persistence 插件(保存会话日志)不同,这里是"语义沉淀":
5
5
  * 用户消息写入灵枢知识层(带去重与重要性),agent 回复与工具结果可选开启。
6
6
  * 只记忆真实用户消息(source.kind === 'user'),过滤插件注入的噪音。
7
+ *
8
+ * P1 完善(GPT 审查·自动记忆只写不读):新增 autoRecall——通过
9
+ * system-prompt/assemble 事件(waterfall,异步允许)在每次模型请求组装
10
+ * system prompt 时自动注入灵枢最近记忆(timeline),让记忆"自动可用"
11
+ * 而不只依赖 Agent 主动调用 recall/think 工具。失败静默(不影响请求)。
7
12
  */
8
13
  import '@deepseek-ai/dsh-session';
14
+ import '@deepseek-ai/dsh-system-prompt';
9
15
  /** 从 ContentBlock[] 提取纯文本。 */
10
16
  function extractText(blocks) {
11
17
  const parts = [];
@@ -16,6 +22,33 @@ function extractText(blocks) {
16
22
  }
17
23
  return parts.join('\n').trim();
18
24
  }
25
+ /**
26
+ * 敏感信息模式(GPT 审查·自动记忆脱敏):写入记忆库前过滤凭据/个人标识。
27
+ * 命中 → 替换为 [已过滤:类别](保留对话主体);过滤后只剩占位符/空白 → 整条跳过。
28
+ * 纯内容过滤,不涉及身份认证——开源场景下的隐私保护。
29
+ */
30
+ const SENSITIVE_PATTERNS = [
31
+ { re: /sk-[A-Za-z0-9_-]{8,}/g, label: 'API密钥' },
32
+ { re: /\b(?:api[_-]?key|apikey|access[_-]?token)\b\s*[:=]\s*[^\s,,。;;]+/gi, label: 'API密钥' },
33
+ { re: /\b(?:password|passwd|pwd)\b\s*[:=]\s*[^\s,,。;;]+/gi, label: '密码' },
34
+ { re: /Bearer\s+[A-Za-z0-9._~+/=-]{8,}/gi, label: '令牌' },
35
+ // 中文密码:值限定非中文连续串(凭据特征),避免误伤「密码是重要的安全概念」
36
+ { re: /密码\s*[::是]\s*[A-Za-z0-9_@#$%^&*!.-]{4,}/g, label: '密码' },
37
+ { re: /\b\d{17}[\dXx]\b/g, label: '身份证号' },
38
+ { re: /\b1[3-9]\d{9}\b/g, label: '手机号' },
39
+ ];
40
+ /** 脱敏:替换敏感片段;返回 null 表示整条都是敏感内容(应跳过写入)。 */
41
+ export function desensitize(text) {
42
+ let out = text;
43
+ for (const { re, label } of SENSITIVE_PATTERNS) {
44
+ out = out.replace(re, `[已过滤:${label}]`);
45
+ }
46
+ // 过滤后只剩占位符/空白 → 纯凭据消息,不写(或全部被替换)
47
+ const residue = out.replace(/\[已过滤:[^\]]+\]/g, '').trim();
48
+ if (!residue)
49
+ return null;
50
+ return out;
51
+ }
19
52
  /** 安装自动记忆钩子(effect 作用域内,随插件卸载自动移除)。 */
20
53
  export function installMemoryHooks(ctx, bridge, opts) {
21
54
  const memorize = (tool, args) => {
@@ -23,6 +56,33 @@ export function installMemoryHooks(ctx, bridge, opts) {
23
56
  .callTool(tool, args)
24
57
  .catch((err) => ctx.logger.warn(`dsh-memory: ${tool} 自动记忆失败: ${err.message}`));
25
58
  };
59
+ // P1 完善(GPT 审查·自动记忆脱敏):写入前过滤敏感信息(默认开启)。
60
+ // 命中敏感模式 → 替换为 [已过滤:类别];纯凭据消息 → 跳过写入(不落库)。
61
+ const sanitize = (text) => {
62
+ if (!opts.desensitize)
63
+ return text;
64
+ return desensitize(text);
65
+ };
66
+ // P1 完善(自动 recall 注入):每次模型请求组装 system prompt 时,注入灵枢最近记忆。
67
+ // 用 system-prompt/assemble 事件(waterfall)而非 llm/stream——后者请求 deep-frozen 不可改写。
68
+ if (opts.autoRecall) {
69
+ const recallLimit = Math.max(1, Math.min(10, opts.autoRecallLimit || 4));
70
+ ctx.on('system-prompt/assemble', async (assembly, _ctx, next) => {
71
+ try {
72
+ // 异步取最近记忆(失败静默——不阻塞模型请求)
73
+ const r = await bridge.callTool('timeline', { limit: recallLimit });
74
+ const text = extractText(r.content);
75
+ if (text) {
76
+ assembly.contexts.push({
77
+ name: 'lingshu:auto-recall',
78
+ text: `【灵枢最近记忆】\n${text.slice(0, 600)}`,
79
+ });
80
+ }
81
+ }
82
+ catch { /* 静默:召回失败不影响请求 */ }
83
+ return next();
84
+ });
85
+ }
26
86
  ctx.on('session/event', (_session, event) => {
27
87
  if (event.type === 'user/message' && opts.userMessage) {
28
88
  // 只记真实用户输入(kind='user'),跳过插件注入/系统上下文
@@ -31,13 +91,19 @@ export function installMemoryHooks(ctx, bridge, opts) {
31
91
  const text = extractText(event.data.content);
32
92
  if (!text)
33
93
  return;
34
- memorize('remember', { content: text, importance: opts.importance, tags: ['dsh', 'user'] });
94
+ const safe = sanitize(text); // 脱敏:纯凭据消息 → null → 跳过写入
95
+ if (safe === null)
96
+ return;
97
+ memorize('remember', { content: safe, importance: opts.importance, tags: ['dsh', 'user'] });
35
98
  }
36
99
  else if (event.type === 'assistant/message' && opts.assistantMessage) {
37
100
  const text = extractText(event.data.message.content);
38
101
  if (!text)
39
102
  return;
40
- memorize('remember', { content: text, importance: opts.importance * 0.8, tags: ['dsh', 'assistant'] });
103
+ const safe = sanitize(text);
104
+ if (safe === null)
105
+ return;
106
+ memorize('remember', { content: safe, importance: opts.importance * 0.8, tags: ['dsh', 'assistant'] });
41
107
  }
42
108
  else if (event.type === 'tool/result' && opts.toolResult) {
43
109
  if (event.data.error)
@@ -45,7 +111,10 @@ export function installMemoryHooks(ctx, bridge, opts) {
45
111
  const text = extractText(event.data.message.content);
46
112
  if (!text)
47
113
  return;
48
- memorize('remember', { content: text, importance: opts.importance * 0.6, tags: ['dsh', 'tool'] });
114
+ const safe = sanitize(text);
115
+ if (safe === null)
116
+ return;
117
+ memorize('remember', { content: safe, importance: opts.importance * 0.6, tags: ['dsh', 'tool'] });
49
118
  }
50
119
  });
51
120
  }
package/lib/index.d.ts CHANGED
@@ -21,7 +21,10 @@ import z from '@deepseek-ai/schemastery';
21
21
  import { type ToolSelection } from './tools.js';
22
22
  import { type MemoryHooksOptions } from './hooks.js';
23
23
  export declare const name = "dsh-memory";
24
- /** 本插件依赖的工具注册服务。 */
24
+ /** 本插件依赖的工具注册服务。
25
+ * P1 修复(GPT 审查):timer/webServer 是可选增强(互维/角色网页),此前强声明
26
+ * 导致最小 host(只有 tools)插件永远 PENDING 不激活。只强依赖 tools;
27
+ * timer/webServer 在 apply 内动态检测(存在则启用,缺失则告警跳过)。 */
25
28
  export declare const inject: string[];
26
29
  /** 插件配置。 */
27
30
  export interface Config {
package/lib/index.js CHANGED
@@ -20,9 +20,18 @@ import z from '@deepseek-ai/schemastery';
20
20
  import { LingshuBridge } from './bridge.js';
21
21
  import { registerLingshuTools } from './tools.js';
22
22
  import { installMemoryHooks } from './hooks.js';
23
+ import { installRoleplayWeb } from './roleplay_web.js';
23
24
  export const name = 'dsh-memory';
24
- /** 本插件依赖的工具注册服务。 */
25
- export const inject = ['tools', 'timer'];
25
+ /** P1 修复(GPT 审查):MCP 调用的标准结果在 content[].text,
26
+ * 代码此前误读不存在的 .result 字段导致互维拿不到 judgment/best/复核文本 */
27
+ function mcpText(r) {
28
+ return (r.content ?? []).map((c) => c.text ?? '').join('\n');
29
+ }
30
+ /** 本插件依赖的工具注册服务。
31
+ * P1 修复(GPT 审查):timer/webServer 是可选增强(互维/角色网页),此前强声明
32
+ * 导致最小 host(只有 tools)插件永远 PENDING 不激活。只强依赖 tools;
33
+ * timer/webServer 在 apply 内动态检测(存在则启用,缺失则告警跳过)。 */
34
+ export const inject = ['tools'];
26
35
  export const Config = z.object({
27
36
  serverName: z.string().default('lingshu'),
28
37
  python: z.string().default('python'),
@@ -39,8 +48,11 @@ export const Config = z.object({
39
48
  assistantMessage: z.boolean().default(false),
40
49
  toolResult: z.boolean().default(false),
41
50
  importance: z.number().default(0.6),
51
+ autoRecall: z.boolean().default(true),
52
+ autoRecallLimit: z.number().default(4),
53
+ desensitize: z.boolean().default(true),
42
54
  })
43
- .default({ userMessage: true, assistantMessage: false, toolResult: false, importance: 0.6 }),
55
+ .default({ userMessage: true, assistantMessage: false, toolResult: false, importance: 0.6, autoRecall: true, autoRecallLimit: 4, desensitize: true }),
44
56
  toolCallTimeoutMs: z.number().default(60_000),
45
57
  maxRetryDelayMs: z.number().default(30_000),
46
58
  failOnStartupError: z.boolean().default(false),
@@ -75,47 +87,97 @@ export async function apply(ctx, config) {
75
87
  const ready = await bridge.waitReady();
76
88
  if (!ready) {
77
89
  const message = '灵枢进程无法启动(检查 python 是否可用、aeis 是否安装:pip install aeis)';
78
- if (config.failOnStartupError)
90
+ if (config.failOnStartupError) {
91
+ // P1 修复(GPT 审查):启动失败抛错前必须 dispose——此前 throw 在 try 之前,
92
+ // 桥接对象泄漏 + 后台重试计时器继续跑
93
+ bridge.dispose();
79
94
  throw new Error(message);
95
+ }
80
96
  ctx.logger.warn(`dsh-memory: ${message},继续后台重试`);
81
97
  }
82
98
  const disposers = [];
99
+ let toolsPoll = null;
83
100
  try {
84
- // 工具注册(就绪后执行;未就绪时 tools/list 会报错,由重连机制兜底)
85
- if (ready) {
86
- disposers.push(await registerLingshuTools(ctx, bridge, {
87
- selection: config.tools,
88
- toolPrefix: `${config.serverName}_`,
89
- }));
101
+ // 工具注册:初始就绪立即注册;若启动时未就绪(python 暂不可用/aeis 未装等
102
+ // 竞态),桥重连成功后自动补注册——修复"工具永久缺失"问题。
103
+ let toolsRegistered = false;
104
+ const tryRegister = async () => {
105
+ if (toolsRegistered || !bridge.isReady())
106
+ return;
107
+ try {
108
+ const dispose = await registerLingshuTools(ctx, bridge, {
109
+ selection: config.tools,
110
+ toolPrefix: `${config.serverName}_`,
111
+ });
112
+ disposers.push(dispose);
113
+ toolsRegistered = true;
114
+ // P1 修复(GPT 审查):注册成功后清除轮询——此前 setInterval 永久保留
115
+ if (toolsPoll) {
116
+ clearInterval(toolsPoll);
117
+ toolsPoll = null;
118
+ }
119
+ ctx.logger.info('dsh-memory: 灵枢工具已注册(就绪后补注册)');
120
+ }
121
+ catch (err) {
122
+ ctx.logger.warn(`dsh-memory: 工具注册失败,稍后重试: ${String(err)}`);
123
+ }
124
+ };
125
+ if (ready)
126
+ await tryRegister();
127
+ if (!toolsRegistered) {
128
+ toolsPoll = setInterval(() => { void tryRegister(); }, 2000);
129
+ disposers.push(() => { if (toolsPoll)
130
+ clearInterval(toolsPoll); });
90
131
  }
91
132
  installMemoryHooks(ctx, bridge, config.memory);
133
+ // 角色扮演网页(同源挂载 /roleplay,复用本插件 bridge)
134
+ await installRoleplayWeb(ctx, bridge, config, disposers);
92
135
  // 互维维护(v1.1):心跳写戳 + 守护 A + 任务验证双通道
93
136
  if (config.mutual.enabled) {
94
- const { installMutualMaintenance } = await import('./mutual.js');
95
- // 双通道 hooks:白箱 base_verify(走 bridge 调灵枢)+ DeepSeek 复核
96
- installMutualMaintenance(ctx, { heartbeatMs: config.mutual.heartbeatMs }, {
97
- verify: async (claim) => {
98
- const r = await bridge.callTool('wisdom_verify', { knowledge: claim, limit: 4 });
99
- const data = r?.result ?? {};
100
- return {
101
- judgment: data.judgment ?? '分析中',
102
- best: data.best?.name ?? '',
103
- d_norm: typeof data.D_norm === 'number' ? data.D_norm : -1,
104
- record_id: data.record_id ?? '',
105
- };
106
- },
107
- review: async (claim, w) => {
108
- // DeepSeek 复核(在白箱判定之上,不重复白箱工作)
109
- const reviewResult = await bridge.callTool('think', {
110
- query: `复核以下主张(白箱判定已给出,请独立评估是否同意):${claim.slice(0, 200)}。白箱判定:${w.judgment},best=${w.best}。只输出 同意/质疑/不同意 + 一句话理由`,
111
- });
112
- const text = String((reviewResult?.result) ?? '');
113
- const conclusion = text.includes('同意') ? '同意' :
114
- text.includes('质疑') ? '质疑' : '不同意';
115
- return { conclusion, reason: text.slice(0, 120) };
116
- },
117
- });
118
- ctx.logger.info('dsh-memory: 互维维护已启用(心跳 10min + 守护 A + 任务验证双通道)');
137
+ // P1 修复(GPT 审查):timer 是可选服务——缺失时告警跳过互维,
138
+ // 不让插件因互维而阻塞(inject 已不再强声明 timer)
139
+ const hasTimer = ctx.timer !== undefined;
140
+ if (!hasTimer) {
141
+ ctx.logger.warn('dsh-memory: timer 服务不可用,跳过互维维护(mutual.enabled=true 但无 timer)');
142
+ }
143
+ else {
144
+ const { installMutualMaintenance } = await import('./mutual.js');
145
+ // 双通道 hooks:白箱 base_verify(走 bridge 调灵枢)+ DeepSeek 复核
146
+ installMutualMaintenance(ctx, { heartbeatMs: config.mutual.heartbeatMs }, {
147
+ verify: async (claim) => {
148
+ const r = await bridge.callTool('wisdom_verify', { knowledge: claim, limit: 4 });
149
+ // P1 修复(GPT 审查):结果在 content[].text(JSON),非 .result
150
+ const text = mcpText(r);
151
+ let data = {};
152
+ try {
153
+ data = JSON.parse(text);
154
+ }
155
+ catch { /* 非 JSON 时用默认 */ }
156
+ return {
157
+ judgment: data.judgment ?? '分析中',
158
+ best: data.best?.name ?? '',
159
+ d_norm: typeof data.D_norm === 'number' ? data.D_norm : -1,
160
+ record_id: data.record_id ?? '',
161
+ };
162
+ },
163
+ review: async (claim, w) => {
164
+ // DeepSeek 复核(在白箱判定之上,不重复白箱工作)
165
+ const reviewResult = await bridge.callTool('think', {
166
+ query: `复核以下主张(白箱判定已给出,请独立评估是否同意):${claim.slice(0, 200)}。白箱判定:${w.judgment},best=${w.best}。只输出 同意/质疑/不同意 + 一句话理由`,
167
+ });
168
+ // P1 修复(GPT 审查):文本在 content[].text;结论解析必须先查
169
+ // 「不同意/不通过」再「质疑」再「同意」——「不同意」含子串「同意」,
170
+ // 此前先匹配「同意」→ 不同意被误判为同意
171
+ const text = mcpText(reviewResult);
172
+ const conclusion = text.includes('不同意') || text.includes('不通过')
173
+ ? '不同意'
174
+ : text.includes('质疑') ? '质疑'
175
+ : text.includes('同意') ? '同意' : '不同意';
176
+ return { conclusion, reason: text.slice(0, 120) };
177
+ },
178
+ });
179
+ ctx.logger.info('dsh-memory: 互维维护已启用(心跳 10min + 守护 A + 任务验证双通道)');
180
+ }
119
181
  }
120
182
  }
121
183
  catch (err) {