@max-null/dsh-memory 0.4.1 → 0.5.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/README.md +31 -12
- package/client.js +195 -150
- package/dist/bm25.d.ts +25 -1
- package/dist/bm25.js +99 -20
- package/dist/engine.d.ts +47 -3
- package/dist/engine.js +192 -11
- package/dist/index.d.ts +1 -1
- package/dist/index.js +45 -12
- package/dist/injection.d.ts +34 -0
- package/dist/injection.js +52 -0
- package/dist/remote.d.ts +9 -5
- package/dist/remote.js +11 -4
- package/dist/routes.js +12 -2
- package/dist/self.js +2 -2
- package/package.json +13 -13
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# dsh-memory
|
|
2
2
|
|
|
3
|
+
本插件属于 **`@max-null/*` 插件系列**——这一系列共同构成 **[SSID(思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** 桌面体验。SSID 是整合它们的盒:`dsh-capture` · `dsh-chat-rail` · `dsh-chinese-thinking` · `dsh-draft-polish` · `dsh-guardian` · `dsh-habit` · `dsh-header-unify` · `dsh-memory` · `dsh-node-appearance` · `dsh-plugin-center` · `dsh-skill-mcp-center` · `dsh-ssid-panels` · `dsh-ssid-zh-ui`。
|
|
4
|
+
|
|
5
|
+
This plugin belongs to the **`@max-null/*` family** — a set of plugins that together form the **[SSID (思灵 · Seek Soul in Darkness)](https://github.com/Max-Null/seek-soul-in-darkness)** desktop experience.
|
|
6
|
+
|
|
3
7
|
一个面向 [DeepSeek Harness](https://github.com/deepseek-ai/deepseek-harness) 的**跨会话明文记忆插件**。遵循「一切皆插件」——它不修改 DSH 源码,声明 `name`/`inject`/`apply`,由 Loader 从 `cordis.yml` 加载。
|
|
4
8
|
|
|
5
9
|
## 设计原则
|
|
@@ -7,7 +11,7 @@
|
|
|
7
11
|
1. **人是所有者**:模型只能写入 `suggested` 状态的记忆,绝不自我提升;只有人工确认(`setStatus`)才能让记忆生效。
|
|
8
12
|
2. **可观测先于精准**:每条记忆是明文,`memory_list` 随时可见、`memory_forget` 随时删除——不存在"静默暗礁"。
|
|
9
13
|
3. **明文是人机共享的审计窗口**:记忆是可读文本,模型可自检其是否过期或出错(规划中的 v2)。
|
|
10
|
-
4. **确定性且缓存安全**:BM25 关键词检索是存储的纯函数、无 LLM 调用;固定指引进 system-prompt section,`
|
|
14
|
+
4. **确定性且缓存安全**:BM25 关键词检索是存储的纯函数、无 LLM 调用;固定指引进 system-prompt section,`approved + injected` 记忆进 recall context(global 全量 + 当前会话工作区),逐条为单行摘要并按注入预算截断(超预算按最近使用优先,省略数在面板可见)。
|
|
11
15
|
|
|
12
16
|
## 用法
|
|
13
17
|
|
|
@@ -25,8 +29,9 @@ npm install @max-null/dsh-memory
|
|
|
25
29
|
## 提供的服务与工具
|
|
26
30
|
|
|
27
31
|
- **服务** `ctx.memory`:`remember` / `list` / `search` / `forget` / `setStatus`
|
|
28
|
-
- **工具**:`memory_save`、`memory_list`、`memory_search`、`memory_confirm`、`memory_forget`
|
|
29
|
-
- **注入**:`tool:memory` 指引 section + `memory:recall` 召回 context
|
|
32
|
+
- **工具**:`memory_save`、`memory_list`、`memory_search`、`memory_confirm`、`memory_forget`、`memory_update`
|
|
33
|
+
- **注入**:`tool:memory` 指引 section + `memory:recall` 召回 context(global 的 `approved + injected` + 当前会话工作区的 `approved + injected`,带 `[memory:<id>:<namespace>]` 来源标记;摘要化 + 预算截断)
|
|
34
|
+
- **检索**:BM25(CJK 单字 + 2-gram,content 与 keywords 字段分离加权;中文多字查询精度显著优于单字切分);可选语义融合(见「可选配置」)
|
|
30
35
|
|
|
31
36
|
## 两层存储(global / project)
|
|
32
37
|
|
|
@@ -35,16 +40,17 @@ npm install @max-null/dsh-memory
|
|
|
35
40
|
| namespace | 默认位置 | 用途 |
|
|
36
41
|
|---|---|---|
|
|
37
42
|
| `global` | `$DSH_HOME/storages/memory.json` | 跨项目的个人偏好 |
|
|
38
|
-
| `project` | `<cwd>/.dsh/storages/
|
|
43
|
+
| `project` | `<cwd>/.dsh/storages/memory_project_<hash>.json` | 跟随仓库的项目共识,可 git 分享 |
|
|
39
44
|
|
|
40
|
-
两个根都可用 config 覆盖(`globalRoot` / `projectRoot`)。`memory_list` / `memory_search` 不带 `namespace`
|
|
45
|
+
两个根都可用 config 覆盖(`globalRoot` / `projectRoot`)。`memory_list` / `memory_search` 不带 `namespace` 过滤时会同时查两层。旧版双重前缀文件名(`memory_project_memory_project_<hash>.json`)在打开时自动迁移为规范名。
|
|
41
46
|
|
|
42
47
|
## 使用流程(人工确认闸门)
|
|
43
48
|
|
|
44
49
|
```
|
|
45
50
|
模型 memory_save → status: suggested(只是建议,未生效)
|
|
46
|
-
人 memory_confirm → status:
|
|
47
|
-
|
|
51
|
+
人 memory_confirm → status: approved(已审核;是否常驻注入由独立开关 injected 决定)
|
|
52
|
+
人(面板/开关) → injected: true(每轮注入:global + 当前会话工作区,摘要化 + 预算截断)
|
|
53
|
+
memory_search → 关键词/语义召回任意状态的记忆(命中标记 lastUsedAt 冷热追踪)
|
|
48
54
|
memory_forget → 随时删除
|
|
49
55
|
```
|
|
50
56
|
|
|
@@ -52,15 +58,28 @@ memory_forget → 随时删除
|
|
|
52
58
|
|
|
53
59
|
## 为什么明文 + BM25,而不是向量检索
|
|
54
60
|
|
|
55
|
-
向量检索的记忆本体是一串不可读的数字,过期信息会成为**无法观测、无法修复的静默暗礁**;BM25 +
|
|
61
|
+
向量检索的记忆本体是一串不可读的数字,过期信息会成为**无法观测、无法修复的静默暗礁**;BM25 + 明文让每一次召回都可解释、每一条记忆都可见可删。语义(向量)检索作为**可插拔的可选项**(0.5.2,见「可选配置」)——记忆本体仍是明文,向量仅作为检索辅助字段(`vector`,明文可读),且以"可观测 + 可修复"为门槛。
|
|
56
62
|
|
|
57
|
-
明文还有一层**跟随仓库分享**的好处:`project` 命名空间的记忆落在项目文件夹内(`<cwd>/.dsh/storages/
|
|
63
|
+
明文还有一层**跟随仓库分享**的好处:`project` 命名空间的记忆落在项目文件夹内(`<cwd>/.dsh/storages/memory_project_<hash>.json`),随 `git` 提交、分享给所有协作者;`global` 命名空间的记忆留在本地 `$DSH_HOME`。团队的共识("本项目统一用 Vue3 `<script setup>`")能沉淀进仓库,而不是散落在每个人的本地。FTS5 的 SQLite 二进制无法这样"跟着仓库走"。
|
|
58
64
|
|
|
59
|
-
##
|
|
65
|
+
## 可选配置
|
|
60
66
|
|
|
61
|
-
|
|
67
|
+
在 `cordis.yml` 的 `config` 里传给插件(均可省略):
|
|
62
68
|
|
|
63
|
-
|
|
69
|
+
```yaml
|
|
70
|
+
- id: memory
|
|
71
|
+
name: '@max-null/dsh-memory'
|
|
72
|
+
config:
|
|
73
|
+
injectionBudget: 1500 # 常驻注入预算(字符;null = 不限制)
|
|
74
|
+
summaryChars: 80 # 单条注入摘要截断上限(字符)
|
|
75
|
+
semanticTopK: 5 # 语义侧参与融合的 topK(仅配置 embeddings 时生效)
|
|
76
|
+
# 混合语义检索(可插拔;缺省 = 纯 BM25)
|
|
77
|
+
# embeddings: { embed: '...' 提供 embed(texts) 的宿主函数 或 插件名 ... }
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`embeddings` 接受 `{ embed(texts): Promise<number[][]>, similarity? }` 对象——由宿主装配层提供嵌入实现(如 DeepSeek 嵌入端点);配置后 `memory_search` 以 BM25 + 语义 RRF 融合,向量增量生成并持久化,嵌入调用失败自动降级为纯 BM25。
|
|
81
|
+
|
|
82
|
+
## SSID 系列
|
|
64
83
|
|
|
65
84
|
## 开发
|
|
66
85
|
|
package/client.js
CHANGED
|
@@ -173,7 +173,8 @@ var STRINGS = {
|
|
|
173
173
|
keywordsLabel: "\u5173\u952E\u8BCD",
|
|
174
174
|
suggested: "\u5F85\u5BA1\u6838",
|
|
175
175
|
approved: "\u5DF2\u5BA1\u6838",
|
|
176
|
-
refresh: "\u5237\u65B0"
|
|
176
|
+
refresh: "\u5237\u65B0",
|
|
177
|
+
coldHint: "\u51B7\u6570\u636E \xB7 \u5EFA\u8BAE\u6309\u9700\u5316"
|
|
177
178
|
},
|
|
178
179
|
en: {
|
|
179
180
|
tabMemory: "Memory",
|
|
@@ -197,7 +198,8 @@ var STRINGS = {
|
|
|
197
198
|
keywordsLabel: "Keywords",
|
|
198
199
|
suggested: "Suggested",
|
|
199
200
|
approved: "Approved",
|
|
200
|
-
refresh: "Refresh"
|
|
201
|
+
refresh: "Refresh",
|
|
202
|
+
coldHint: "Cold \xB7 consider on-demand"
|
|
201
203
|
}
|
|
202
204
|
};
|
|
203
205
|
var localeId = "zh";
|
|
@@ -226,7 +228,26 @@ function useT() {
|
|
|
226
228
|
}
|
|
227
229
|
var ssid = {
|
|
228
230
|
accent: "#4FC3F7",
|
|
229
|
-
|
|
231
|
+
// Root column: toolbar + namespace tabs stay pinned at the top (flex:none);
|
|
232
|
+
// only the content area below scrolls (2026-08-22 用户: tab 与功能不应随滚动)。
|
|
233
|
+
wrap: { display: "flex", flexDirection: "column", gap: 8, padding: "10px 12px", height: "100%", boxSizing: "border-box" },
|
|
234
|
+
header: { display: "flex", flexDirection: "column", gap: 8, flex: "none" },
|
|
235
|
+
content: { flex: 1, minHeight: 0, overflowY: "auto", display: "flex", flexDirection: "column", gap: 8, marginRight: -4, paddingRight: 4 },
|
|
236
|
+
// 计数徽标(醒目:accent 底 + 白色数字)。
|
|
237
|
+
count: {
|
|
238
|
+
display: "inline-flex",
|
|
239
|
+
alignItems: "center",
|
|
240
|
+
justifyContent: "center",
|
|
241
|
+
minWidth: 18,
|
|
242
|
+
height: 18,
|
|
243
|
+
padding: "0 6px",
|
|
244
|
+
borderRadius: 9,
|
|
245
|
+
background: "var(--dsw-alias-state-business-primary, #4FC3F7)",
|
|
246
|
+
color: "#fff",
|
|
247
|
+
fontSize: 11,
|
|
248
|
+
fontWeight: 600,
|
|
249
|
+
lineHeight: 1
|
|
250
|
+
},
|
|
230
251
|
card: {
|
|
231
252
|
background: "var(--dsw-alias-bg-layer-1, #131a26)",
|
|
232
253
|
border: "1px solid var(--dsw-alias-border-l2, #1e2836)",
|
|
@@ -275,11 +296,20 @@ var memoryApi = {
|
|
|
275
296
|
setInjected(id, injected, cwd) {
|
|
276
297
|
return api("setInjected", { id, injected, ...cwd === void 0 ? {} : { cwd } });
|
|
277
298
|
},
|
|
278
|
-
injectionPreview() {
|
|
279
|
-
return api("injectionPreview");
|
|
299
|
+
injectionPreview(cwd) {
|
|
300
|
+
return api("injectionPreview", { ...cwd === void 0 ? {} : { cwd } });
|
|
280
301
|
}
|
|
281
302
|
};
|
|
282
303
|
var ORGANIZE_PROMPT = "\u8BF7\u6574\u7406\u6211\u7684\u8BB0\u5FC6\u5E93\uFF1A\u7528 memory_list \u67E5\u770B\u5168\u90E8\u8BB0\u5FC6\u3002\u6574\u7406\u89C4\u5219\uFF08\u5FC5\u987B\u6267\u884C\uFF09\uFF1A\u2460\u540C\u7C7B\u6761\u76EE\u5408\u5E76\u2014\u2014\u5DE5\u4F5C\u4E60\u60EF/\u7EA6\u5B9A\u7C7B\u591A\u6761\u5408\u5E76\u4E3A\u4E00\u6761\uFF08\u5185\u5BB9\u7528 \u2460\u2461\u2462 \u5E76\u5217\uFF0C\u907F\u514D\u788E\u7247\u5316\uFF09\uFF1B\u2461\u4E0E\u300C[\u8BB0\u5FC6\u7CFB\u7EDF\u81EA\u8FF0]\u300D\uFF08\u4F60\u4E0A\u4E0B\u6587\u4E2D\u7684\u8BB0\u5FC6\u673A\u5236\u8BF4\u660E\uFF09\u5185\u5BB9\u91CD\u590D\u7684\u63D2\u4EF6\u4ECB\u7ECD\u6761\u76EE\uFF08\u5982 dsh-memory \u63D2\u4EF6\u53D1\u5E03\u4FE1\u606F\uFF09\u5E94\u5220\u9664\u2014\u2014\u673A\u5236\u8BF4\u660E\u5DF2\u7531\u7CFB\u7EDF\u5E38\u9A7B\u63D0\u4F9B\uFF0C\u65E0\u9700\u7528\u6237\u5B58\u50A8\uFF1B\u2462\u5BF9\u8FC7\u65F6\u3001\u9519\u8BEF\u6216\u5DF2\u53D8\u5316\u7684\u5185\u5BB9\u7528 memory_update \u4FEE\u6B63\uFF08\u4F1A\u91CD\u7F6E\u4E3A\u5F85\u5BA1\u6838\uFF09\uFF1B\u2463\u7CBE\u7B80\u5197\u957F\u5185\u5BB9\uFF0C\u4E3A\u6BCF\u6761\u8865\u5145\u6216\u4FEE\u6B63 keywords\uFF1B\u2464\u9700\u8981\u5220\u9664\u7684\u7528 memory_forget\uFF0C\u9700\u8981\u65B0\u589E\u7684\u7528 memory_save\u3002\u5224\u65AD\u5185\u5BB9\u662F\u5426\u8FC7\u65F6\u7684\u65B9\u6CD5\uFF1A\u628A\u8BB0\u5FC6\u91CC\u63D0\u5230\u7684\u5DE5\u5177\u540D/\u6570\u91CF\u4E0E\u4F60\u5F53\u524D\u5B9E\u9645\u53EF\u7528\u7684\u8BB0\u5FC6\u5DE5\u5177\u5BF9\u7167\u2014\u2014\u4F60\u5F53\u524D\u53EF\u7528\uFF1Amemory_save / memory_list / memory_search / memory_confirm / memory_forget / memory_update\uFF08\u5171 6 \u4E2A\uFF09\uFF1B\u82E5\u8BB0\u5FC6\u4E2D\u7684\u5DE5\u5177\u5217\u8868\u3001\u6570\u91CF\u3001\u6D41\u7A0B\u4E0E\u6B64\u4E0D\u7B26\u5373\u4E3A\u8FC7\u65F6\uFF0C\u7528 memory_update \u4FEE\u6B63\u3002\u6539\u52A8\u5168\u90E8\u843D\u5728 suggested \u7B49\u5F85\u5BA1\u6838\uFF08\u4E0D\u8981\u8C03\u7528 memory_confirm\uFF09\uFF0C\u5B8C\u6210\u540E\u7528\u4E00\u53E5\u8BDD\u6C47\u62A5\u6574\u7406\u7ED3\u679C\u3002";
|
|
304
|
+
var DAY_MS = 864e5;
|
|
305
|
+
var COLD_DAYS = 30;
|
|
306
|
+
function fmtDate(ms) {
|
|
307
|
+
const date = new Date(ms);
|
|
308
|
+
return `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}-${String(date.getDate()).padStart(2, "0")}`;
|
|
309
|
+
}
|
|
310
|
+
function isCold(usedAt, now = Date.now()) {
|
|
311
|
+
return usedAt === void 0 || now - usedAt > COLD_DAYS * DAY_MS;
|
|
312
|
+
}
|
|
283
313
|
function MemoryView(props) {
|
|
284
314
|
const t = useT();
|
|
285
315
|
const [records, setRecords] = (0, import_react.useState)([]);
|
|
@@ -295,7 +325,7 @@ function MemoryView(props) {
|
|
|
295
325
|
return;
|
|
296
326
|
}
|
|
297
327
|
try {
|
|
298
|
-
setPreview(await memoryApi.injectionPreview());
|
|
328
|
+
setPreview(await memoryApi.injectionPreview(props.cwd));
|
|
299
329
|
} catch {
|
|
300
330
|
setPreview(null);
|
|
301
331
|
}
|
|
@@ -369,6 +399,11 @@ function MemoryView(props) {
|
|
|
369
399
|
const q = query.trim().toLowerCase();
|
|
370
400
|
const byNs = namespace === null ? records : namespace === "workspace" ? records.filter((record) => record.namespace === "project") : records.filter((record) => record.namespace === "global");
|
|
371
401
|
const filtered = byNs.filter((record) => q === "" || record.content.toLowerCase().includes(q));
|
|
402
|
+
const nsCounts = {
|
|
403
|
+
all: records.length,
|
|
404
|
+
global: records.filter((record) => record.namespace === "global").length,
|
|
405
|
+
workspace: records.filter((record) => record.namespace === "project").length
|
|
406
|
+
};
|
|
372
407
|
const groups = [
|
|
373
408
|
{ key: "pending", label: t("groupPending"), items: filtered.filter((record) => record.status === "suggested") },
|
|
374
409
|
{ key: "ondemand", label: t("groupOnDemand"), items: filtered.filter((record) => record.status === "approved" && !record.injected) },
|
|
@@ -379,164 +414,174 @@ function MemoryView(props) {
|
|
|
379
414
|
{ style: ssid.wrap },
|
|
380
415
|
(0, import_react.createElement)(
|
|
381
416
|
"div",
|
|
382
|
-
{ style:
|
|
383
|
-
(0, import_react.createElement)("button", {
|
|
384
|
-
type: "button",
|
|
385
|
-
title: t("organizeMemory"),
|
|
386
|
-
onClick: () => {
|
|
387
|
-
void organize();
|
|
388
|
-
},
|
|
389
|
-
disabled: organizing,
|
|
390
|
-
style: { ...ssid.btn, color: ssid.accent, borderColor: ssid.accent }
|
|
391
|
-
}, organizing ? "\u2026" : t("organizeMemory")),
|
|
392
|
-
(0, import_react.createElement)("button", {
|
|
393
|
-
type: "button",
|
|
394
|
-
title: t("injectPreview"),
|
|
395
|
-
onClick: () => {
|
|
396
|
-
void togglePreview();
|
|
397
|
-
},
|
|
398
|
-
style: { ...ssid.btn, ...previewOpen ? { color: ssid.accent, borderColor: ssid.accent } : {} }
|
|
399
|
-
}, t("injectPreview")),
|
|
400
|
-
(0, import_react.createElement)("input", {
|
|
401
|
-
value: query,
|
|
402
|
-
onChange: (event) => {
|
|
403
|
-
setQuery(event.target.value);
|
|
404
|
-
},
|
|
405
|
-
placeholder: t("memorySearch"),
|
|
406
|
-
style: {
|
|
407
|
-
flex: 1,
|
|
408
|
-
padding: "6px 10px",
|
|
409
|
-
fontSize: 12.5,
|
|
410
|
-
boxSizing: "border-box",
|
|
411
|
-
background: "var(--dsw-alias-bg-layer-1, #0f141d)",
|
|
412
|
-
border: "1px solid var(--dsw-alias-border-l2, #1e2836)",
|
|
413
|
-
borderRadius: 8,
|
|
414
|
-
color: "var(--dsw-alias-label-primary, #d8e0ea)",
|
|
415
|
-
outline: "none"
|
|
416
|
-
}
|
|
417
|
-
}),
|
|
418
|
-
(0, import_react.createElement)("button", {
|
|
419
|
-
type: "button",
|
|
420
|
-
title: t("refresh"),
|
|
421
|
-
onClick: () => {
|
|
422
|
-
void refreshFromDisk();
|
|
423
|
-
},
|
|
424
|
-
disabled: refreshing,
|
|
425
|
-
style: ssid.btn
|
|
426
|
-
}, refreshing ? "\u2026" : "\u21BB")
|
|
427
|
-
),
|
|
428
|
-
(0, import_react.createElement)(
|
|
429
|
-
"div",
|
|
430
|
-
{ style: { display: "flex", gap: 4 } },
|
|
431
|
-
[null, "global", "workspace"].map((ns) => (0, import_react.createElement)("button", {
|
|
432
|
-
key: ns ?? "all",
|
|
433
|
-
onClick: () => {
|
|
434
|
-
setNamespace(ns);
|
|
435
|
-
},
|
|
436
|
-
style: { flex: 1, ...ssid.btn, ...namespace === ns ? { color: ssid.accent, borderColor: ssid.accent } : {} }
|
|
437
|
-
}, ns === null ? t("allNamespaces") : ns === "global" ? t("nsGlobal") : t("nsWorkspace")))
|
|
438
|
-
),
|
|
439
|
-
// 注入预览(开发者):self 自述 + 当前注入的记忆 + 上下文占用统计
|
|
440
|
-
previewOpen && preview !== null ? (0, import_react.createElement)(
|
|
441
|
-
"div",
|
|
442
|
-
{ style: { ...ssid.card, display: "flex", flexDirection: "column", gap: 6 } },
|
|
417
|
+
{ style: ssid.header },
|
|
443
418
|
(0, import_react.createElement)(
|
|
444
419
|
"div",
|
|
445
|
-
{ style: {
|
|
446
|
-
(0, import_react.createElement)("
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
420
|
+
{ style: { display: "flex", gap: 6 } },
|
|
421
|
+
(0, import_react.createElement)("button", {
|
|
422
|
+
type: "button",
|
|
423
|
+
title: t("organizeMemory"),
|
|
424
|
+
onClick: () => {
|
|
425
|
+
void organize();
|
|
426
|
+
},
|
|
427
|
+
disabled: organizing,
|
|
428
|
+
style: { ...ssid.btn, color: ssid.accent, borderColor: ssid.accent }
|
|
429
|
+
}, organizing ? "\u2026" : t("organizeMemory")),
|
|
430
|
+
(0, import_react.createElement)("button", {
|
|
431
|
+
type: "button",
|
|
432
|
+
title: t("injectPreview"),
|
|
433
|
+
onClick: () => {
|
|
434
|
+
void togglePreview();
|
|
435
|
+
},
|
|
436
|
+
style: { ...ssid.btn, ...previewOpen ? { color: ssid.accent, borderColor: ssid.accent } : {} }
|
|
437
|
+
}, t("injectPreview")),
|
|
438
|
+
(0, import_react.createElement)("input", {
|
|
439
|
+
value: query,
|
|
440
|
+
onChange: (event) => {
|
|
441
|
+
setQuery(event.target.value);
|
|
442
|
+
},
|
|
443
|
+
placeholder: t("memorySearch"),
|
|
444
|
+
style: {
|
|
445
|
+
flex: 1,
|
|
446
|
+
padding: "6px 10px",
|
|
447
|
+
fontSize: 12.5,
|
|
448
|
+
boxSizing: "border-box",
|
|
449
|
+
background: "var(--dsw-alias-bg-layer-1, #0f141d)",
|
|
450
|
+
border: "1px solid var(--dsw-alias-border-l2, #1e2836)",
|
|
451
|
+
borderRadius: 8,
|
|
452
|
+
color: "var(--dsw-alias-label-primary, #d8e0ea)",
|
|
453
|
+
outline: "none"
|
|
454
|
+
}
|
|
455
|
+
}),
|
|
456
|
+
(0, import_react.createElement)("button", {
|
|
457
|
+
type: "button",
|
|
458
|
+
title: t("refresh"),
|
|
459
|
+
onClick: () => {
|
|
460
|
+
void refreshFromDisk();
|
|
461
|
+
},
|
|
462
|
+
disabled: refreshing,
|
|
463
|
+
style: ssid.btn
|
|
464
|
+
}, refreshing ? "\u2026" : "\u21BB")
|
|
454
465
|
),
|
|
455
|
-
(0, import_react.createElement)("div", { style: { ...ssid.text, fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all" } }, preview.self),
|
|
456
|
-
preview.injected.length === 0 ? (0, import_react.createElement)("div", { style: ssid.muted }, t("empty")) : preview.injected.map((record) => (0, import_react.createElement)(
|
|
457
|
-
"div",
|
|
458
|
-
{ key: record.id, style: { ...ssid.muted, fontSize: 11 } },
|
|
459
|
-
`- [memory:${record.id.slice(0, 8)}] ${record.content}`
|
|
460
|
-
))
|
|
461
|
-
) : null,
|
|
462
|
-
// 未选择工作区:工作区视图显示占位(0.3.4 工作区路由语义)
|
|
463
|
-
namespace === "workspace" && (props.cwd === void 0 || props.cwd === "") ? (0, import_react.createElement)("div", { style: ssid.empty }, t("noWorkspace")) : groups.length === 0 ? (0, import_react.createElement)("div", { style: ssid.empty }, t("empty")) : groups.map((group) => (0, import_react.createElement)(
|
|
464
|
-
"div",
|
|
465
|
-
{ key: group.key, style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
466
466
|
(0, import_react.createElement)(
|
|
467
467
|
"div",
|
|
468
|
-
{ style:
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
"
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
type: "button",
|
|
475
|
-
title: t("confirmAll"),
|
|
468
|
+
{ style: { display: "flex", gap: 4 } },
|
|
469
|
+
[null, "global", "workspace"].map((ns) => {
|
|
470
|
+
const label = ns === null ? t("allNamespaces") : ns === "global" ? t("nsGlobal") : t("nsWorkspace");
|
|
471
|
+
const count = ns === null ? nsCounts.all : ns === "global" ? nsCounts.global : nsCounts.workspace;
|
|
472
|
+
return (0, import_react.createElement)("button", {
|
|
473
|
+
key: ns ?? "all",
|
|
476
474
|
onClick: () => {
|
|
477
|
-
|
|
475
|
+
setNamespace(ns);
|
|
478
476
|
},
|
|
479
|
-
style: { ...ssid.btn,
|
|
480
|
-
},
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
477
|
+
style: { flex: 1, display: "flex", alignItems: "center", justifyContent: "center", gap: 6, ...ssid.btn, ...namespace === ns ? { color: ssid.accent, borderColor: ssid.accent } : {} }
|
|
478
|
+
}, (0, import_react.createElement)("span", null, label), (0, import_react.createElement)("span", { style: ssid.count }, String(count)));
|
|
479
|
+
})
|
|
480
|
+
)
|
|
481
|
+
),
|
|
482
|
+
// 内容区独立滚动:预览 + 分组列表。
|
|
483
|
+
(0, import_react.createElement)(
|
|
484
|
+
"div",
|
|
485
|
+
{ style: ssid.content },
|
|
486
|
+
// 注入预览(开发者):self 自述 + 实际注入行(摘要化+预算)+ 预算使用统计
|
|
487
|
+
previewOpen && preview !== null ? (0, import_react.createElement)(
|
|
485
488
|
"div",
|
|
486
|
-
{
|
|
487
|
-
(0, import_react.createElement)("div", { style: ssid.text }, ContentWithRefs({ text: record.content })),
|
|
488
|
-
// keywords 展示(0.3.5:设计约定 UI 显示 keywords)
|
|
489
|
-
record.keywords !== void 0 && record.keywords.length > 0 ? (0, import_react.createElement)(
|
|
490
|
-
"div",
|
|
491
|
-
{ style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6 } },
|
|
492
|
-
record.keywords.map((keyword) => (0, import_react.createElement)("span", {
|
|
493
|
-
key: keyword,
|
|
494
|
-
style: {
|
|
495
|
-
fontSize: 10,
|
|
496
|
-
padding: "1px 7px",
|
|
497
|
-
borderRadius: 8,
|
|
498
|
-
background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.14))",
|
|
499
|
-
color: "var(--dsw-alias-label-secondary, #67748a)"
|
|
500
|
-
}
|
|
501
|
-
}, keyword))
|
|
502
|
-
) : null,
|
|
489
|
+
{ style: { ...ssid.card, display: "flex", flexDirection: "column", gap: 6 } },
|
|
503
490
|
(0, import_react.createElement)(
|
|
504
491
|
"div",
|
|
505
|
-
{ style: { ...ssid.muted,
|
|
506
|
-
|
|
492
|
+
{ style: { ...ssid.muted, fontSize: 10.5, display: "flex", justifyContent: "space-between", alignItems: "center" } },
|
|
493
|
+
(0, import_react.createElement)("span", null, t("contextUsage")),
|
|
494
|
+
(0, import_react.createElement)("span", null, (() => {
|
|
495
|
+
const selfChars = preview.self.length;
|
|
496
|
+
const total = selfChars + preview.chars;
|
|
497
|
+
const tokens = Math.ceil(total / 2);
|
|
498
|
+
const budgetText = preview.budget === null ? "" : ` / \u9884\u7B97 ${preview.budget}`;
|
|
499
|
+
const omittedText = preview.omitted > 0 ? `\uFF08\u7701\u7565 ${preview.omitted} \u6761\uFF09` : "";
|
|
500
|
+
return `${selfChars}+${preview.chars} \u5B57\u7B26${budgetText}${omittedText} \u2248 ${tokens} token`;
|
|
501
|
+
})())
|
|
507
502
|
),
|
|
503
|
+
(0, import_react.createElement)("div", { style: { ...ssid.text, fontSize: 11, whiteSpace: "pre-wrap", wordBreak: "break-all" } }, preview.self),
|
|
504
|
+
preview.lines.length === 0 ? (0, import_react.createElement)("div", { style: ssid.muted }, t("empty")) : preview.lines.map((line, index) => (0, import_react.createElement)("div", { key: index, style: { ...ssid.muted, fontSize: 11 } }, line))
|
|
505
|
+
) : null,
|
|
506
|
+
// 未选择工作区:工作区视图显示占位(0.3.4 工作区路由语义)
|
|
507
|
+
namespace === "workspace" && (props.cwd === void 0 || props.cwd === "") ? (0, import_react.createElement)("div", { style: ssid.empty }, t("noWorkspace")) : groups.length === 0 ? (0, import_react.createElement)("div", { style: ssid.empty }, t("empty")) : groups.map((group) => (0, import_react.createElement)(
|
|
508
|
+
"div",
|
|
509
|
+
{ key: group.key, style: { display: "flex", flexDirection: "column", gap: 6 } },
|
|
508
510
|
(0, import_react.createElement)(
|
|
509
511
|
"div",
|
|
510
|
-
{ style:
|
|
511
|
-
(0, import_react.createElement)("
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
(0, import_react.createElement)(
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
512
|
+
{ style: ssid.title },
|
|
513
|
+
(0, import_react.createElement)("span", null, group.label),
|
|
514
|
+
(0, import_react.createElement)(
|
|
515
|
+
"div",
|
|
516
|
+
{ style: { display: "flex", alignItems: "center", gap: 6 } },
|
|
517
|
+
group.key === "pending" && group.items.length > 0 ? (0, import_react.createElement)("button", {
|
|
518
|
+
type: "button",
|
|
519
|
+
title: t("confirmAll"),
|
|
520
|
+
onClick: () => {
|
|
521
|
+
void confirmAll();
|
|
522
|
+
},
|
|
523
|
+
style: { ...ssid.btn, padding: "1px 8px", fontSize: 10.5 }
|
|
524
|
+
}, t("confirmAll")) : null,
|
|
525
|
+
(0, import_react.createElement)("span", { style: ssid.count }, String(group.items.length))
|
|
526
|
+
)
|
|
527
|
+
),
|
|
528
|
+
group.items.map((record) => (0, import_react.createElement)(
|
|
529
|
+
"div",
|
|
530
|
+
{ key: record.id, style: ssid.card },
|
|
531
|
+
(0, import_react.createElement)("div", { style: ssid.text }, ContentWithRefs({ text: record.content })),
|
|
532
|
+
// keywords 展示(0.3.5:设计约定 UI 显示 keywords)
|
|
533
|
+
record.keywords !== void 0 && record.keywords.length > 0 ? (0, import_react.createElement)(
|
|
534
|
+
"div",
|
|
535
|
+
{ style: { display: "flex", flexWrap: "wrap", gap: 4, marginTop: 6 } },
|
|
536
|
+
record.keywords.map((keyword) => (0, import_react.createElement)("span", {
|
|
537
|
+
key: keyword,
|
|
538
|
+
style: {
|
|
539
|
+
fontSize: 10,
|
|
540
|
+
padding: "1px 7px",
|
|
541
|
+
borderRadius: 8,
|
|
542
|
+
background: "var(--dsw-alias-bg-module-platform, rgba(128,148,168,.14))",
|
|
543
|
+
color: "var(--dsw-alias-label-secondary, #67748a)"
|
|
544
|
+
}
|
|
545
|
+
}, keyword))
|
|
546
|
+
) : null,
|
|
547
|
+
(0, import_react.createElement)(
|
|
548
|
+
"div",
|
|
549
|
+
{ style: { ...ssid.muted, marginTop: 6 } },
|
|
550
|
+
`${record.namespace} \xB7 ${record.status === "approved" ? t("approved") : t("suggested")}${record.injected ? ` \xB7 ${t("groupInjected")}` : ""}${record.lastUsedAt !== void 0 ? ` \xB7 \u4E0A\u641C ${fmtDate(record.lastUsedAt)}` : ""}${isCold(record.lastUsedAt) ? ` \xB7 ${t("coldHint")}` : ""}`
|
|
551
|
+
),
|
|
552
|
+
(0, import_react.createElement)(
|
|
553
|
+
"div",
|
|
554
|
+
{ style: { display: "flex", gap: 6, marginTop: 8, alignItems: "center" } },
|
|
555
|
+
(0, import_react.createElement)("button", {
|
|
556
|
+
type: "button",
|
|
557
|
+
title: record.status === "approved" ? t("injectSwitch") : t("approveFirst"),
|
|
558
|
+
disabled: record.status !== "approved",
|
|
559
|
+
onClick: () => {
|
|
560
|
+
void toggleInjected(record);
|
|
561
|
+
},
|
|
562
|
+
style: {
|
|
563
|
+
...ssid.btn,
|
|
564
|
+
...record.injected ? { color: ssid.accent, borderColor: ssid.accent } : {},
|
|
565
|
+
opacity: record.status !== "approved" ? 0.4 : 1,
|
|
566
|
+
cursor: record.status !== "approved" ? "not-allowed" : "pointer"
|
|
567
|
+
}
|
|
568
|
+
}, record.injected ? `\u2713 ${t("injectSwitch")}` : t("injectSwitch")),
|
|
569
|
+
record.status === "suggested" ? (0, import_react.createElement)("button", {
|
|
570
|
+
style: ssid.btn,
|
|
571
|
+
onClick: () => {
|
|
572
|
+
void memoryApi.confirm(record.id, props.cwd).then(() => reload());
|
|
573
|
+
}
|
|
574
|
+
}, t("confirm")) : null,
|
|
575
|
+
(0, import_react.createElement)("button", {
|
|
576
|
+
style: ssid.btn,
|
|
577
|
+
onClick: () => {
|
|
578
|
+
void memoryApi.forget(record.id, props.cwd).then(() => reload());
|
|
579
|
+
}
|
|
580
|
+
}, t("forget"))
|
|
581
|
+
)
|
|
582
|
+
))
|
|
538
583
|
))
|
|
539
|
-
)
|
|
584
|
+
)
|
|
540
585
|
);
|
|
541
586
|
}
|
|
542
587
|
function currentSessionCwd(ctx) {
|
package/dist/bm25.d.ts
CHANGED
|
@@ -3,7 +3,31 @@
|
|
|
3
3
|
* store — no model call — so recall is deterministic and a miss is explainable
|
|
4
4
|
* as "no keyword match".
|
|
5
5
|
*/
|
|
6
|
-
/**
|
|
6
|
+
/**
|
|
7
|
+
* Split text into lowercase terms: English/number runs stay whole; each CJK
|
|
8
|
+
* ideograph its own term AND each adjacent pair its own bigram (0.5.2) —
|
|
9
|
+
* bigram raises Chinese multi-char hit precision, single chars keep
|
|
10
|
+
* single-character queries working.
|
|
11
|
+
*/
|
|
7
12
|
export declare function tokenize(text: string): string[];
|
|
8
13
|
/** Score one query against each document with BM25, in document order. */
|
|
9
14
|
export declare function bm25Scores(query: string, docs: readonly string[]): number[];
|
|
15
|
+
/**
|
|
16
|
+
* 字段加权 BM25(0.5.2):body(记忆内容)与 tags(人工关键词)分离打分,
|
|
17
|
+
* tags 命中 ×TAG_BOOST;用于 memory_search,人工关键词比正文更可检索。
|
|
18
|
+
*/
|
|
19
|
+
export declare function bm25FieldScores(query: string, docs: readonly {
|
|
20
|
+
body: string;
|
|
21
|
+
tags?: string;
|
|
22
|
+
}[]): number[];
|
|
23
|
+
/**
|
|
24
|
+
* RRF(Reciprocal Rank Fusion,0.5.2 混合检索):按各通道排名倒数融合。
|
|
25
|
+
* 只依赖排名不依赖分数,两个异构打分通道(BM25 / 余弦相似度)可直接合流。
|
|
26
|
+
* @param rankings - 每通道一个 id → 排名(0 = 第一);同一 id 在多个通道的
|
|
27
|
+
* 分数累加。
|
|
28
|
+
* @param k - RRF 常数(惯例 60)。
|
|
29
|
+
* @returns id → 融合分数(按降序排序的数组)。
|
|
30
|
+
*/
|
|
31
|
+
export declare function rrfFuse(rankings: readonly (readonly string[])[], k?: number): Array<[string, number]>;
|
|
32
|
+
/** 余弦相似度(默认 similarity;向量维度不一致返回 0)。 */
|
|
33
|
+
export declare function cosineSimilarity(left: readonly number[], right: readonly number[]): number;
|