@modusensus/dsh-mneme 0.7.32 → 0.8.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.
- package/README.md +42 -20
- package/lib/api.js +60 -0
- package/lib/client.js +152 -2
- package/lib/commands.js +30 -3
- package/lib/config.js +21 -0
- package/lib/inject.js +39 -5
- package/lib/lang.js +12 -0
- package/lib/scope.js +83 -0
- package/lib/service.js +287 -62
- package/lib/settings.js +7 -0
- package/lib/store.js +100 -5
- package/lib/tools.js +86 -12
- package/package.json +1 -1
- package/src/api.js +60 -0
- package/src/commands.js +30 -3
- package/src/config.js +21 -0
- package/src/inject.js +39 -5
- package/src/lang.js +12 -0
- package/src/scope.js +83 -0
- package/src/service.js +287 -62
- package/src/settings.js +7 -0
- package/src/store.js +100 -5
- package/src/tools.js +86 -12
- package/test/api.test.js +90 -4
- package/test/client.test.js +58 -0
- package/test/commands.test.js +40 -3
- package/test/inject.test.js +93 -0
- package/test/scope-retrieval.test.js +197 -0
- package/test/scope-storage.test.js +247 -0
- package/test/scope-strict.test.js +165 -0
- package/test/service.test.js +76 -0
package/README.md
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
[](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin)
|
|
8
|
-
[](https://github.com/modusensus/dsh-mneme)
|
|
9
9
|
[](https://github.com/modusensus/dsh-mneme/actions)
|
|
10
10
|
[](https://nodejs.org)
|
|
11
11
|
[](https://www.npmjs.com/package/@modusensus/dsh-mneme)
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
|
|
14
14
|
> 给 DeepSeek Harness 的跨会话记忆插件:让 Agent 记住你、记住项目、自动整理记忆。**Mneme**(Μνήμη)——希腊记忆女神 Mnemosyne 之名,掌管记忆与梦境,正如 autoDream 在后台巩固记忆。
|
|
15
15
|
|
|
16
|
-
`dsh-mneme` 是一个 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 插件,为 Agent 提供持久的跨会话记忆能力。它借鉴了 Claude 的 **Dream 机制** 与 cc-haha / Claude Code 的 **autoDream**
|
|
16
|
+
`dsh-mneme` 是一个 [DeepSeek Harness (DSH)](https://github.com/deepseek-ai/deepseek-harness) 插件,为 Agent 提供持久的跨会话记忆能力。它借鉴了 Claude 的 **Dream 机制** 与 cc-haha / Claude Code 的 **autoDream** 记忆巩固功能——不仅**存储**记忆,还会**自动巩固**(去重、合并、冲突裁决、摘要生成),让记忆库越用越精炼。完整溯源与致谢见文末「[🙏 致谢](#-致谢)」。
|
|
17
17
|
|
|
18
18
|
## ⏱️ 30 秒理解
|
|
19
19
|
|
|
20
|
-
**一句话**:给 Agent
|
|
20
|
+
**一句话**:给 Agent 装上跨会话记忆——记住你、记住项目,并在后台自动整理,越用越懂你;**v0.8.0 起**记忆可按 agent 与工作区隔离(`scopeEnabled`),多 Agent / 多项目互不串台。
|
|
21
21
|
|
|
22
22
|
| ① 写入 | ② 存储 | ③ 进化 |
|
|
23
23
|
|--------|--------|--------|
|
|
24
|
-
| 对话中模型主动记录(`memory_save
|
|
24
|
+
| 对话中模型主动记录(`memory_save`,可带敏感度与事件时间);会话结束自动提炼(`autoSummarize`) | SQLite 主库 + 人类可编辑 Markdown 镜像;实体 / 属性 / 时间轴三层结构化;记忆带 agent / workspace / 敏感度 / 发生时间标注 | 新会话自动注入相关记忆(按当前作用域加权,strictScope 开启后硬隔离);autoDream 后台去重 / 合并 / 归档,冲突冻结待人工裁决 |
|
|
25
25
|
|
|
26
26
|
```bash
|
|
27
27
|
# 30 秒上手
|
|
@@ -49,18 +49,19 @@ dsh web
|
|
|
49
49
|
- 逐 type 记录 `committed / failed / pending` 回执,健康端点区分 `ok / degraded / unknown`
|
|
50
50
|
- 状态写失败不静默:同步失败落日志并留债务,重启自动收敛
|
|
51
51
|
|
|
52
|
-
### 模型工具(
|
|
52
|
+
### 模型工具(9 个)
|
|
53
53
|
|
|
54
54
|
| 工具 | 功能 |
|
|
55
55
|
|------|------|
|
|
56
|
-
| `memory_save` |
|
|
57
|
-
| `memory_search` |
|
|
58
|
-
| `memory_list` | 按类型分页列出(`include_archived=true`
|
|
56
|
+
| `memory_save` | 记录一条记忆(自动按标题去重合并;v0.8.0 起去重键含 agent/workspace/scope 作用域,可选 `sensitivity` / `occurred_at` 参数) |
|
|
57
|
+
| `memory_search` | 全文搜索(中文子串友好,可启用向量语义搜索;可选 `occurred_from` / `occurred_to` 按事件发生时间过滤) |
|
|
58
|
+
| `memory_list` | 按类型分页列出(`include_archived=true` 可查看已归档;可选 `occurred_from` / `occurred_to` 时间过滤) |
|
|
59
59
|
| `memory_get` | 读取单条记忆完整正文(v0.7.25,按 id;memory_search / memory_list 命中后读全文) |
|
|
60
60
|
| `memory_update` | 修改已有记忆 |
|
|
61
61
|
| `memory_delete` | 删除记忆(按记忆 ID 精确删除) |
|
|
62
62
|
| `memory_forget` | 抑制注入(降权不删除,可恢复) |
|
|
63
63
|
| `memory_archive` | 归档/恢复记忆(v0.2.5;归档后隐藏于列表/搜索/注入/整理,`archived=false` 可恢复) |
|
|
64
|
+
| `memory_runtime` | 本地推理运行时的状态 / 供给 / 校验(v0.7.32,自管运行时三档取件) |
|
|
64
65
|
|
|
65
66
|
### 自动注入 + 会话摘要
|
|
66
67
|
|
|
@@ -69,11 +70,13 @@ dsh web
|
|
|
69
70
|
|
|
70
71
|
### autoDream 自动记忆整理 🧠
|
|
71
72
|
|
|
73
|
+
> **溯源**:autoDream 的理念源自 Claude Code 的 Auto Dream 记忆巩固功能(Anthropic,Memory 2.0 的后台整理子代理),其「离线巩固」思路与 UC Berkeley & Letta 的 *Sleep-time Compute* 论文([arXiv:2504.13171](https://arxiv.org/abs/2504.13171))一脉相承。dsh-mneme 将其引入 DSH 插件生态,并发展为自己的实现——K-Means++ 聚类预分组、类型化决策清单与可回放的 sha256 摘要审计链,详见文末「[🙏 致谢](#-致谢)」。
|
|
74
|
+
|
|
72
75
|
- **触发**:记忆数 > 10 或总字符 > 5000 时,异步自动触发(不阻塞写入)
|
|
73
76
|
- **决策清单式整理**:LLM 输出 `keep` / `merge` / `archive` / `conflict` / `update` 决策清单,服务端校验后逐条应用
|
|
74
77
|
- `merge`:合并主题相近的条目,保留信息最完整者
|
|
75
78
|
- `archive`:归档过时/冗余条目(可恢复,不物理删除)
|
|
76
|
-
- `conflict
|
|
79
|
+
- `conflict`:裁决矛盾信息,胜者保留、败者归档并追加溯源注释;`conflictFreezeEnabled`(默认关)开启后矛盾对不自动裁决,改为冻结进**冲突队列**(状态页并排对比,人工选保留方,#166)
|
|
77
80
|
- `update`(v0.2.1):直接修正单条记忆的过时/错误内容(单 id / 必须实际变化 / 非 summary / 24h 保护 / 每次 ≤2)
|
|
78
81
|
- **失败追踪(v0.2.1)**:用户纠正记忆时写入 `failure_memories` 表(旧值/新值),为后续自进化积累数据
|
|
79
82
|
- **摘要生成**:整理后生成"记忆库总览"(单一实例),作为下次会话的优先注入
|
|
@@ -123,7 +126,7 @@ dsh web
|
|
|
123
126
|
|
|
124
127
|
- **用户画像**:一段自由文本描述用户自己(角色、背景、偏好),**每轮注入**到系统提示,让 Agent 始终遵循
|
|
125
128
|
- **规则**:Agent 必须遵守的行为规则列表(如"回答先给结论"),同样每轮注入
|
|
126
|
-
-
|
|
129
|
+
- **自定义指令**:注册斜杠命令(`/名称 [补充说明]`),触发时把用户定义的指令内容作为一条用户消息提交给当前 Agent,命令后缀会作为补充说明一并提交;界面仅显示提交回执(宿主不支持 followup 提交时退回旧行为,指令原文会显示在界面,便于复制后手动发送)。命令持久化到 SQLite,启动时自动注册到 DSH 命令表,增删实时生效
|
|
127
130
|
|
|
128
131
|
> 画像与规则通过独立的 `[用户设置]` 注入区块(优先级高于记忆库),即使记忆为空也会注入。
|
|
129
132
|
|
|
@@ -225,6 +228,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
225
228
|
|
|
226
229
|
| 版本 | 亮点 |
|
|
227
230
|
|------|------|
|
|
231
|
+
| **v0.8.0** | 作用域隔离(issue #17 批次 A)+ 冲突集中处理 + 斜杠命令提交 Agent + 注入转义回归修复:记忆按 agent 与工作区双维标注(agent_scope / workspace_scope / sensitivity / occurred_at 四列可空),检索按当前会话作用域加权(命中 ×1.25、他 scope ×0.5 保留可见),opt-in `strictScope` 硬过滤贯通检索/注入/列表/单取(fail-closed);`scopeEnabled` / `strictScope` 默认关、面板「作用域隔离」组可启停;去重键扩展含作用域三元——跨作用域同标题不再物理合并;`occurred_at` 事件发生时间 + `occurred_from/to` 过滤贯通搜索与列表;冲突冻结(conflictFreezeEnabled)新增人工出口——状态页冲突队列并排对比 + 保留 A/B/仅标记(#166);斜杠命令经 agent.followup 真正提交模型(#152);恢复 v0.7.4 的注入花括号转义 + hot memory 跳过 reasoning——`{{.Server.Version}}` 类文本不再卡死会话(#165);988 测试全绿 |
|
|
228
232
|
| **v0.7.32** | 冲突动作集扩展 + 运行时自管化 + 记忆/提示语言 + 候选集 hybrid 档 + 一批修复:`sleepActionSet` 新增 `full` 档(supersede/differentiate/update/merge/conflict/keep 六分支,默认 `conflict` 零行为变化)——supersede 赢家正文干净、输家归档附「已被取代」注记,differentiate 双留+差异注记;sleep 校验接入 `dreamSkipInvalid` 宽容策略(默认 true,一票否决变宽容,严格可关);运行时三档取件(收编/本地 .tgz/registry)+ transformers 转可选 peer(#131);`memory.language`(zh/en)提示/注入/镜像语言可选(#124);summarize 节流 + 产出上限 + 同会话去重(#127);inject 正文读取修复(#129)、#135 五连修(前缀解析/effort 最低档/importance 豁免/ready 语义/覆盖度降级告警)、boot 回填指纹短路修复(#128)、degraded 轮跳过明细落库(#104);916 测试全绿、总覆盖 92.7%(runtime 96%、embedding 100%) |
|
|
229
233
|
| **v0.7.30** | 状态页向量卡修复(issue #118):改读开放的 `/semantic`——不再吃 `/vector-config` 的 401「加载失败」,ollama/local 模式不再恒显「未启用」;新增「初始化中(embedder 不可达,正在重试)」与「已索引 N / M 条」回填进度显示;legacy OpenAI 兼容 embedder「Object · 0D」显示修复(embedder 显式 `name: "OpenAI"` + 维度回退 `index.dimension`);「已索引 N / M」分子分母同口径(`embeddedCount` 剔除归档/遗忘,与 `count()` 默认过滤对齐);embedder init 失败有界重试(共 5 次,不再一次性永久降级,unload 清理定时器)+ `OllamaEmbedder.ready` 生命周期位 + `/semantic` 新增 `ready` 字段;745 测试全绿 |
|
|
230
234
|
| **v0.7.29** | 实体抽取路由契约修复 + 面板控件 + 帮助与反馈:`streamEntityText` 抽取为可测试导出的 `createEntityStreamAdapter`——显式 `provider`/`model` 优先、缺省兜底读默认路由选择;实体抽取思考强度 `entityExtractionReasoning` 被模型拒绝时自动去 effort 重试(与 autoDream/sleep 同一降级策略);设置面板新增实体抽取 `provider`/`model`/思考强度三控件;面板底部新增「帮助与反馈」卡片(GitHub issue 预填 + mailto `work@modusensus.space` + 浏览已知问题)+ 配套 `GET /api/dsh-mneme/info`;744 测试全绿 |
|
|
@@ -330,7 +334,7 @@ v0.3.0 起新增**记忆基因**层:从记忆里抽取**命名实体**、**带
|
|
|
330
334
|
| **v0.7.30** | ✅ 完成 | 状态页向量卡修复 + embedder 有界重试 | 卡片改读 `/semantic`(不再 401「加载失败」、ollama/local 不再恒显「未启用」)+「初始化中 / 已索引 N / M 条」显示(issue #118);legacy OpenAI 兼容 embedder 显式 `name: "OpenAI"` + 维度回退 `index.dimension`(「Object · 0D」修复);`embeddedCount` 对齐 `count()` 默认口径、剔除归档/遗忘(「已索引 269 / 36」修复);embedder init 失败 5 次有界重试不再一次性永久降级 + `OllamaEmbedder.ready` + `/semantic` `ready` 字段;745 测试全绿 |
|
|
331
335
|
| **v0.7.31** | ✅ 完成 | peerDependencies 宿主版本声明修复 | 原 `^0.1.0-rc.6` 按 node-semver 预发布元组规则不匹配 `0.1.5-rc.1` 等中间预发布版本(当前 dsh 用户安装 ERESOLVE),也违反 awesome-dsh-plugin 的 peer-range 预发布分支规范;改显式三段式预发布分支(覆盖 0.1.0-rc.6 至 0.1.5-rc.1 全部已发布 0.1.x 含预发布 + 0.2 预发布留显式分支 + 0.3+ 待验证后放开);745 测试全绿 |
|
|
332
336
|
| **v0.7.32** | ✅ 完成 | sleep 冲突动作集扩展 + 运行时自管化 + 候选集 hybrid 档 + 一批修复 | `sleepActionSet` 新增 `full` 档(supersede/differentiate/update/merge/conflict/keep 六分支,默认 `conflict` 零行为变化)+ sleep 校验接入 `dreamSkipInvalid` 宽容策略 + 被跳决策带 phase 落库 `dream_runs.skipped`;运行时三档取件 + transformers 转可选 peer(#131);`memory.language` 提示/注入/镜像语言可选(#124);候选集向量驱动 hybrid 档(#125,PR #147);summarize 节流 + 产出上限 + 同会话去重(#127);inject 正文读取修复(#129)+ #135 五连修 + boot 回填指纹短路修复(#128)+ degraded 轮跳过明细落库(#104);runtime 覆盖统计归一 + 补齐(总覆盖 92.7%);916 测试全绿 |
|
|
333
|
-
| **v0.8.0** |
|
|
337
|
+
| **v0.8.0** | ✅ 完成 | 作用域隔离 + 冲突队列 | 按 agent 与工作区双维隔离记忆(标注 / 检索加权 / opt-in strictScope 四路硬过滤,issue #17 批次 A);冻结冲突新增人工确认队列(状态页并排对比,#166);斜杠命令真正提交 Agent(#152);注入转义回归恢复 + hot memory 跳过 reasoning(#162)。图谱增强(兴趣漂移可视化)、跨 workspace 记忆共享仍在规划 |
|
|
334
338
|
|
|
335
339
|
> 新能力一律做成**可开关的功能**(配置启用/关闭),默认保守开启、不破坏现有行为。`failure_memories` 表与 autoDream 决策引擎已为后续反思性成长铺好路。
|
|
336
340
|
|
|
@@ -456,6 +460,10 @@ dsh web
|
|
|
456
460
|
| `searchSemanticDedupThreshold` | `0.95` | 语义去重相似度阈值(v0.5.0,默认 0.95,范围 0.5-1.0):`searchSemanticDedup=true` 时生效,调整可防小模型误折叠 |
|
|
457
461
|
| `memoryQualityFilter` | `{enabled:true, archiveThreshold:30, degradeThreshold:60, minContentLength:10, exemptImportance:4}` | 记忆质量过滤(v0.4.6,默认开):写库前启发式打分 0-100,元记忆词汇/自指/过短/重复/近似重复扣分;≥60 正常存储,30-60 降权(注入排序按 importance×quality/100),<30 归档标记 `low_quality`(显式搜索仍可召回,永不自动注入;`exemptImportance` 豁免:importance ≥ 该值只降权不归档,#135) |
|
|
458
462
|
| `llmAudit` | `{enabled:true, retentionDays:90}` | LLM 消耗审计(v0.4.6,默认开):每次后台 LLM 调用(autoDream/autoSummarize)写 `llm_audit_logs`(tokens/duration/status/source);失败记 error 不阻塞;只读 API `/api/dsh-mneme/semantic/llm-audit` + `/llm-audit/stats` |
|
|
463
|
+
| `scopeEnabled` | `false` | 作用域隔离总开关(v0.8.0,issue #17):memory_save 按会话身份写入 agent / workspace 标注(agentPreset / 工作区路径,registry 反查取不到回退 header.cwd,再取不到 NULL);去重键扩展含作用域三元——跨作用域同标题不再物理合并;检索按当前会话作用域加权(命中 ×1.25、他 scope ×0.5 保留可见)。也走 feature_flags 白名单(面板可启停) |
|
|
464
|
+
| `strictScope` | `false` | 作用域硬过滤(v0.8.0,依赖 `scopeEnabled`):检索 / 注入 / 列表 / 单取四路过滤,带他者作用域的记忆完全不可见;会话身份解析不到时 fail-closed 只见未标注行。关闭时为软隔离(降权保留可见)。也走 feature_flags 白名单 |
|
|
465
|
+
| `conflictFreezeEnabled` | `false` | 冲突冻结(v0.4.4):dream 发现矛盾对不自动裁决,冻结进冲突队列;状态页「冲突队列」支持并排对比与人工确认(保留 A / 保留 B / 仅标记已处理,v0.8.0) |
|
|
466
|
+
| `escapePromptVariables` | `true` | 注入文本花括号转义(v0.8.0 恢复,issue #162):注入边界把 `{{...}}` 转义,防止 hot memory / 记忆原文里的 Go template / Vue 语法触发宿主 interpolate 抛错卡死会话(v0.7.4 曾修复、v0.7.11 误删) |
|
|
459
467
|
|
|
460
468
|
> 🔐 **API 安全**:DSH 无内置鉴权且默认仅监听 `127.0.0.1`。插件 API 默认开放(便于 Web 面板即装即用)。如需防护(如局域网暴露),在配置中设置 `apiToken`:写操作(画像/规则/命令)与密钥端点(`vector-config`、`vector-reindex`)需携带 `Authorization: Bearer <token>`(前端设置面板可填入同一 token),只读的 `list` / `search` / `semantic` 保持开放。`/api/dsh-mneme/vector-config` 返回的 `apiKey` 已掩码(`sk-***…`),存储仍保留明文供调用;前端回传空或掩码值表示"不改 key"。
|
|
461
469
|
|
|
@@ -552,25 +560,29 @@ dsh-mneme config show # 查看当前配置(toke
|
|
|
552
560
|
|
|
553
561
|
```
|
|
554
562
|
src/
|
|
555
|
-
├── store.js # SQLite 存储(CRUD、搜索、归档/遗忘、schema
|
|
563
|
+
├── store.js # SQLite 存储(CRUD、搜索、归档/遗忘、schema 迁移、scope 四列)
|
|
556
564
|
├── mirror.js # Markdown 镜像(渲染/解析,人工优先)
|
|
557
|
-
├── service.js #
|
|
565
|
+
├── service.js # 领域逻辑(去重合并、注入筛选、写入钩子、scope 加权/硬过滤、冲突队列)
|
|
566
|
+
├── scope.js # 作用域解析(agentPreset + workspace registry 反查,issue #17)
|
|
558
567
|
├── config.js # schemastery 配置 schema
|
|
559
|
-
├── tools.js #
|
|
560
|
-
├── inject.js # systemPrompt.context
|
|
568
|
+
├── tools.js # 9 个模型工具(defineTool)
|
|
569
|
+
├── inject.js # systemPrompt.context 动态注入(含花括号转义)
|
|
561
570
|
├── summarize.js # 会话结束 LLM 摘要
|
|
562
571
|
├── dream.js # autoDream 调度 + runDream(LLM 决策 + 摘要)
|
|
563
572
|
├── dream/decisions.js# 决策校验(fail-safe)+ 决策应用
|
|
573
|
+
├── dream/clustering.js # 候选集聚类(k-means++ seeding)
|
|
574
|
+
├── dream/sleep.js # Sleep Mode 分层压缩
|
|
564
575
|
├── entities/extractor.js # 实体抽取器(v0.3.0:LLM JSON 抽取 + 去重 + fail-safe)
|
|
565
576
|
├── search/bm25.js # BM25 稀疏召回(v0.5.0:分词 + IDF 索引)
|
|
566
577
|
├── search/adaptive.js# 自适应相似度阈值(v0.5.0)
|
|
567
|
-
├── hot-memory.js # 会话级短期热记忆(v0.5.0:滚动轮次 + token
|
|
578
|
+
├── hot-memory.js # 会话级短期热记忆(v0.5.0:滚动轮次 + token 预算;注入剥离 reasoning)
|
|
579
|
+
├── runtime/ # 自管本地推理运行时(收编 / 本地 .tgz / registry 三档取件 + 校验)
|
|
568
580
|
├── embedding.js # OpenAI 兼容 embeddings 客户端 + 向量检索
|
|
569
|
-
├── api.js # HTTP 路由(Web
|
|
581
|
+
├── api.js # HTTP 路由(Web 面板数据通道,含 /conflicts 冲突队列)
|
|
570
582
|
└── index.js # 插件接线
|
|
571
583
|
lib/ # src 的同步分发产物(npm run sync;发布前由 root prepack 的 check-sync.js 校验一致性;唯一手写例外 lib/client.js——Web 面板 bundle,sync 不覆盖)
|
|
572
|
-
test/ #
|
|
573
|
-
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js 召回基准
|
|
584
|
+
test/ # 988 个 node:test 测试(审计与三轴线压测不变量;src↔lib 一致性由 scripts/check-sync.js 发布闸门校验)
|
|
585
|
+
scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压测 · sync-lib.js 同步 · check-sync.js 发布闸门 · benchmark-recall.js 召回基准 · release-prep.mjs 发布准备
|
|
574
586
|
```
|
|
575
587
|
|
|
576
588
|
## 🧪 开发
|
|
@@ -578,7 +590,7 @@ scripts/ # e2e-dsh.js 端到端演示 · stress-dsh.js 三轴线压
|
|
|
578
590
|
```bash
|
|
579
591
|
cd dsh-mneme
|
|
580
592
|
npm install # 安装 peer 依赖(以 devDependencies 形式,用于本地测试)
|
|
581
|
-
npm test # 运行
|
|
593
|
+
npm test # 运行 988 个测试
|
|
582
594
|
npm run stress # 三轴线压测:长会话检索 / 冲突仲裁 / 多 Agent 并发(离线 mock LLM)
|
|
583
595
|
npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动执行)
|
|
584
596
|
```
|
|
@@ -596,6 +608,16 @@ npm run sync # 把 src/ 同步到 lib/(发布时由 prepack 钩子自动
|
|
|
596
608
|
- [本地模型部署指南](docs/LOCAL_MODEL.md)
|
|
597
609
|
- [从 v0.1 升级说明](docs/MIGRATION.md)
|
|
598
610
|
|
|
611
|
+
## 🙏 致谢
|
|
612
|
+
|
|
613
|
+
autoDream 的理念溯源(理念借鉴、实现原创):
|
|
614
|
+
|
|
615
|
+
- **[Claude Code 的 Auto Dream](https://code.claude.com/docs/en/memory)**(Anthropic,Memory 2.0):理念源头——会话间隙由后台子代理整理记忆文件(去重、修矛盾、清衰减)。
|
|
616
|
+
- **[Sleep-time Compute: Beyond Inference Scaling at Test-time](https://arxiv.org/abs/2504.13171)**(UC Berkeley & Letta,arXiv:2504.13171):Auto Dream「离线巩固」思想背后的学术脉络。
|
|
617
|
+
- **cc-haha**:早期实现思路的参照之一。
|
|
618
|
+
|
|
619
|
+
在上述工作之上,dsh-mneme 做了自己的工程发展:K-Means++ 聚类预分组(`src/dream/clustering.js`)、类型化决策清单(keep / merge / archive / conflict / update,及 sleep 侧 supersede / differentiate)与可回放的 sha256 摘要审计链(`dream_runs` / `receipt_chain`)。如有遗漏的灵感来源,欢迎提 issue 指出。
|
|
620
|
+
|
|
599
621
|
## 📜 License
|
|
600
622
|
|
|
601
623
|
MIT
|
package/lib/api.js
CHANGED
|
@@ -1014,6 +1014,66 @@ export function createApi(ctx, service, settings, commands, embedder, semantic =
|
|
|
1014
1014
|
}
|
|
1015
1015
|
});
|
|
1016
1016
|
|
|
1017
|
+
// --- 冲突集中处理(v0.8.0):队列 + 人工确认 --------------------------------
|
|
1018
|
+
// GET /conflicts:未解决冲突 + 双方当前内容(此前面板只有计数与徽章,
|
|
1019
|
+
// resolveConflictPending 在存储层躺着没有任何调用方)。POST /conflicts/resolve:
|
|
1020
|
+
// {id, winner: "a"|"b"|null, apply}——盖章审计,apply 时按 dream 非冻结
|
|
1021
|
+
// conflict 路径同款处置(service.resolveConflictPending 内部复用
|
|
1022
|
+
// applyDecisions:CAS + 事务 + 注记 + 幂等)。认证与其他写路由一致。
|
|
1023
|
+
register({
|
|
1024
|
+
kind: "exact",
|
|
1025
|
+
path: "/api/dsh-mneme/conflicts",
|
|
1026
|
+
handler(req, res) {
|
|
1027
|
+
try {
|
|
1028
|
+
if (req.method !== "GET") {
|
|
1029
|
+
sendJson(res, 404, { error: "not-found" });
|
|
1030
|
+
return;
|
|
1031
|
+
}
|
|
1032
|
+
if (!requireAuth(req, res, apiToken)) return;
|
|
1033
|
+
const url = new URL(req.url, "http://localhost");
|
|
1034
|
+
const limit = Math.min(200, Math.max(1, Number(url.searchParams.get("limit") ?? 50) || 50));
|
|
1035
|
+
const offset = Math.max(0, Number(url.searchParams.get("offset") ?? 0) || 0);
|
|
1036
|
+
const items = service.listConflictQueue({ limit, offset });
|
|
1037
|
+
sendJson(res, 200, { items, total: service.countConflictPending?.() ?? items.length });
|
|
1038
|
+
} catch {
|
|
1039
|
+
sendJson(res, 500, { error: "internal" });
|
|
1040
|
+
}
|
|
1041
|
+
}
|
|
1042
|
+
});
|
|
1043
|
+
|
|
1044
|
+
register({
|
|
1045
|
+
kind: "exact",
|
|
1046
|
+
path: "/api/dsh-mneme/conflicts/resolve",
|
|
1047
|
+
handler(req, res) {
|
|
1048
|
+
try {
|
|
1049
|
+
if (req.method !== "POST") {
|
|
1050
|
+
sendJson(res, 404, { error: "not-found" });
|
|
1051
|
+
return;
|
|
1052
|
+
}
|
|
1053
|
+
if (!requireAuth(req, res, apiToken)) return;
|
|
1054
|
+
return readBody(req).then((text) => {
|
|
1055
|
+
let body = {};
|
|
1056
|
+
try { body = JSON.parse(text || "{}"); } catch { /* 坏 JSON → 走缺参分支 */ }
|
|
1057
|
+
const id = typeof body.id === "string" ? body.id.trim() : "";
|
|
1058
|
+
if (!id) {
|
|
1059
|
+
sendJson(res, 400, { error: "missing-id" });
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
const winner = body.winner === "a" || body.winner === "b" ? body.winner : null;
|
|
1063
|
+
const apply = body.apply !== false;
|
|
1064
|
+
const result = service.resolveConflictPending(id, { winner, apply });
|
|
1065
|
+
if (!result) {
|
|
1066
|
+
sendJson(res, 404, { error: "not-found" });
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1069
|
+
sendJson(res, 200, { ok: true, conflict: result });
|
|
1070
|
+
}).catch(() => sendJson(res, 500, { error: "internal" }));
|
|
1071
|
+
} catch {
|
|
1072
|
+
sendJson(res, 500, { error: "internal" });
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
});
|
|
1076
|
+
|
|
1017
1077
|
// --- health: mirror sync state (F-NEW-03 / v0.3.6) ---
|
|
1018
1078
|
// Auth-gated; only returns a sanitized error code (never raw last_error which
|
|
1019
1079
|
// may leak paths/token-like strings/internal hosts). On state read failure it
|
package/lib/client.js
CHANGED
|
@@ -105,6 +105,31 @@ window.__ModuleLoader__.load({
|
|
|
105
105
|
h("span", { className: "mneme-heatpct" }, `${pct}%`));
|
|
106
106
|
};
|
|
107
107
|
|
|
108
|
+
/**
|
|
109
|
+
* v0.8.0 A4(issue #17):scope 标注徽章——条目的归属来源一行摘要。
|
|
110
|
+
*
|
|
111
|
+
* 仅在条目带任一 scope 标注(agent_scope / workspace_scope / sensitivity)
|
|
112
|
+
* 时渲染;字段缺省返回 null(未标注条目视觉零变化,前端不感知开关状态,
|
|
113
|
+
* 与 HeatBadge 的「数据缺省即不渲染」同一模式)。tooltip 展示完整归属,
|
|
114
|
+
* 标签复用详情抽屉的 i18n 键(云端复验观察点:tooltip 曾硬编码英文)。
|
|
115
|
+
*
|
|
116
|
+
* @param {{m: object, t: (key: string) => string}} props
|
|
117
|
+
* @param {object} props.m 记忆条目(/list、/search 透出的 toApiList 形状,
|
|
118
|
+
* scope 四字段条件存在)
|
|
119
|
+
* @param {(key: string) => string} props.t i18n 取词函数(调用点闭包提供)
|
|
120
|
+
* @returns {object|null} 徽章 vnode;无任何标注时为 null
|
|
121
|
+
*/
|
|
122
|
+
const ScopeBadge = ({ m, t }) => {
|
|
123
|
+
if (!m || (!m.agent_scope && !m.workspace_scope && !m.sensitivity)) return null;
|
|
124
|
+
const tip = [
|
|
125
|
+
m.agent_scope ? `${t("memory.explorer.detail.agentScope")}: ${m.agent_scope}` : null,
|
|
126
|
+
m.workspace_scope ? `${t("memory.explorer.detail.workspaceScope")}: ${m.workspace_scope}` : null,
|
|
127
|
+
m.sensitivity ? `${t("memory.explorer.detail.sensitivity")}: ${m.sensitivity}` : null
|
|
128
|
+
].filter(Boolean).join(" · ");
|
|
129
|
+
return h("span", { className: "mneme-badge mneme-badge--scope", title: tip },
|
|
130
|
+
t("memory.explorer.scopeBadge"));
|
|
131
|
+
};
|
|
132
|
+
|
|
108
133
|
// Unified API fetcher: attaches the optional apiToken (set in the settings
|
|
109
134
|
// view, persisted in localStorage) as a Bearer header. When no token has
|
|
110
135
|
// been configured the header is omitted and the API stays open (default).
|
|
@@ -270,6 +295,7 @@ window.__ModuleLoader__.load({
|
|
|
270
295
|
"memory.features.group.core": "核心",
|
|
271
296
|
"memory.features.group.enhance": "记忆增强",
|
|
272
297
|
"memory.features.group.dream": "巩固与睡眠",
|
|
298
|
+
"memory.features.group.scope": "作用域隔离",
|
|
273
299
|
"memory.features.group.advanced": "高级",
|
|
274
300
|
"memory.features.advancedToggle": "高级(注入策略 · 反思 · 实验项)",
|
|
275
301
|
"memory.features.restartHint": "重启 DSH 后生效",
|
|
@@ -322,6 +348,10 @@ window.__ModuleLoader__.load({
|
|
|
322
348
|
"memory.features.bm25SearchEnabled.hint": "传统关键词打分检索,与向量召回互补",
|
|
323
349
|
"memory.features.conflictFreezeEnabled": "冲突冻结",
|
|
324
350
|
"memory.features.conflictFreezeEnabled.hint": "巩固发现互相矛盾的记忆时先冻结待确认",
|
|
351
|
+
"memory.features.scopeEnabled": "作用域标注",
|
|
352
|
+
"memory.features.scopeEnabled.hint": "按会话身份(agent/工作区)给新记忆打归属标注;重启 DSH 后生效",
|
|
353
|
+
"memory.features.strictScope": "严格隔离",
|
|
354
|
+
"memory.features.strictScope.hint": "检索与注入硬过滤:带他者作用域的记忆完全不可见(关闭时仅降权保留可见);重启 DSH 后生效",
|
|
325
355
|
"memory.features.trustEpistemicWeighting": "可信度加权",
|
|
326
356
|
"memory.features.trustEpistemicWeighting.hint": "按来源可信度调整召回排序(实验性)",
|
|
327
357
|
"memory.features.reflectionFailureTracking": "反思失败追踪",
|
|
@@ -374,6 +404,11 @@ window.__ModuleLoader__.load({
|
|
|
374
404
|
"memory.explorer.exportFailed": "导出失败",
|
|
375
405
|
"memory.explorer.conflictBadge": "冲突",
|
|
376
406
|
"memory.explorer.archivedBadge": "已归档",
|
|
407
|
+
"memory.explorer.scopeBadge": "作用域",
|
|
408
|
+
"memory.explorer.detail.agentScope": "Agent 作用域",
|
|
409
|
+
"memory.explorer.detail.workspaceScope": "工作区作用域",
|
|
410
|
+
"memory.explorer.detail.sensitivity": "敏感度",
|
|
411
|
+
"memory.explorer.detail.occurred": "发生时间",
|
|
377
412
|
"memory.explorer.detail.edit": "编辑",
|
|
378
413
|
"memory.explorer.detail.save": "保存",
|
|
379
414
|
"memory.explorer.detail.cancel": "取消",
|
|
@@ -394,6 +429,15 @@ window.__ModuleLoader__.load({
|
|
|
394
429
|
"memory.status.dreamNever": "尚未运行",
|
|
395
430
|
"memory.status.conflicts": "待确认冲突",
|
|
396
431
|
"memory.status.conflictsHint": "冻结的矛盾记忆,等待人工确认",
|
|
432
|
+
"memory.status.conflictQueue.reason": "原因",
|
|
433
|
+
"memory.status.conflictQueue.sideA": "A 方",
|
|
434
|
+
"memory.status.conflictQueue.sideB": "B 方",
|
|
435
|
+
"memory.status.conflictQueue.keepA": "保留 A",
|
|
436
|
+
"memory.status.conflictQueue.keepB": "保留 B",
|
|
437
|
+
"memory.status.conflictQueue.markReviewed": "仅标记已处理",
|
|
438
|
+
"memory.status.conflictQueue.applyHint": "确认后:保留方正文追加已否决注记,另一方归档。",
|
|
439
|
+
"memory.status.conflictQueue.missing": "(该记忆已不存在)",
|
|
440
|
+
"memory.status.conflictQueue.refresh": "刷新队列",
|
|
397
441
|
"memory.status.workbench": "工作动态",
|
|
398
442
|
"memory.status.dreamConsolidate": "记忆巩固",
|
|
399
443
|
"memory.status.summarize": "总结提炼",
|
|
@@ -592,6 +636,7 @@ window.__ModuleLoader__.load({
|
|
|
592
636
|
"memory.features.group.core": "Core",
|
|
593
637
|
"memory.features.group.enhance": "Enhancement",
|
|
594
638
|
"memory.features.group.dream": "Consolidation",
|
|
639
|
+
"memory.features.group.scope": "Scope isolation",
|
|
595
640
|
"memory.features.group.advanced": "Advanced",
|
|
596
641
|
"memory.features.advancedToggle": "Advanced (injection · reflection · experimental)",
|
|
597
642
|
"memory.features.restartHint": "Takes effect after restarting DSH",
|
|
@@ -644,6 +689,10 @@ window.__ModuleLoader__.load({
|
|
|
644
689
|
"memory.features.bm25SearchEnabled.hint": "Classic keyword scoring, complementary to vector recall",
|
|
645
690
|
"memory.features.conflictFreezeEnabled": "Conflict freezing",
|
|
646
691
|
"memory.features.conflictFreezeEnabled.hint": "Freeze contradictory memories for confirmation during consolidation",
|
|
692
|
+
"memory.features.scopeEnabled": "Scope tagging",
|
|
693
|
+
"memory.features.scopeEnabled.hint": "Stamp new memories with the writing session's identity (agent/workspace); takes effect after restarting DSH",
|
|
694
|
+
"memory.features.strictScope": "Strict isolation",
|
|
695
|
+
"memory.features.strictScope.hint": "Hard-filter search and injection: memories scoped to other agents/workspaces become invisible (off = demoted but visible); takes effect after restarting DSH",
|
|
647
696
|
"memory.features.trustEpistemicWeighting": "Credibility weighting",
|
|
648
697
|
"memory.features.trustEpistemicWeighting.hint": "Adjust recall ranking by source credibility (experimental)",
|
|
649
698
|
"memory.features.reflectionFailureTracking": "Reflection failure tracking",
|
|
@@ -696,6 +745,11 @@ window.__ModuleLoader__.load({
|
|
|
696
745
|
"memory.explorer.exportFailed": "Export failed",
|
|
697
746
|
"memory.explorer.conflictBadge": "Conflict",
|
|
698
747
|
"memory.explorer.archivedBadge": "Archived",
|
|
748
|
+
"memory.explorer.scopeBadge": "Scoped",
|
|
749
|
+
"memory.explorer.detail.agentScope": "Agent scope",
|
|
750
|
+
"memory.explorer.detail.workspaceScope": "Workspace scope",
|
|
751
|
+
"memory.explorer.detail.sensitivity": "Sensitivity",
|
|
752
|
+
"memory.explorer.detail.occurred": "Occurred",
|
|
699
753
|
"memory.explorer.detail.edit": "Edit",
|
|
700
754
|
"memory.explorer.detail.save": "Save",
|
|
701
755
|
"memory.explorer.detail.cancel": "Cancel",
|
|
@@ -716,6 +770,15 @@ window.__ModuleLoader__.load({
|
|
|
716
770
|
"memory.status.dreamNever": "Not yet run",
|
|
717
771
|
"memory.status.conflicts": "Pending conflicts",
|
|
718
772
|
"memory.status.conflictsHint": "Frozen contradictory memories awaiting confirmation",
|
|
773
|
+
"memory.status.conflictQueue.reason": "Reason",
|
|
774
|
+
"memory.status.conflictQueue.sideA": "Side A",
|
|
775
|
+
"memory.status.conflictQueue.sideB": "Side B",
|
|
776
|
+
"memory.status.conflictQueue.keepA": "Keep A",
|
|
777
|
+
"memory.status.conflictQueue.keepB": "Keep B",
|
|
778
|
+
"memory.status.conflictQueue.markReviewed": "Mark reviewed only",
|
|
779
|
+
"memory.status.conflictQueue.applyHint": "On confirm: a veto note is appended to the kept side and the other side is archived.",
|
|
780
|
+
"memory.status.conflictQueue.missing": "(memory no longer exists)",
|
|
781
|
+
"memory.status.conflictQueue.refresh": "Refresh queue",
|
|
719
782
|
"memory.status.workbench": "Activity",
|
|
720
783
|
"memory.status.dreamConsolidate": "Consolidation",
|
|
721
784
|
"memory.status.summarize": "Summarization",
|
|
@@ -1043,6 +1106,20 @@ window.__ModuleLoader__.load({
|
|
|
1043
1106
|
".mneme-badge{flex:none;display:inline-flex;align-items:center;gap:4px;height:18px;padding:0 7px;border-radius:6px;font-size:11px;line-height:14px}",
|
|
1044
1107
|
".mneme-badge--conflict{color:var(--dsw-alias-state-error,#c33);background:color-mix(in srgb,var(--dsw-alias-state-error,#c33) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-error,#c33) 30%,transparent)}",
|
|
1045
1108
|
".mneme-badge--archived{color:var(--dsw-alias-label-tertiary);background:var(--dsw-alias-interactive-bg-hover)}",
|
|
1109
|
+
".mneme-badge--scope{color:var(--dsw-alias-state-business-primary);background:color-mix(in srgb,var(--dsw-alias-state-business-primary) 10%,transparent);border:1px solid color-mix(in srgb,var(--dsw-alias-state-business-primary) 30%,transparent)}",
|
|
1110
|
+
// 冲突集中处理队列(v0.8.0):状态页的待确认冲突列表
|
|
1111
|
+
".mneme-conflictq{display:flex;flex-direction:column;gap:10px}",
|
|
1112
|
+
".mneme-conflictq-head{display:flex;align-items:center;gap:8px}",
|
|
1113
|
+
".mneme-conflict-item{border:1px solid var(--dsw-alias-border,var(--dsw-alias-interactive-bg-hover));border-radius:10px;padding:10px;display:flex;flex-direction:column;gap:8px}",
|
|
1114
|
+
".mneme-conflict-reason{font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary)}",
|
|
1115
|
+
".mneme-conflict-pair{display:grid;grid-template-columns:1fr 1fr;gap:8px}",
|
|
1116
|
+
".mneme-conflict-side{border:1px solid var(--dsw-alias-border,var(--dsw-alias-interactive-bg-hover));border-radius:8px;padding:8px;display:flex;flex-direction:column;gap:4px;min-width:0}",
|
|
1117
|
+
".mneme-conflict-sidelabel{font-size:11px;color:var(--dsw-alias-label-tertiary)}",
|
|
1118
|
+
".mneme-conflict-sidetitle{font-size:13px;font-weight:600;color:var(--dsw-alias-label-primary);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}",
|
|
1119
|
+
".mneme-conflict-snippet{font-size:12px;line-height:17px;color:var(--dsw-alias-label-secondary);display:-webkit-box;-webkit-line-clamp:3;-webkit-box-orient:vertical;overflow:hidden}",
|
|
1120
|
+
".mneme-conflict-missing{font-size:12px;color:var(--dsw-alias-label-tertiary)}",
|
|
1121
|
+
".mneme-conflict-actions{display:flex;align-items:center;gap:6px;flex-wrap:wrap}",
|
|
1122
|
+
".mneme-conflict-hint{font-size:11px;color:var(--dsw-alias-label-tertiary);margin-right:auto}",
|
|
1046
1123
|
// 详情抽屉:sheet 内右侧滑出,覆盖在浏览区之上
|
|
1047
1124
|
".mneme-xmain{position:relative}",
|
|
1048
1125
|
"@keyframes mneme-slidein{from{opacity:0;transform:translateX(16px)}to{opacity:1;transform:translateX(0)}}",
|
|
@@ -1655,7 +1732,9 @@ window.__ModuleLoader__.load({
|
|
|
1655
1732
|
const FEATURE_GROUPS = [
|
|
1656
1733
|
{ key: "group.core", items: ["autoInject", "autoSummarize", "hotMemoryEnabled", "memoryQualityFilter.enabled", "llmAudit.enabled"] },
|
|
1657
1734
|
{ key: "group.enhance", items: ["entityExtractionEnabled", "codingRetrospect", "rerankEnabled", "searchSemanticDedup", "bm25SearchEnabled"] },
|
|
1658
|
-
{ key: "group.dream", items: ["autoDream", "sleepModeEnabled"] }
|
|
1735
|
+
{ key: "group.dream", items: ["autoDream", "sleepModeEnabled"] },
|
|
1736
|
+
// v0.8.0 A4(issue #17):作用域隔离组——标注总开关 + 严格硬过滤。
|
|
1737
|
+
{ key: "group.scope", items: ["scopeEnabled", "strictScope"] }
|
|
1659
1738
|
];
|
|
1660
1739
|
const FEATURE_ADVANCED_BOOLS = ["hybridInject", "selectiveInjectEnabled", "adaptiveThresholdEnabled", "reflectionUpdateEnabled", "reflectionFailureTracking", "conflictFreezeEnabled", "trustEpistemicWeighting"];
|
|
1661
1740
|
// 字符串键(blur/Enter 提交,空串合法 = 跟随默认):巩固模型与语义
|
|
@@ -2856,6 +2935,62 @@ window.__ModuleLoader__.load({
|
|
|
2856
2935
|
);
|
|
2857
2936
|
}
|
|
2858
2937
|
|
|
2938
|
+
// --- 冲突集中处理队列(v0.8.0,状态页)---------------------------------
|
|
2939
|
+
// 此前冻结冲突只有计数与散落徽章,resolveConflictPending 无任何调用方——
|
|
2940
|
+
// 这里是第一处理入口:并排展示双方内容 + reason,人工选保留方后走
|
|
2941
|
+
// POST /conflicts/resolve(service 端按 dream 非冻结 conflict 同款处置)。
|
|
2942
|
+
// 队列为空时整块不渲染(不打扰无冲突实例)。
|
|
2943
|
+
function ConflictsQueue({ t }) {
|
|
2944
|
+
const [items, setItems] = useState(null);
|
|
2945
|
+
const [busy, setBusy] = useState(false);
|
|
2946
|
+
const load = () => {
|
|
2947
|
+
apiFetch("/api/dsh-mneme/conflicts")
|
|
2948
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2949
|
+
.then((d) => setItems(d.items || []))
|
|
2950
|
+
.catch(() => setItems([]));
|
|
2951
|
+
};
|
|
2952
|
+
useEffect(() => { load(); }, []);
|
|
2953
|
+
if (!items || items.length === 0) return null;
|
|
2954
|
+
const resolve = (id, winner) => {
|
|
2955
|
+
setBusy(true);
|
|
2956
|
+
apiFetch("/api/dsh-mneme/conflicts/resolve", {
|
|
2957
|
+
method: "POST",
|
|
2958
|
+
headers: { "Content-Type": "application/json" },
|
|
2959
|
+
body: JSON.stringify({ id, winner, apply: winner !== null })
|
|
2960
|
+
})
|
|
2961
|
+
.then((res) => { if (!res.ok) throw new Error("http"); return res.json(); })
|
|
2962
|
+
.then(() => load())
|
|
2963
|
+
.catch(() => {})
|
|
2964
|
+
.finally(() => setBusy(false));
|
|
2965
|
+
};
|
|
2966
|
+
const side = (s, label) => h("div", { className: "mneme-conflict-side" },
|
|
2967
|
+
h("div", { className: "mneme-conflict-sidelabel" }, label),
|
|
2968
|
+
s.missing
|
|
2969
|
+
? h("div", { className: "mneme-conflict-missing" }, t("memory.status.conflictQueue.missing"))
|
|
2970
|
+
: h(react.Fragment, null,
|
|
2971
|
+
h("div", { className: "mneme-conflict-sidetitle", title: s.title }, s.title || "…"),
|
|
2972
|
+
h("div", { className: "mneme-conflict-snippet" }, (s.content || "").slice(0, 140)),
|
|
2973
|
+
s.archived && h("span", { className: "mneme-badge mneme-badge--archived" }, t("memory.explorer.archivedBadge")))
|
|
2974
|
+
);
|
|
2975
|
+
return h("div", { className: "mneme-conflictq" },
|
|
2976
|
+
h("div", { className: "mneme-conflictq-head" },
|
|
2977
|
+
h("span", { className: "mneme-xcount" }, t("memory.status.conflicts")),
|
|
2978
|
+
h("button", { className: "mneme-footbtn", onClick: () => load() },
|
|
2979
|
+
h(Icon, { name: "refresh", size: 12 }), t("memory.status.conflictQueue.refresh"))),
|
|
2980
|
+
items.map((it) => h("div", { key: it.id, className: "mneme-conflict-item" },
|
|
2981
|
+
it.reason && h("div", { className: "mneme-conflict-reason" },
|
|
2982
|
+
`${t("memory.status.conflictQueue.reason")}: ${it.reason}`),
|
|
2983
|
+
h("div", { className: "mneme-conflict-pair" },
|
|
2984
|
+
side(it.memory_a, t("memory.status.conflictQueue.sideA")),
|
|
2985
|
+
side(it.memory_b, t("memory.status.conflictQueue.sideB"))),
|
|
2986
|
+
h("div", { className: "mneme-conflict-actions" },
|
|
2987
|
+
h("span", { className: "mneme-conflict-hint" }, t("memory.status.conflictQueue.applyHint")),
|
|
2988
|
+
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, "a") }, t("memory.status.conflictQueue.keepA")),
|
|
2989
|
+
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, "b") }, t("memory.status.conflictQueue.keepB")),
|
|
2990
|
+
h("button", { className: "mneme-footbtn", disabled: busy, onClick: () => resolve(it.id, null) }, t("memory.status.conflictQueue.markReviewed"))))
|
|
2991
|
+
));
|
|
2992
|
+
}
|
|
2993
|
+
|
|
2859
2994
|
// --- 状态页工作台:让用户切实看见插件在干活 ---
|
|
2860
2995
|
// 活动流合并 llm_audit 里 autoDream/autoSummarize 的后台调用(状态、
|
|
2861
2996
|
// token、沉淀条数);沉淀记忆列表用 audit 行携带的 related_memory_ids
|
|
@@ -3046,6 +3181,7 @@ window.__ModuleLoader__.load({
|
|
|
3046
3181
|
h(DreamStatusCards, { t }),
|
|
3047
3182
|
h(HeatStatusCard, { t })
|
|
3048
3183
|
),
|
|
3184
|
+
h(ConflictsQueue, { t }),
|
|
3049
3185
|
h(WorkbenchSection, { t, onBrowse })
|
|
3050
3186
|
);
|
|
3051
3187
|
}
|
|
@@ -3139,6 +3275,18 @@ window.__ModuleLoader__.load({
|
|
|
3139
3275
|
memory.source && h(react.Fragment, null,
|
|
3140
3276
|
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.source")),
|
|
3141
3277
|
h("span", { className: "mneme-dmetaval", title: memory.source }, memory.source)),
|
|
3278
|
+
memory.occurred_at && h(react.Fragment, null,
|
|
3279
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.occurred")),
|
|
3280
|
+
h("span", { className: "mneme-dmetaval", title: memory.occurred_at }, formatDateShort(memory.occurred_at))),
|
|
3281
|
+
memory.agent_scope && h(react.Fragment, null,
|
|
3282
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.agentScope")),
|
|
3283
|
+
h("span", { className: "mneme-dmetaval", title: memory.agent_scope }, memory.agent_scope)),
|
|
3284
|
+
memory.workspace_scope && h(react.Fragment, null,
|
|
3285
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.workspaceScope")),
|
|
3286
|
+
h("span", { className: "mneme-dmetaval", title: memory.workspace_scope }, memory.workspace_scope)),
|
|
3287
|
+
memory.sensitivity && h(react.Fragment, null,
|
|
3288
|
+
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.sensitivity")),
|
|
3289
|
+
h("span", { className: "mneme-dmetaval", title: memory.sensitivity }, memory.sensitivity)),
|
|
3142
3290
|
memory.quality_score != null && h(react.Fragment, null,
|
|
3143
3291
|
h("span", { className: "mneme-dmetakey" }, t("memory.explorer.detail.quality")),
|
|
3144
3292
|
h("span", { className: "mneme-dmetaval" }, String(memory.quality_score))),
|
|
@@ -3800,6 +3948,7 @@ window.__ModuleLoader__.load({
|
|
|
3800
3948
|
h("div", { className: "mneme-cardfoot" },
|
|
3801
3949
|
h(ImportanceStars, { value: m.importance || 0, size: 12 }),
|
|
3802
3950
|
h(HeatBadge, { value: m.heat }),
|
|
3951
|
+
h(ScopeBadge, { m, t }),
|
|
3803
3952
|
m.source && h("span", { className: "mneme-cardsrc", title: m.source }, m.source)
|
|
3804
3953
|
)
|
|
3805
3954
|
)),
|
|
@@ -3861,7 +4010,8 @@ window.__ModuleLoader__.load({
|
|
|
3861
4010
|
},
|
|
3862
4011
|
h("span", { className: "mneme-xdot", style: { color: memoryTypeColor(m.type) }, title: typeLabel(t, m.type), "aria-hidden": "true" }),
|
|
3863
4012
|
h("span", { className: "mneme-xtime" }, time),
|
|
3864
|
-
h("span", { className: "mneme-xname" }, m.title || m.content?.slice(0, 40))
|
|
4013
|
+
h("span", { className: "mneme-xname" }, m.title || m.content?.slice(0, 40)),
|
|
4014
|
+
h(ScopeBadge, { m, t })
|
|
3865
4015
|
);
|
|
3866
4016
|
})
|
|
3867
4017
|
))
|
package/lib/commands.js
CHANGED
|
@@ -1,11 +1,16 @@
|
|
|
1
|
+
import { createUserMessage } from "@deepseek-ai/dsh-llm";
|
|
1
2
|
import { STR } from "./lang.js";
|
|
2
3
|
|
|
3
4
|
// Custom slash-command manager: keeps the DSH command registry in sync with
|
|
4
5
|
// user-defined commands persisted in SQLite. Commands are registered on boot
|
|
5
6
|
// and (re)registered on add/remove through the API.
|
|
6
7
|
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
8
|
+
// Per the DSH command contract, a handler's CommandResult text is only a UI
|
|
9
|
+
// settlement notice and never reaches the model. To actually drive the agent,
|
|
10
|
+
// the handler submits the user-authored instruction itself as a user followup
|
|
11
|
+
// message on the receiving agent (same pattern as the official /goal command).
|
|
12
|
+
// The notice just acknowledges the submission; an optional rawInput suffix
|
|
13
|
+
// typed after the command is appended for the model.
|
|
9
14
|
|
|
10
15
|
export function createCommandManager({ ctx, settings, logger, language = "zh" }) {
|
|
11
16
|
const registered = new Map(); // name -> disposer
|
|
@@ -18,7 +23,8 @@ export function createCommandManager({ ctx, settings, logger, language = "zh" })
|
|
|
18
23
|
name: command.name,
|
|
19
24
|
// 缺省描述随实例语言(命令描述进命令注册表,对 LLM 可见)。
|
|
20
25
|
description: command.description || STR.commandFallbackDesc[language](command.name),
|
|
21
|
-
|
|
26
|
+
input: { hint: STR.commandInputHint[language] },
|
|
27
|
+
handler: (invocation) => submit(command, invocation)
|
|
22
28
|
});
|
|
23
29
|
} catch (error) {
|
|
24
30
|
logger?.warn?.(`dsh-mneme: failed to register command /${command.name}: ${String(error)}`);
|
|
@@ -27,6 +33,27 @@ export function createCommandManager({ ctx, settings, logger, language = "zh" })
|
|
|
27
33
|
registered.set(command.name, dispose);
|
|
28
34
|
}
|
|
29
35
|
|
|
36
|
+
/** Submit the instruction (plus optional rawInput suffix) to the receiving agent. */
|
|
37
|
+
function submit(command, invocation) {
|
|
38
|
+
const suffix = typeof invocation?.rawInput === "string" ? invocation.rawInput.trim() : "";
|
|
39
|
+
const text = suffix ? `${command.instruction}\n\n${suffix}` : command.instruction;
|
|
40
|
+
const agent = invocation?.agent;
|
|
41
|
+
if (typeof agent?.followup !== "function") {
|
|
42
|
+
// 兜底:宿主未提供接收 agent 时退回旧行为,至少把指令展示在 notice 里。
|
|
43
|
+
return { kind: "success", text };
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
agent.followup(createUserMessage({
|
|
47
|
+
content: [{ type: "text", text }],
|
|
48
|
+
source: { kind: "user" }
|
|
49
|
+
}));
|
|
50
|
+
} catch (error) {
|
|
51
|
+
logger?.warn?.(`dsh-mneme: failed to submit /${command.name} to agent: ${String(error)}`);
|
|
52
|
+
return { kind: "error", text: STR.commandSubmitFailed[language](command.name) };
|
|
53
|
+
}
|
|
54
|
+
return { kind: "success", text: STR.commandSubmitted[language](command.name) };
|
|
55
|
+
}
|
|
56
|
+
|
|
30
57
|
function unregisterOne(name) {
|
|
31
58
|
const dispose = registered.get(name);
|
|
32
59
|
if (dispose) {
|
package/lib/config.js
CHANGED
|
@@ -58,6 +58,11 @@ export const Config = z.object({
|
|
|
58
58
|
// 编码记忆注入加权系数:编码任务时对 rejected_solution/pitfall/constraint
|
|
59
59
|
// 记忆的 importance 乘以该系数排序,让编码记忆在编码场景更靠前。
|
|
60
60
|
codingBoostFactor: z.number().min(1).max(5).default(2),
|
|
61
|
+
// 注入边界花括号转义(issue #162,v0.7.4 曾修后被 v0.7.11 重构误删):默认开启,
|
|
62
|
+
// 在 memory / 短期上下文 / 用户设置三处注入出口对连续 2+ 个花括号插 `\`
|
|
63
|
+
// (`{{a}}`→`{\{a\}\}`),让 DSH 的 interpolate() 不再扫到非法变量名而 throw。
|
|
64
|
+
// 关闭时按原样透传。
|
|
65
|
+
escapePromptVariables: z.boolean().default(true),
|
|
61
66
|
autoDream: z.boolean().default(true),
|
|
62
67
|
dreamThresholdCount: z.natural().min(1).max(1000).default(10),
|
|
63
68
|
dreamThresholdChars: z.natural().min(100).max(100000).default(5000),
|
|
@@ -438,6 +443,22 @@ export const Config = z.object({
|
|
|
438
443
|
recallRecordDefault: z.boolean().default(true),
|
|
439
444
|
// recall_runs 滚动清理保留天数。
|
|
440
445
|
recallRetentionDays: z.natural().min(1).max(3650).default(90),
|
|
446
|
+
|
|
447
|
+
// --- scope: v0.8.0 A1 存储层(issue #17)--------------------------------
|
|
448
|
+
// 总开关默认关:关闭时写入不标注 scope、去重维持 (type, title) 现状,行为
|
|
449
|
+
// 逐字节不变。开启后 memory_save 写入 agent_scope(session header 的
|
|
450
|
+
// agentPreset)与 workspace_scope(registry 反查 canonical path,header.cwd
|
|
451
|
+
// 兜底,取不到 NULL——解析绝不阻塞写入),去重键扩展为 (type, title,
|
|
452
|
+
// agent_scope, workspace_scope, sensitivity)。检索侧加权/过滤在 A2/A3 落地。
|
|
453
|
+
// 也走 feature_flags(FEATURE_FLAG_BOOLEANS 白名单),面板可启停=线上回滚开关。
|
|
454
|
+
scopeEnabled: z.boolean().default(false),
|
|
455
|
+
// strictScope(A3):硬过滤模式。关闭=A2 软隔离(他 scope 降权保留可见);
|
|
456
|
+
// 开启后检索/注入/list/get 按 issue #17 四象限可见性公式硬过滤——带他 scope
|
|
457
|
+
// 的记忆完全不可见,未标注(NULL)恒可见;当前会话某维度解析不到时该维度
|
|
458
|
+
// 带标注的记忆一律不可见(fail-closed:身份不明只见全局)。依赖 scopeEnabled
|
|
459
|
+
// 打开才有意义(没有写入标注就无可过滤维度),但独立成键:可先开标注积累
|
|
460
|
+
// 数据、观察 A2 加权质量后再切硬过滤。
|
|
461
|
+
strictScope: z.boolean().default(false),
|
|
441
462
|
});
|
|
442
463
|
|
|
443
464
|
// Fields forced to false by the light-mode preset. Everything not listed here
|