@kenz1117/dsh-engram 0.4.3 → 0.4.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 KenZ (kenz1117)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.en.md ADDED
@@ -0,0 +1,135 @@
1
+ <div align="center">
2
+
3
+ # dsh-engram
4
+
5
+ <p align="center">Cross-session long-term memory for DeepSeek Harness — the agent remembers your preferences, project conventions, and factual history across sessions and projects, and keeps evolving with use (capture → reinforce → distill → decay). Pure TypeScript: no external processes, no Python dependency.</p>
6
+
7
+ <p align="center">
8
+ <a href="https://github.com/kenz1117/dsh-engram/blob/main/LICENSE"><img alt="GitHub license" src="https://img.shields.io/github/license/kenz1117/dsh-engram"></a>
9
+ <a href="https://github.com/kenz1117/dsh-engram"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/kenz1117/dsh-engram"></a>
10
+ <a href="https://www.npmjs.com/package/@kenz1117/dsh-engram"><img alt="npm version" src="https://img.shields.io/npm/v/@kenz1117/dsh-engram"></a>
11
+ <a href="https://www.npmjs.com/package/@kenz1117/dsh-engram"><img alt="npm downloads" src="https://img.shields.io/npm/dm/@kenz1117/dsh-engram"></a>
12
+ <a href="https://github.com/kenz1117/dsh-engram/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/kenz1117/dsh-engram"></a>
13
+ <a href="https://github.com/kenz1117/dsh-engram/graphs/contributors"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/kenz1117/dsh-engram"></a>
14
+ <a href="https://awesome-dsh-plugin.com"><img alt="Awesome DSH Plugin" src="https://awesome-dsh-plugin.com/badge.svg"></a>
15
+ </p>
16
+
17
+ [中文](README.md) | English
18
+
19
+ </div>
20
+
21
+ ---
22
+
23
+ ## Quick Start
24
+
25
+ ```sh
26
+ dsh plugin --profile web add @kenz1117/dsh-engram
27
+ ```
28
+
29
+ Zero configuration after install (stores and model cache default to `~/.dsh/engram`; profile injection on, automatic capture off).
30
+
31
+ ## Features
32
+
33
+ - **Cross-session memory**: a user memory profile is injected at session start (up to 8 entries, configurable), so the agent naturally knows who you are and what you are building; tools recall facts across sessions.
34
+ - **Dual-scope stores**: `user.db` shared globally; `project-<cwd>.db` isolated per working directory — personal preferences follow the person, project conventions follow the repo.
35
+ - **Hybrid retrieval**: FTS5 (unicode61 + Chinese 2-gram pre-tokenization) fused with local vectors (`Xenova/bge-small-zh-v1.5`, 512-dim, q8) via RRF, plus one-hop expansion over relation edges; the embedding model runs offline, and a failed download degrades to keyword-only retrieval with an explicit marker.
36
+ - **Knowledge flywheel**: capture/save → contradiction candidates (high-similarity neighbors create `contradicts` edges on write, for the model/user to adjudicate) → hit reinforcement (confidence +0.05) → distillation (topic clusters merge into higher-level rules, supersedes chains, confidence inheritance) → decay (low-importance, long-unaccessed entries archive; restorable).
37
+ - **Automatic capture** (when `ingest` is enabled): each new turn's first step extracts candidate facts from the previous turn out of the session log, written with low confidence and deduplicated by embedding — memories accumulate without you saying "remember this".
38
+ - **Provenance audit**: every memory records its source session, turn, and event seq; `engram_review` traces the full source chain, supersede chain, contradictions, and operation log; all writes/edits/forgets/distills/decays land in the operation log table.
39
+ - **Web management panel**: a "Memory Library" tab in Settings — stat cards, filter by status/kind/content, inline detail and edit (through the supersede chain), forget/restore, Markdown/JSON export. UI copy is bilingual zh/en and follows the host language setting live.
40
+ - **Portable data**: `engram_export` exports Markdown / JSON files in one step.
41
+
42
+ ## Tools (9, narrow parameters)
43
+
44
+ | Tool | Purpose |
45
+ |---|---|
46
+ | `engram_save` | Save (automatic contradiction-candidate detection when embeddings are available) |
47
+ | `engram_search` | Hybrid semantic + keyword retrieval (hits reinforce confidence) |
48
+ | `engram_timeline` | Timeline browsing |
49
+ | `engram_update` | Correct an entry (supersedes chain) |
50
+ | `engram_forget` | Forget (soft-delete, restorable) |
51
+ | `engram_review` | Audit one entry: source chain, supersede chain, contradictions, operation log |
52
+ | `engram_stats` | Whole-store statistics and signal ratio |
53
+ | `engram_export` | Export Markdown / JSON files (data portability) |
54
+ | `engram_distill` | Distill: merge same-topic clusters into higher-level rules (LLM) |
55
+
56
+ ## Configuration
57
+
58
+ Optional configuration (cordis.yml):
59
+
60
+ ```yaml
61
+ - id: dsh-engram
62
+ name: '@kenz1117/dsh-engram'
63
+ config:
64
+ dbDir: '~/.dsh/engram' # store and model-cache root directory
65
+ injectProfile: true # inject the user memory profile at session start
66
+ profileTopN: 8 # injection cap (1-64)
67
+ modelCacheDir: '~/.dsh/engram/models' # embedding model cache directory
68
+ hfEndpoint: 'https://huggingface.co' # model download endpoint; set a mirror behind restricted networks
69
+ ingest: 'off' # automatic capture: off | light (user messages only, ≤2/turn) | eager (assistant messages too, ≤5/turn)
70
+ # provider and model must be given as a pair: aux-LLM route override for capture/distill (parsed from the session log by default)
71
+ # provider: 'deepseek'
72
+ # model: 'deepseek-v4-flash'
73
+ decayAfterDays: 30 # decay: last access older than this many days
74
+ decayImportanceBelow: 0.3 # decay: and importance below this → archive (restorable)
75
+ ```
76
+
77
+ ## How It Works
78
+
79
+ The plugin has a host half (Node) and a browser half (React):
80
+
81
+ ```
82
+ Session agent Host half (Node)
83
+ │ │
84
+ ├─ every turn, first step ◀─────────────── ├─ user memory profile injection (plugin-source user snapshot)
85
+ ├─ engram_save / search / review … ──────▶ ├─ SQLite dual stores (user.db / project-<cwd>.db)
86
+ │ ├─ FTS5 keyword track + local vector track, RRF fusion
87
+ ├─ engram_distill ───────────────────────▶ ├─ aux-LLM distillation (cluster merge → supersedes chain)
88
+ │ └─ automatic capture: session log → candidate facts (ingest on)
89
+ └─ Settings "Memory Library" tab ◀──────── ─── loopback API /api/engram/* (writes verify loopback Origin)
90
+ ```
91
+
92
+ - **Dual-scope stores**: `user.db` shared globally; `project-<cwd>.db` isolated per working directory.
93
+ - **Automatic capture** (when `ingest` is on): each new turn's first step extracts candidate facts from the previous turn out of the session log (the read source is the session log; aux-call request auditing goes to the plugin's own operation log — unknown events are never appended to the session log). Candidates are written with low confidence and deduplicated by embedding.
94
+ - **Provenance chain**: every memory records its source session, turn, and event seq, fully traceable via `engram_review`; the operation log table records every write/edit/forget/distill/decay.
95
+ - **Offline embeddings**: the model downloads once (q8, ~50MB; mirror endpoint configurable), then runs fully offline; on failure the plugin keeps working and retrieval degrades to keyword-only with an explicit marker.
96
+ - **UI localization**: the client half registers zh/en dictionaries through the host locale service and follows the host language setting live; data-level enums (status/kind) are mapped only in the display layer and stay English in storage.
97
+
98
+ ## Web Management Panel (Settings → "Memory Library" tab)
99
+
100
+ On profiles with a webServer (web, etc.), a "Memory Library" tab appears in **Settings** automatically (registered through the `settings.section` slot; the client half is a React component loaded from `lib/client.js` through the host module table): stat cards, filter by status/kind/content, inline detail and edit (through the supersede chain), forget/restore, Markdown/JSON export. Data flows through the loopback API `/api/engram/*` (writes verify the loopback Origin). Profiles without a webServer (headless, etc.) skip the panel; every other capability is unaffected.
101
+
102
+ ## Development
103
+
104
+ ```sh
105
+ pnpm install # postinstall symlinks @deepseek-ai/* peers from ../deepseek-harness (run pnpm install && pnpm run build in the harness repo first)
106
+ pnpm test # unit + composition tests; real-embedding e2e runs when ENGRAM_E2E=1 (HF_ENDPOINT configurable) and the network allows
107
+ pnpm typecheck
108
+ pnpm bundle
109
+ ```
110
+
111
+ ## Model Experience
112
+
113
+ ### Request context and condition
114
+
115
+ #### What the model sees
116
+
117
+ Each turn's first step appends a plugin-source user snapshot: `User memory profile (dsh-engram, cross-session):` followed by the user-scope memory list (up to 8 by default; `injectProfile: false` disables). Tool results are plain text lines (with `id=`, scope/kind annotations, contradiction-candidate hints, and degradation notes). Automatic capture and distillation each make one aux-LLM call (billed independently of the main conversation path, with purpose attribution).
118
+
119
+ #### Token effect
120
+
121
+ Profile injection is a conditional fixed cost (entries × content length); the tool schemas are a standing cost (9 narrow-parameter tools).
122
+
123
+ #### KV Cache effect
124
+
125
+ Profile text changes as the memory store changes — changes only land at turn boundaries in new sessions or after memory updates; within a session the prefix stays stable while the injected content is unchanged; tool schemas are constant and never affect the prefix.
126
+
127
+ ## Known Limitations and Deferred Work
128
+
129
+ - **Last-turn blind spot in automatic capture** — capture is triggered by the next turn's first step, so a session's final turn is never captured; a session-end event hook is future work.
130
+ - **No LLM adjudication of contradiction candidates** — writes only report candidates by vector similarity (≥0.88) and create edges; semantic-contradiction confirmation is left to model/user adjudication and distillation.
131
+ - **Memories written during embedder degradation have no vectors** — memories written before the model is ready do not participate in the semantic track; after semantics come online run `pnpm backfill` once to backfill existing vectors (after `pnpm build` has warmed the model cache; `HF_ENDPOINT` configurable).
132
+
133
+ ## License
134
+
135
+ [MIT](LICENSE) © 2026 KenZ (kenz1117)
package/README.md CHANGED
@@ -1,16 +1,61 @@
1
- # @kenz1117/dsh-engram
1
+ <div align="center">
2
2
 
3
- DeepSeek Harness 的跨会话长期记忆插件:Agent 在会话与项目之间记住用户偏好、项目约定与经历事实,并随使用持续演化(摄取 → 强化 → 蒸馏 → 衰减)。纯 TypeScript,零外部进程、零 Python 依赖。
3
+ # dsh-engram
4
4
 
5
- [English](README.md) | 中文
5
+ <p align="center">DeepSeek Harness 跨会话长期记忆插件 — Agent 在会话与项目之间记住用户偏好、项目约定与经历事实,并随使用持续演化(摄取 → 强化 → 蒸馏 → 衰减)。纯 TypeScript,零外部进程、零 Python 依赖。</p>
6
6
 
7
- ## 使用本包
7
+ <p align="center">
8
+ <a href="https://github.com/kenz1117/dsh-engram/blob/main/LICENSE"><img alt="GitHub license" src="https://img.shields.io/github/license/kenz1117/dsh-engram"></a>
9
+ <a href="https://github.com/kenz1117/dsh-engram"><img alt="GitHub last commit" src="https://img.shields.io/github/last-commit/kenz1117/dsh-engram"></a>
10
+ <a href="https://www.npmjs.com/package/@kenz1117/dsh-engram"><img alt="npm version" src="https://img.shields.io/npm/v/@kenz1117/dsh-engram"></a>
11
+ <a href="https://www.npmjs.com/package/@kenz1117/dsh-engram"><img alt="npm downloads" src="https://img.shields.io/npm/dm/@kenz1117/dsh-engram"></a>
12
+ <a href="https://github.com/kenz1117/dsh-engram/issues"><img alt="GitHub issues" src="https://img.shields.io/github/issues/kenz1117/dsh-engram"></a>
13
+ <a href="https://github.com/kenz1117/dsh-engram/graphs/contributors"><img alt="GitHub contributors" src="https://img.shields.io/github/contributors/kenz1117/dsh-engram"></a>
14
+ <a href="https://awesome-dsh-plugin.com"><img alt="Awesome DSH Plugin" src="https://awesome-dsh-plugin.com/badge.svg"></a>
15
+ </p>
16
+
17
+ 中文 | [English](README.en.md)
18
+
19
+ </div>
20
+
21
+ ---
22
+
23
+ ## 快速开始
8
24
 
9
25
  ```sh
10
26
  dsh plugin --profile web add @kenz1117/dsh-engram
11
27
  ```
12
28
 
13
- 安装后无需配置即可使用(默认分库与模型缓存在 `~/.dsh/engram`,画像注入开启,自动摄取关闭)。可选配置(cordis.yml):
29
+ 安装后无需配置即可使用(默认分库与模型缓存在 `~/.dsh/engram`,画像注入开启,自动摄取关闭)。
30
+
31
+ ## 特性
32
+
33
+ - **跨会话记忆**:会话开始注入用户画像摘要(至多 8 条,可配),Agent 天然"记得"你是谁、在做什么;工具检索跨会话召回历史事实。
34
+ - **双层分库**:`user.db` 全局共享;`project-<cwd>.db` 按工作目录隔离——个人偏好跟人走,项目约定跟仓库走。
35
+ - **混合检索**:FTS5(unicode61 + 中文 2-gram 预切词)与本地向量(`Xenova/bge-small-zh-v1.5`,512 维,q8)RRF 融合 + 关系边一跳扩展;嵌入模型离线运行,下载失败自动降级纯关键词并显式标记。
36
+ - **知识飞轮**:摄取/保存 → 矛盾候选(写入时高相似近邻建 `contradicts` 边并报告,模型/用户裁决)→ 命中强化(confidence +0.05)→ 蒸馏(同主题簇合并为高层规律、supersedes 取代链、置信度继承)→ 衰减(低重要性且长期未访问归档,可恢复)。
37
+ - **自动摄取**(`ingest` 配置开启时):新一轮第一步从会话日志提取上一轮的候选事实,低 confidence 写入并按嵌入去重——不说"记住"也能攒记忆。
38
+ - **来源审计**:每条记忆记录来源会话、轮次与事件 seq,`engram_review` 完整回查来源链、取代链、矛盾与操作日志;全部写入/修改/遗忘/蒸馏/衰减入操作日志表。
39
+ - **Web 管理面板**:设置页「记忆库」tab——统计卡片、按状态/种类/内容过滤、行内详情与编辑(走取代链)、遗忘/恢复、导出 Markdown/JSON。界面文案中英双语,跟随宿主语言设置实时切换。
40
+ - **数据可携带**:`engram_export` 一键导出 Markdown / JSON 文件。
41
+
42
+ ## 工具(9 个,窄参数)
43
+
44
+ | 工具 | 作用 |
45
+ |---|---|
46
+ | `engram_save` | 保存(嵌入可用时自动做矛盾候选检测) |
47
+ | `engram_search` | 语义 + 关键词混合检索(命中强化置信度) |
48
+ | `engram_timeline` | 时间线浏览 |
49
+ | `engram_update` | 修正(supersedes 取代链) |
50
+ | `engram_forget` | 遗忘(软删可恢复) |
51
+ | `engram_review` | 审计单条:来源链、取代链、矛盾、操作日志 |
52
+ | `engram_stats` | 全库统计与信噪比 |
53
+ | `engram_export` | 导出 Markdown / JSON 文件(数据可携带) |
54
+ | `engram_distill` | 蒸馏:同主题簇合并为高层规律(LLM) |
55
+
56
+ ## 配置
57
+
58
+ 可选配置(cordis.yml):
14
59
 
15
60
  ```yaml
16
61
  - id: dsh-engram
@@ -29,32 +74,30 @@ dsh plugin --profile web add @kenz1117/dsh-engram
29
74
  decayImportanceBelow: 0.3 # 衰减:且 importance 低于该值 → 归档(可恢复)
30
75
  ```
31
76
 
32
- 工具(9 个,窄参数):
77
+ ## 工作原理
33
78
 
34
- | 工具 | 作用 |
35
- |---|---|
36
- | `engram_save` | 保存(嵌入可用时自动做矛盾候选检测) |
37
- | `engram_search` | 语义 + 关键词混合检索(命中强化置信度) |
38
- | `engram_timeline` | 时间线浏览 |
39
- | `engram_update` | 修正(supersedes 取代链) |
40
- | `engram_forget` | 遗忘(软删可恢复) |
41
- | `engram_review` | 审计单条:来源链、取代链、矛盾、操作日志 |
42
- | `engram_stats` | 全库统计与信噪比 |
43
- | `engram_export` | 导出 Markdown / JSON 文件(数据可携带) |
44
- | `engram_distill` | 蒸馏:同主题簇合并为高层规律(LLM) |
79
+ 插件由宿主半(Node)与浏览器半(React)组成:
45
80
 
46
- ## Web 管理面板(设置页「记忆库」tab)
47
-
48
- 宿主带 webServer 的 profile(web 等)会在**设置页**自动出现「记忆库」tab(经 `settings.section` 槽位注册,client 半为 React 组件、随 `lib/client.js` 由宿主模块表装载):统计卡片、按状态/种类/内容过滤、编辑(走取代链)、遗忘/恢复、导出 Markdown/JSON 下载。数据经回环 API `/api/engram/*`(写操作校验回环 Origin)。headless 等无 webServer 的组合不挂载,其余能力不受影响。
49
-
50
- ## 理解实现
81
+ ```
82
+ 会话 Agent 宿主半(Node)
83
+ │ │
84
+ ├─ 每轮第一步 ◀─────────────────────────── ├─ 用户画像快照注入(plugin 来源 user 快照)
85
+ ├─ engram_save / search / review … ──────▶ ├─ SQLite 双库(user.db / project-<cwd>.db)
86
+ │ ├─ FTS5 关键词道 + 本地向量道 RRF 融合
87
+ ├─ engram_distill ───────────────────────▶ ├─ 辅助 LLM 蒸馏(簇合并 → supersedes 链)
88
+ │ └─ 自动摄取:会话日志 → 候选事实(ingest 开启时)
89
+ └─ 设置页「记忆库」tab ◀────────────────── ─── 回环 API /api/engram/*(写操作校验回环 Origin)
90
+ ```
51
91
 
52
92
  - **双层分库**:`user.db` 全局共享;`project-<cwd>.db` 按工作目录隔离。
53
- - **混合检索**:FTS5(unicode61 + 中文 2-gram 预切词)与本地向量(`Xenova/bge-small-zh-v1.5`,512 维,q8)RRF 融合 + 关系边一跳扩展。
54
- - **知识飞轮**:摄取/保存 → 矛盾候选(写入时高相似近邻建 `contradicts` 边并报告,模型/用户裁决)→ 命中强化(confidence +0.05)→ 蒸馏(簇合并、supersedes 链、置信度继承)→ 衰减(低重要性且长期未访问归档,可恢复)。
55
- - **自动摄取**(`ingest` 开启时):新一轮第一步从会话日志提取上一轮的候选事实(读取源是日志,辅助调用请求本身也 append 到日志)。候选以低 confidence 写入并按嵌入去重。
93
+ - **自动摄取**(`ingest` 开启时):新一轮第一步从会话日志提取上一轮的候选事实(读取源是会话日志;辅助调用的请求审计走插件自身操作日志,不向会话日志 append 未知事件)。候选以低 confidence 写入并按嵌入去重。
56
94
  - **来源链**:每条记忆记录来源会话、轮次与事件 seq,`engram_review` 可完整回查;操作日志表记录全部写入/修改/遗忘/蒸馏/衰减。
57
95
  - **嵌入离线**:模型首次使用需联网下载(q8 约 50MB,端点可配镜像),此后完全离线;失败时插件照常工作,检索降级纯关键词并显式标记。
96
+ - **界面本地化**:client 半经宿主 locale 服务注册 zh/en 词典,跟随宿主语言设置实时切换;状态/种类等数据枚举仅在显示层映射,存储值保持英文。
97
+
98
+ ## Web 管理面板(设置页「记忆库」tab)
99
+
100
+ 宿主带 webServer 的 profile(web 等)会在**设置页**自动出现「记忆库」tab(经 `settings.section` 槽位注册,client 半为 React 组件、随 `lib/client.js` 由宿主模块表装载):统计卡片、按状态/种类/内容过滤、行内详情与编辑(走取代链)、遗忘/恢复、导出 Markdown/JSON 下载。数据经回环 API `/api/engram/*`(写操作校验回环 Origin)。headless 等无 webServer 的组合不挂载,其余能力不受影响。
58
101
 
59
102
  ## 开发
60
103
 
@@ -71,11 +114,11 @@ pnpm bundle
71
114
 
72
115
  #### What the model sees
73
116
 
74
- 会话每轮第一步追加一条 plugin 来源的 user 快照:`User memory profile (dsh-engram, cross-session):` 加用户级记忆列表(默认至多 8 条,`injectProfile: false` 关闭)。工具调用结果为纯文本行列表(含 `id=`、scope/kind 标注、矛盾候选提示与降级说明)。自动摄取的辅助 LLM 请求以 `engram/ingest-request`、蒸馏以 `engram/distill-request` 事件记入会话日志。
117
+ 会话每轮第一步追加一条 plugin 来源的 user 快照:`User memory profile (dsh-engram, cross-session):` 加用户级记忆列表(默认至多 8 条,`injectProfile: false` 关闭)。工具调用结果为纯文本行列表(含 `id=`、scope/kind 标注、矛盾候选提示与降级说明)。自动摄取与蒸馏各产生一次辅助 LLM 调用(独立于主对话计费路径,带 purpose 归因)。
75
118
 
76
119
  #### Token effect
77
120
 
78
- 画像注入为条件性固定成本(条数 × 内容长度);工具 schema 为常驻成本(9 个窄参数工具);自动摄取与蒸馏各产生一次辅助 LLM 调用(独立于主对话计费路径,带 purpose 归因)。
121
+ 画像注入为条件性固定成本(条数 × 内容长度);工具 schema 为常驻成本(9 个窄参数工具)。
79
122
 
80
123
  #### KV Cache effect
81
124
 
@@ -86,4 +129,7 @@ pnpm bundle
86
129
  - **自动摄取的最后一轮盲区** —— 摄取由下一轮的第一步触发,会话最后一轮不摄取;会话结束事件钩子是后续工作。
87
130
  - **矛盾候选无 LLM 判定** —— 写入时仅按向量相似度(≥0.88)报告候选并建边,语义矛盾的确认留给模型/用户裁决与蒸馏。
88
131
  - **嵌入器降级期间的记忆无向量** —— 模型未就绪时写入的记忆不参与语义道;语义上线后跑一次 `pnpm backfill` 补算存量向量(`pnpm build` 的模型缓存就绪后执行,可经 `HF_ENDPOINT` 配镜像)。
89
- - **Web 管理面板未实现** —— 浏览、搜索、编辑、导出记忆库的 client 半在三期。
132
+
133
+ ## 许可证
134
+
135
+ [MIT](LICENSE) © 2026 KenZ (kenz1117)
package/lib/client.js CHANGED
@@ -1,3 +1,3 @@
1
- window.__ModuleLoader__.load({id:`@kenz1117/dsh-engram`,factory:e=>{var t={exports:{}},n=t.exports;Object.defineProperty(n,Symbol.toStringTag,{value:`Module`});let r=e("react"),i=e("react/jsx-runtime"),a=`@kenz1117/dsh-engram/panel.module.css`;if(typeof document<`u`&&document.querySelector(`style[data-plugin-css=`+JSON.stringify(a)+`]`)===null){let e=document.createElement(`style`);e.dataset.plugin=`@kenz1117/dsh-engram`,e.dataset.pluginCss=a,e.textContent=`.bWwFiW_panel{max-width:980px}.bWwFiW_toolbar{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:14px;display:flex}.bWwFiW_scopeTabs{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:flex-end;gap:18px;margin-right:auto;display:flex}.bWwFiW_scopeOn,.bWwFiW_scopeOff{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;padding:7px 1px 9px;font-size:13px;line-height:20px;position:relative}.bWwFiW_scopeOn,.bWwFiW_scopeOff:hover{color:var(--dsw-alias-label-primary)}.bWwFiW_scopeOn:after{background:var(--dsw-alias-brand-primary);content:"";border-radius:2px 2px 0 0;height:2px;position:absolute;bottom:-1px;left:0;right:0}.bWwFiW_button{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:8px;padding:5px 12px;font-size:13px}.bWwFiW_button:hover{border-color:var(--dsw-alias-brand-primary)}.bWwFiW_button:disabled{opacity:.45;cursor:default}.bWwFiW_primary{background:var(--dsw-alias-button-primary-fill);border-color:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.bWwFiW_cards{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:14px;display:grid}.bWwFiW_card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);text-align:left;cursor:pointer;color:var(--dsw-alias-label-primary);font:inherit;border-radius:10px;padding:10px 14px}.bWwFiW_card b{font-size:20px;display:block}.bWwFiW_card span{color:var(--dsw-alias-label-secondary);font-size:12px}.bWwFiW_filters{flex-wrap:wrap;gap:8px;margin-bottom:14px;display:flex}.bWwFiW_input{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 10px;font-size:13px}.bWwFiW_filters .bWwFiW_input{flex:1;min-width:160px}.bWwFiW_item{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:10px;padding:12px 14px}.bWwFiW_row1{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:6px;display:flex}.bWwFiW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-tsp-secondary);border-radius:99px;padding:1px 8px;font-size:11px}.bWwFiW_active{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.bWwFiW_archived{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.bWwFiW_forgotten{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.bWwFiW_content{white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-primary)}.bWwFiW_meta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:14px;margin-top:6px;font-size:12px;display:flex}.bWwFiW_ops{gap:8px;margin-top:8px;display:flex}.bWwFiW_pager{color:var(--dsw-alias-label-secondary);justify-content:center;align-items:center;gap:10px;margin-top:14px;display:flex}.bWwFiW_empty{text-align:center;color:var(--dsw-alias-label-secondary);padding:40px 0}.bWwFiW_expand{border-top:1px dashed var(--dsw-alias-border-l2);margin-top:10px;padding-top:10px}.bWwFiW_expandLoading{color:var(--dsw-alias-label-secondary);font-size:13px}.bWwFiW_editForm{flex-direction:column;display:flex}.bWwFiW_editForm .bWwFiW_input{box-sizing:border-box;width:100%}.bWwFiW_fieldLabel{color:var(--dsw-alias-label-secondary);margin:10px 0 4px;font-size:12px;display:block}.bWwFiW_modalFoot{justify-content:flex-end;gap:8px;margin-top:12px;display:flex}.bWwFiW_review{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;max-height:320px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:10px;font-size:12px;overflow:auto}`,document.head.appendChild(e)}var o={content:`bWwFiW_content`,empty:`bWwFiW_empty`,expand:`bWwFiW_expand`,badge:`bWwFiW_badge`,expandLoading:`bWwFiW_expandLoading`,editForm:`bWwFiW_editForm`,forgotten:`bWwFiW_forgotten`,input:`bWwFiW_input`,panel:`bWwFiW_panel`,toolbar:`bWwFiW_toolbar`,cards:`bWwFiW_cards`,primary:`bWwFiW_primary`,active:`bWwFiW_active`,ops:`bWwFiW_ops`,pager:`bWwFiW_pager`,review:`bWwFiW_review`,scopeOn:`bWwFiW_scopeOn`,archived:`bWwFiW_archived`,item:`bWwFiW_item`,card:`bWwFiW_card`,row1:`bWwFiW_row1`,scopeOff:`bWwFiW_scopeOff`,fieldLabel:`bWwFiW_fieldLabel`,filters:`bWwFiW_filters`,modalFoot:`bWwFiW_modalFoot`,button:`bWwFiW_button`,meta:`bWwFiW_meta`,scopeTabs:`bWwFiW_scopeTabs`};let s=[`fact`,`preference`,`decision`,`episode`,`skill`];async function c(e,t){let n=await fetch(`/api/engram/${e}`,t),r=await n.json();if(!n.ok)throw Error(r.error??`HTTP ${String(n.status)}`);return r}function l(e){return new Date(e).toLocaleString(`zh-CN`,{hour12:!1})}function u({recordId:e,scope:t}){let[n,a]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let n=!1;return c(`review?scope=${t}&id=${encodeURIComponent(e)}`).then(e=>{if(n)return;let t=e.record,r=[`内容: ${t.content}`,`属性: kind=${t.kind}, status=${t.status}, importance=${t.importance.toFixed(2)}, confidence=${t.confidence.toFixed(2)}, 访问 ${t.accessCount} 次`,`来源: ${t.sourceSessionId??`显式保存`}${t.sourceRound===null?``:` 第${String(t.sourceRound)}轮`}`];for(let t of[`supersededBy`,`supersedes`,`contradicts`,`related`]){let n=e[t];n!==void 0&&n.length>0&&r.push(`${t}: ${n.join(`, `)}`)}let i=e.operations;i!==void 0&&i.length>0&&(r.push(`最近操作:`),i.forEach(e=>r.push(` ${l(e.at)} ${e.op}${e.detail?` ${e.detail}`:``}`))),a(r.join(`
2
- `))}).catch(e=>{n||a(`加载失败:${e.message}`)}),()=>{n=!0}},[e,t]),n===null?(0,i.jsx)(`div`,{className:o.expandLoading,children:`加载中…`}):(0,i.jsx)(`pre`,{className:o.review,children:n})}function d({record:e,onClose:t,onSaved:n}){let[a,l]=(0,r.useState)(e.content),[u,d]=(0,r.useState)(e.kind),[f,p]=(0,r.useState)(e.importance);return(0,i.jsxs)(`div`,{className:o.editForm,children:[(0,i.jsx)(`label`,{className:o.fieldLabel,children:`内容`}),(0,i.jsx)(`textarea`,{className:o.input,rows:3,value:a,onChange:e=>l(e.target.value)}),(0,i.jsx)(`label`,{className:o.fieldLabel,children:`种类`}),(0,i.jsx)(`select`,{className:o.input,value:u,onChange:e=>d(e.target.value),children:s.map(e=>(0,i.jsx)(`option`,{value:e,children:e},e))}),(0,i.jsxs)(`label`,{className:o.fieldLabel,children:[`重要性 `,f.toFixed(2)]}),(0,i.jsx)(`input`,{type:`range`,min:0,max:1,step:.05,value:f,onChange:e=>p(Number(e.target.value))}),(0,i.jsxs)(`div`,{className:o.modalFoot,children:[(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:t,children:`取消`}),(0,i.jsx)(`button`,{type:`button`,className:`${o.button} ${o.primary}`,onClick:()=>{c(`update`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:e.id,scope:e.scope,content:a,kind:u,importance:f})}).then(n).catch(e=>{alert(e.message)})},children:`保存(旧条目归档)`})]})]})}function f(){let[e,t]=(0,r.useState)(`user`),[n,a]=(0,r.useState)(`all`),[f,p]=(0,r.useState)(`all`),[m,h]=(0,r.useState)(``),[g,_]=(0,r.useState)(0),[v,y]=(0,r.useState)(null),[b,x]=(0,r.useState)(null),[S,C]=(0,r.useState)(null),[w,T]=(0,r.useState)(null),[E,D]=(0,r.useState)(0),O=(0,r.useCallback)(()=>{D(e=>e+1)},[]);(0,r.useEffect)(()=>{let t=!1,r=new URLSearchParams({scope:e,status:n,kind:f,limit:`20`,offset:String(g)});return m!==``&&r.set(`q`,m),c(`list?${r.toString()}`).then(e=>{t||(y(e),C(null))}).catch(e=>{t||C(e.message)}),c(`stats`).then(e=>{t||x(e.parts)}).catch(()=>{}),()=>{t=!0}},[e,n,f,m,g,E]);let k=(e,t)=>{c(e,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t.id,scope:t.scope})}).then(O).catch(e=>{alert(e.message)})},A=(e,t)=>{T(n=>n!==null&&n.kind===e&&n.record.id===t.id?null:{kind:e,record:t})},j=Math.floor(g/20)+1,M=v===null?1:Math.max(1,Math.ceil(v.total/20));return(0,i.jsxs)(`div`,{className:o.panel,children:[(0,i.jsxs)(`div`,{className:o.toolbar,children:[(0,i.jsx)(`div`,{className:o.scopeTabs,children:[`user`,`project`].map(n=>(0,i.jsx)(`button`,{type:`button`,className:e===n?o.scopeOn:o.scopeOff,onClick:()=>{t(n),_(0),T(null)},children:n===`user`?`用户级`:`项目级`},n))}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:O,children:`刷新`}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{location.href=`/api/engram/export?scope=${e}&format=markdown`},children:`导出 MD`}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{location.href=`/api/engram/export?scope=${e}&format=json`},children:`导出 JSON`})]}),(0,i.jsx)(`div`,{className:o.cards,children:(b??[]).map(n=>(0,i.jsxs)(`button`,{type:`button`,className:o.card,style:n.scope===e?{borderColor:`var(--dsw-alias-brand-primary)`}:void 0,onClick:()=>{t(n.scope),_(0)},children:[(0,i.jsx)(`b`,{children:n.stats.total}),(0,i.jsxs)(`span`,{children:[n.scope,` · active `,n.stats.active,` · 信噪比 `,Math.round(n.stats.signalRatio*100),`%`]})]},n.scope))}),(0,i.jsxs)(`div`,{className:o.filters,children:[(0,i.jsxs)(`select`,{className:o.input,value:n,onChange:e=>{a(e.target.value),_(0),T(null)},children:[(0,i.jsx)(`option`,{value:`all`,children:`全部状态`}),(0,i.jsx)(`option`,{value:`active`,children:`active`}),(0,i.jsx)(`option`,{value:`archived`,children:`archived`}),(0,i.jsx)(`option`,{value:`forgotten`,children:`forgotten`})]}),(0,i.jsxs)(`select`,{className:o.input,value:f,onChange:e=>{p(e.target.value),_(0),T(null)},children:[(0,i.jsx)(`option`,{value:`all`,children:`全部种类`}),s.map(e=>(0,i.jsx)(`option`,{value:e,children:e},e))]}),(0,i.jsx)(`input`,{className:o.input,placeholder:`按内容搜索…`,value:m,onChange:e=>{h(e.target.value.trim()),_(0),T(null)}})]}),S!==null&&(0,i.jsx)(`div`,{className:o.empty,children:`加载失败:${S}`}),S===null&&v!==null&&v.records.length===0&&(0,i.jsx)(`div`,{className:o.empty,children:`没有符合条件的记忆`}),(v?.records??[]).map(e=>(0,i.jsxs)(`div`,{className:o.item,children:[(0,i.jsxs)(`div`,{className:o.row1,children:[(0,i.jsx)(`span`,{className:`${o.badge} ${o[e.status]}`,children:e.status}),(0,i.jsx)(`span`,{className:o.badge,children:e.kind}),(0,i.jsx)(`span`,{className:o.badge,children:e.scope})]}),(0,i.jsx)(`div`,{className:o.content,children:e.content}),(0,i.jsxs)(`div`,{className:o.meta,children:[(0,i.jsxs)(`span`,{children:[`重要性 `,e.importance.toFixed(2)]}),(0,i.jsxs)(`span`,{children:[`置信 `,e.confidence.toFixed(2)]}),(0,i.jsxs)(`span`,{children:[`访问 `,e.accessCount,` 次`]}),(0,i.jsx)(`span`,{children:e.sourceSessionId===null?`显式保存`:`来源 ${e.sourceSessionId.slice(0,16)}…${e.sourceRound===null?``:` 第${String(e.sourceRound)}轮`}`}),(0,i.jsx)(`span`,{children:l(e.createdAt)})]}),(0,i.jsxs)(`div`,{className:o.ops,children:[(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{A(`review`,e)},children:`详情`}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{A(`edit`,e)},children:`编辑`}),e.status===`active`?(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{k(`forget`,e)},children:`遗忘`}):(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{k(`restore`,e)},children:`恢复`})]}),w!==null&&w.record.id===e.id&&(0,i.jsx)(`div`,{className:o.expand,children:w.kind===`review`?(0,i.jsx)(u,{recordId:e.id,scope:e.scope}):(0,i.jsx)(d,{record:e,onClose:()=>{T(null)},onSaved:()=>{T(null),O()}})})]},e.id)),(0,i.jsxs)(`div`,{className:o.pager,children:[(0,i.jsx)(`button`,{type:`button`,className:o.button,disabled:g===0,onClick:()=>{_(Math.max(0,g-20))},children:`上一页`}),(0,i.jsxs)(`span`,{children:[`第 `,j,` / `,M,` 页 · 共 `,v?.total??0,` 条`]}),(0,i.jsx)(`button`,{type:`button`,className:o.button,disabled:v===null||g+20>=v.total,onClick:()=>{_(g+20)},children:`下一页`})]})]})}let p=[`slots`];function m(e){e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`engram`,order:20,label:()=>`记忆库`},f))}return n.apply=m,n.inject=p,t.exports}});
1
+ window.__ModuleLoader__.load({id:`@kenz1117/dsh-engram`,factory:e=>{var t={exports:{}},n=t.exports;Object.defineProperty(n,Symbol.toStringTag,{value:`Module`});let r=e("react"),i=e("react/jsx-runtime"),a=`@kenz1117/dsh-engram/panel.module.css`;if(typeof document<`u`&&document.querySelector(`style[data-plugin-css=`+JSON.stringify(a)+`]`)===null){let e=document.createElement(`style`);e.dataset.plugin=`@kenz1117/dsh-engram`,e.dataset.pluginCss=a,e.textContent=`.bWwFiW_panel{max-width:980px}.bWwFiW_toolbar{flex-wrap:wrap;align-items:center;gap:10px;margin-bottom:14px;display:flex}.bWwFiW_scopeTabs{border-bottom:1px solid var(--dsw-alias-border-l2);align-items:flex-end;gap:18px;margin-right:auto;display:flex}.bWwFiW_scopeOn,.bWwFiW_scopeOff{color:var(--dsw-alias-label-tertiary);cursor:pointer;background:0 0;border:0;padding:7px 1px 9px;font-size:13px;line-height:20px;position:relative}.bWwFiW_scopeOn,.bWwFiW_scopeOff:hover{color:var(--dsw-alias-label-primary)}.bWwFiW_scopeOn:after{background:var(--dsw-alias-brand-primary);content:"";border-radius:2px 2px 0 0;height:2px;position:absolute;bottom:-1px;left:0;right:0}.bWwFiW_button{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);cursor:pointer;border-radius:8px;padding:5px 12px;font-size:13px}.bWwFiW_button:hover{border-color:var(--dsw-alias-brand-primary)}.bWwFiW_button:disabled{opacity:.45;cursor:default}.bWwFiW_primary{background:var(--dsw-alias-button-primary-fill);border-color:var(--dsw-alias-button-primary-fill);color:var(--dsw-alias-label-primary-foreground)}.bWwFiW_cards{grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:10px;margin-bottom:14px;display:grid}.bWwFiW_card{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);text-align:left;cursor:pointer;color:var(--dsw-alias-label-primary);font:inherit;border-radius:10px;padding:10px 14px}.bWwFiW_card b{font-size:20px;display:block}.bWwFiW_card span{color:var(--dsw-alias-label-secondary);font-size:12px}.bWwFiW_filters{flex-wrap:wrap;gap:8px;margin-bottom:14px;display:flex}.bWwFiW_input{background:var(--dsw-alias-bg-layer-1);color:var(--dsw-alias-label-primary);border:1px solid var(--dsw-alias-border-l2);border-radius:8px;padding:6px 10px;font-size:13px}.bWwFiW_filters .bWwFiW_input{flex:1;min-width:160px}.bWwFiW_item{background:var(--dsw-alias-bg-layer-1);border:1px solid var(--dsw-alias-border-l2);border-radius:10px;margin-bottom:10px;padding:12px 14px}.bWwFiW_row1{flex-wrap:wrap;align-items:center;gap:8px;margin-bottom:6px;display:flex}.bWwFiW_badge{border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-secondary);background:var(--dsw-alias-fill-tsp-secondary);border-radius:99px;padding:1px 8px;font-size:11px}.bWwFiW_active{color:var(--dsw-alias-state-success-primary);border-color:var(--dsw-alias-state-success-primary)}.bWwFiW_archived{color:var(--dsw-alias-state-warn-primary);border-color:var(--dsw-alias-state-warn-primary)}.bWwFiW_forgotten{color:var(--dsw-alias-state-error-primary);border-color:var(--dsw-alias-state-error-primary)}.bWwFiW_content{white-space:pre-wrap;word-break:break-word;color:var(--dsw-alias-label-primary)}.bWwFiW_meta{color:var(--dsw-alias-label-secondary);flex-wrap:wrap;gap:14px;margin-top:6px;font-size:12px;display:flex}.bWwFiW_ops{gap:8px;margin-top:8px;display:flex}.bWwFiW_pager{color:var(--dsw-alias-label-secondary);justify-content:center;align-items:center;gap:10px;margin-top:14px;display:flex}.bWwFiW_empty{text-align:center;color:var(--dsw-alias-label-secondary);padding:40px 0}.bWwFiW_expand{border-top:1px dashed var(--dsw-alias-border-l2);margin-top:10px;padding-top:10px}.bWwFiW_expandLoading{color:var(--dsw-alias-label-secondary);font-size:13px}.bWwFiW_editForm{flex-direction:column;display:flex}.bWwFiW_editForm .bWwFiW_input{box-sizing:border-box;width:100%}.bWwFiW_fieldLabel{color:var(--dsw-alias-label-secondary);margin:10px 0 4px;font-size:12px;display:block}.bWwFiW_modalFoot{justify-content:flex-end;gap:8px;margin-top:12px;display:flex}.bWwFiW_review{background:var(--dsw-alias-bg-base);border:1px solid var(--dsw-alias-border-l2);white-space:pre-wrap;max-height:320px;color:var(--dsw-alias-label-primary);border-radius:8px;margin:0;padding:10px;font-size:12px;overflow:auto}`,document.head.appendChild(e)}var o={forgotten:`bWwFiW_forgotten`,expand:`bWwFiW_expand`,editForm:`bWwFiW_editForm`,review:`bWwFiW_review`,item:`bWwFiW_item`,ops:`bWwFiW_ops`,fieldLabel:`bWwFiW_fieldLabel`,filters:`bWwFiW_filters`,card:`bWwFiW_card`,scopeOff:`bWwFiW_scopeOff`,input:`bWwFiW_input`,cards:`bWwFiW_cards`,badge:`bWwFiW_badge`,primary:`bWwFiW_primary`,button:`bWwFiW_button`,active:`bWwFiW_active`,content:`bWwFiW_content`,row1:`bWwFiW_row1`,scopeOn:`bWwFiW_scopeOn`,meta:`bWwFiW_meta`,pager:`bWwFiW_pager`,empty:`bWwFiW_empty`,archived:`bWwFiW_archived`,expandLoading:`bWwFiW_expandLoading`,modalFoot:`bWwFiW_modalFoot`,toolbar:`bWwFiW_toolbar`,panel:`bWwFiW_panel`,scopeTabs:`bWwFiW_scopeTabs`};let s=[`fact`,`preference`,`decision`,`episode`,`skill`],c={active:`statusActive`,archived:`statusArchived`,forgotten:`statusForgotten`},l={fact:`kindFact`,preference:`kindPreference`,decision:`kindDecision`,episode:`kindEpisode`,skill:`kindSkill`},u={user:`scopeUser`,project:`scopeProject`},d={write:`opWrite`,update:`opUpdate`,forget:`opForget`,restore:`opRestore`,decay:`opDecay`,"ingest-request":`opIngestRequest`,"distill-request":`opDistillRequest`},f={supersededBy:`relSupersededBy`,supersedes:`relSupersedes`,contradicts:`relContradicts`,related:`relRelated`};function p(e,t){let n=l[t];return n===void 0?t:e(n)}function m(e,t){let n=d[t];return n===void 0?t:e(n)}function h(e,t){if(t.sourceSessionId===null)return e(`sourceExplicit`);let n=`${t.sourceSessionId.slice(0,16)}…`;return e(`sourceSession`,{id:t.sourceRound===null?n:`${n} ${e(`round`,{n:t.sourceRound})}`})}async function g(e,t){let n=await fetch(`/api/engram/${e}`,t),r=await n.json();if(!n.ok)throw Error(r.error??`HTTP ${String(n.status)}`);return r}function _(e){return new Date(e).toLocaleString(navigator.language||`zh-CN`,{hour12:!1})}function v({t:e,recordId:t,scope:n}){let[a,s]=(0,r.useState)(null);return(0,r.useEffect)(()=>{let r=!1;return g(`review?scope=${n}&id=${encodeURIComponent(t)}`).then(t=>{if(r)return;let n=t.record,i=[`${e(`labelContent`)}: ${n.content}`,`${e(`detailAttributes`)}: ${e(`labelKind`)}=${p(e,n.kind)}, ${e(`labelStatus`)}=${e(c[n.status])}, ${e(`importance`)}=${n.importance.toFixed(2)}, ${e(`confidence`)}=${n.confidence.toFixed(2)}, ${e(`accessCount`,{n:n.accessCount})}`,`${e(`detailSource`)}: ${h(e,n)}`];for(let n of[`supersededBy`,`supersedes`,`contradicts`,`related`]){let r=t[n],a=f[n];r!==void 0&&r.length>0&&i.push(`${a===void 0?n:e(a)}: ${r.join(`, `)}`)}let a=t.operations;a!==void 0&&a.length>0&&(i.push(`${e(`detailOperations`)}:`),a.forEach(t=>i.push(` ${_(t.at)} ${m(e,t.op)}${t.detail?` ${t.detail}`:``}`))),s(i.join(`
2
+ `))}).catch(t=>{r||s(e(`loadFailed`,{msg:t.message}))}),()=>{r=!0}},[t,n,e]),a===null?(0,i.jsx)(`div`,{className:o.expandLoading,children:e(`loading`)}):(0,i.jsx)(`pre`,{className:o.review,children:a})}function y({t:e,record:t,onClose:n,onSaved:a}){let[c,l]=(0,r.useState)(t.content),[u,d]=(0,r.useState)(t.kind),[f,m]=(0,r.useState)(t.importance);return(0,i.jsxs)(`div`,{className:o.editForm,children:[(0,i.jsx)(`label`,{className:o.fieldLabel,children:e(`labelContent`)}),(0,i.jsx)(`textarea`,{className:o.input,rows:3,value:c,onChange:e=>l(e.target.value)}),(0,i.jsx)(`label`,{className:o.fieldLabel,children:e(`labelKind`)}),(0,i.jsx)(`select`,{className:o.input,value:u,onChange:e=>d(e.target.value),children:s.map(t=>(0,i.jsx)(`option`,{value:t,children:p(e,t)},t))}),(0,i.jsx)(`label`,{className:o.fieldLabel,children:`${e(`importance`)} ${f.toFixed(2)}`}),(0,i.jsx)(`input`,{type:`range`,min:0,max:1,step:.05,value:f,onChange:e=>m(Number(e.target.value))}),(0,i.jsxs)(`div`,{className:o.modalFoot,children:[(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:n,children:e(`cancel`)}),(0,i.jsx)(`button`,{type:`button`,className:`${o.button} ${o.primary}`,onClick:()=>{g(`update`,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t.id,scope:t.scope,content:c,kind:u,importance:f})}).then(a).catch(e=>{alert(e.message)})},children:e(`saveWithHint`)})]})]})}function b({t:e}){let[t,n]=(0,r.useState)(`user`),[a,l]=(0,r.useState)(`all`),[d,f]=(0,r.useState)(`all`),[m,b]=(0,r.useState)(``),[x,S]=(0,r.useState)(0),[C,w]=(0,r.useState)(null),[T,E]=(0,r.useState)(null),[D,O]=(0,r.useState)(null),[k,A]=(0,r.useState)(null),[j,M]=(0,r.useState)(0),N=(0,r.useCallback)(()=>{M(e=>e+1)},[]);(0,r.useEffect)(()=>{let e=!1,n=new URLSearchParams({scope:t,status:a,kind:d,limit:`20`,offset:String(x)});return m!==``&&n.set(`q`,m),g(`list?${n.toString()}`).then(t=>{e||(w(t),O(null))}).catch(t=>{e||O(t.message)}),g(`stats`).then(t=>{e||E(t.parts)}).catch(()=>{}),()=>{e=!0}},[t,a,d,m,x,j]);let P=(e,t)=>{g(e,{method:`POST`,headers:{"content-type":`application/json`},body:JSON.stringify({id:t.id,scope:t.scope})}).then(N).catch(e=>{alert(e.message)})},F=(e,t)=>{A(n=>n!==null&&n.kind===e&&n.record.id===t.id?null:{kind:e,record:t})},I=Math.floor(x/20)+1,L=C===null?1:Math.max(1,Math.ceil(C.total/20));return(0,i.jsxs)(`div`,{className:o.panel,children:[(0,i.jsxs)(`div`,{className:o.toolbar,children:[(0,i.jsx)(`div`,{className:o.scopeTabs,children:[`user`,`project`].map(r=>(0,i.jsx)(`button`,{type:`button`,className:t===r?o.scopeOn:o.scopeOff,onClick:()=>{n(r),S(0),A(null)},children:e(u[r])},r))}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:N,children:e(`refresh`)}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{location.href=`/api/engram/export?scope=${t}&format=markdown`},children:e(`exportMd`)}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{location.href=`/api/engram/export?scope=${t}&format=json`},children:e(`exportJson`)})]}),(0,i.jsx)(`div`,{className:o.cards,children:(T??[]).map(r=>(0,i.jsxs)(`button`,{type:`button`,className:o.card,style:r.scope===t?{borderColor:`var(--dsw-alias-brand-primary)`}:void 0,onClick:()=>{n(r.scope),S(0)},children:[(0,i.jsx)(`b`,{children:r.stats.total}),(0,i.jsx)(`span`,{children:`${e(u[r.scope])} · ${e(`cardActive`,{n:r.stats.active})} · ${e(`signalRatio`,{n:Math.round(r.stats.signalRatio*100)})}`})]},r.scope))}),(0,i.jsxs)(`div`,{className:o.filters,children:[(0,i.jsxs)(`select`,{className:o.input,value:a,onChange:e=>{l(e.target.value),S(0),A(null)},children:[(0,i.jsx)(`option`,{value:`all`,children:e(`allStatuses`)}),(0,i.jsx)(`option`,{value:`active`,children:e(`statusActive`)}),(0,i.jsx)(`option`,{value:`archived`,children:e(`statusArchived`)}),(0,i.jsx)(`option`,{value:`forgotten`,children:e(`statusForgotten`)})]}),(0,i.jsxs)(`select`,{className:o.input,value:d,onChange:e=>{f(e.target.value),S(0),A(null)},children:[(0,i.jsx)(`option`,{value:`all`,children:e(`allKinds`)}),s.map(t=>(0,i.jsx)(`option`,{value:t,children:p(e,t)},t))]}),(0,i.jsx)(`input`,{className:o.input,placeholder:e(`searchPlaceholder`),value:m,onChange:e=>{b(e.target.value.trim()),S(0),A(null)}})]}),D!==null&&(0,i.jsx)(`div`,{className:o.empty,children:e(`loadFailed`,{msg:D})}),D===null&&C!==null&&C.records.length===0&&(0,i.jsx)(`div`,{className:o.empty,children:e(`empty`)}),(C?.records??[]).map(t=>(0,i.jsxs)(`div`,{className:o.item,children:[(0,i.jsxs)(`div`,{className:o.row1,children:[(0,i.jsx)(`span`,{className:`${o.badge} ${o[t.status]}`,children:e(c[t.status])}),(0,i.jsx)(`span`,{className:o.badge,children:p(e,t.kind)}),(0,i.jsx)(`span`,{className:o.badge,children:e(u[t.scope])})]}),(0,i.jsx)(`div`,{className:o.content,children:t.content}),(0,i.jsxs)(`div`,{className:o.meta,children:[(0,i.jsx)(`span`,{children:`${e(`importance`)} ${t.importance.toFixed(2)}`}),(0,i.jsx)(`span`,{children:`${e(`confidence`)} ${t.confidence.toFixed(2)}`}),(0,i.jsx)(`span`,{children:e(`accessCount`,{n:t.accessCount})}),(0,i.jsx)(`span`,{children:h(e,t)}),(0,i.jsx)(`span`,{children:_(t.createdAt)})]}),(0,i.jsxs)(`div`,{className:o.ops,children:[(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{F(`review`,t)},children:e(`detail`)}),(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{F(`edit`,t)},children:e(`edit`)}),t.status===`active`?(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{P(`forget`,t)},children:e(`forget`)}):(0,i.jsx)(`button`,{type:`button`,className:o.button,onClick:()=>{P(`restore`,t)},children:e(`restore`)})]}),k!==null&&k.record.id===t.id&&(0,i.jsx)(`div`,{className:o.expand,children:k.kind===`review`?(0,i.jsx)(v,{t:e,recordId:t.id,scope:t.scope}):(0,i.jsx)(y,{t:e,record:t,onClose:()=>{A(null)},onSaved:()=>{A(null),N()}})})]},t.id)),(0,i.jsxs)(`div`,{className:o.pager,children:[(0,i.jsx)(`button`,{type:`button`,className:o.button,disabled:x===0,onClick:()=>{S(Math.max(0,x-20))},children:e(`prevPage`)}),(0,i.jsx)(`span`,{children:e(`pagerInfo`,{page:I,pages:L,total:C?.total??0})}),(0,i.jsx)(`button`,{type:`button`,className:o.button,disabled:C===null||x+20>=C.total,onClick:()=>{S(x+20)},children:e(`nextPage`)})]})]})}let x=`engram`,S={nav:`记忆库`,scopeUser:`用户级`,scopeProject:`项目级`,refresh:`刷新`,exportMd:`导出 MD`,exportJson:`导出 JSON`,allStatuses:`全部状态`,allKinds:`全部种类`,searchPlaceholder:`按内容搜索…`,statusActive:`生效中`,statusArchived:`已归档`,statusForgotten:`已遗忘`,kindFact:`事实`,kindPreference:`偏好`,kindDecision:`决策`,kindEpisode:`事件`,kindSkill:`技能`,labelContent:`内容`,labelKind:`种类`,labelStatus:`状态`,importance:`重要性`,confidence:`置信`,accessCount:`访问 {n} 次`,sourceExplicit:`显式保存`,sourceSession:`来源 {id}`,round:`第 {n} 轮`,cardActive:`生效 {n}`,signalRatio:`信噪比 {n}%`,detail:`详情`,edit:`编辑`,forget:`遗忘`,restore:`恢复`,empty:`没有符合条件的记忆`,loadFailed:`加载失败:{msg}`,loading:`加载中…`,pagerInfo:`第 {page} / {pages} 页 · 共 {total} 条`,prevPage:`上一页`,nextPage:`下一页`,cancel:`取消`,saveWithHint:`保存(旧条目归档)`,detailAttributes:`属性`,detailSource:`来源`,detailRelations:`关系`,detailOperations:`最近操作`,relSupersededBy:`被取代`,relSupersedes:`取代`,relContradicts:`矛盾`,relRelated:`关联`,opWrite:`写入`,opUpdate:`更新`,opForget:`遗忘`,opRestore:`恢复`,opDecay:`衰减归档`,opIngestRequest:`摄取请求`,opDistillRequest:`蒸馏请求`},C={nav:`Memory Library`,scopeUser:`User`,scopeProject:`Project`,refresh:`Refresh`,exportMd:`Export MD`,exportJson:`Export JSON`,allStatuses:`All statuses`,allKinds:`All kinds`,searchPlaceholder:`Search content…`,statusActive:`active`,statusArchived:`archived`,statusForgotten:`forgotten`,kindFact:`fact`,kindPreference:`preference`,kindDecision:`decision`,kindEpisode:`episode`,kindSkill:`skill`,labelContent:`Content`,labelKind:`Kind`,labelStatus:`Status`,importance:`Importance`,confidence:`Confidence`,accessCount:`{n} accesses`,sourceExplicit:`Explicitly saved`,sourceSession:`Source {id}`,round:`round {n}`,cardActive:`{n} active`,signalRatio:`signal {n}%`,detail:`Details`,edit:`Edit`,forget:`Forget`,restore:`Restore`,empty:`No matching memories`,loadFailed:`Failed to load: {msg}`,loading:`Loading…`,pagerInfo:`Page {page} / {pages} · {total} records`,prevPage:`Prev`,nextPage:`Next`,cancel:`Cancel`,saveWithHint:`Save (archives old entry)`,detailAttributes:`Attributes`,detailSource:`Source`,detailRelations:`Relations`,detailOperations:`Recent operations`,relSupersededBy:`Superseded by`,relSupersedes:`Supersedes`,relContradicts:`Contradicts`,relRelated:`Related`,opWrite:`write`,opUpdate:`update`,opForget:`forget`,opRestore:`restore`,opDecay:`decay`,opIngestRequest:`ingest-request`,opDistillRequest:`distill-request`},w=[`slots`,`locale`];function T(e){e.effect(()=>e.locale.register(x,{zh:S,en:C}),`dsh-engram: dictionaries`);let t=e.locale.bind(x);e.slots.inject(`settings.section`,()=>e.slots.register({name:`settings.section`,id:`engram`,order:20,label:()=>t(`nav`),locale:x},b))}return n.apply=T,n.inject=w,t.exports}});
3
3
  //# sourceMappingURL=client.js.map
package/lib/client.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","names":["useState","styles","useCallback"],"sources":["../src/client/EngramPanel.tsx","../src/client/index.ts"],"sourcesContent":["/**\n * 记忆库设置面板:统计卡片、过滤列表、编辑(取代链)、遗忘/恢复、导出。\n * 数据经回环 API(/api/engram/*)读写;详情与编辑为条目下方行内展开\n * (不嵌套弹窗);内容节点一律 DOM/JSX 构建(防 XSS)。\n * @module @kenz1117/dsh-engram/client/EngramPanel\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport styles from './panel.module.css'\n\n/** 列表/详情用的记忆视图(宿主 /api/engram/list 的行结构)。 */\ninterface MemoryRow {\n readonly id: string\n readonly scope: 'user' | 'project'\n readonly kind: string\n readonly content: string\n readonly importance: number\n readonly confidence: number\n readonly status: 'active' | 'archived' | 'forgotten'\n readonly createdAt: number\n readonly accessCount: number\n readonly sourceSessionId: string | null\n readonly sourceRound: number | null\n}\n\ninterface ListResult {\n readonly records: MemoryRow[]\n readonly total: number\n}\n\ninterface StatsPart {\n readonly scope: 'user' | 'project'\n readonly stats: {\n readonly total: number\n readonly active: number\n readonly archived: number\n readonly forgotten: number\n readonly signalRatio: number\n }\n}\n\nconst KINDS = ['fact', 'preference', 'decision', 'episode', 'skill'] as const\nconst PAGE_SIZE = 20\n\nasync function api<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(`/api/engram/${path}`, init)\n const body = (await response.json()) as T & { error?: string }\n if (!response.ok) throw new Error(body.error ?? `HTTP ${String(response.status)}`)\n return body\n}\n\nfunction fmtTime(ms: number): string {\n return new Date(ms).toLocaleString('zh-CN', { hour12: false })\n}\n\n/** 行内审计区:来源链、关系与最近操作日志(review API)。 */\nfunction ReviewBody({ recordId, scope }: { recordId: string; scope: 'user' | 'project' }): React.ReactElement {\n const [text, setText] = useState<string | null>(null)\n useEffect(() => {\n let cancelled = false\n api<Record<string, unknown>>(`review?scope=${scope}&id=${encodeURIComponent(recordId)}`)\n .then((view) => {\n if (cancelled) return\n const record = view.record as MemoryRow\n const lines = [\n `内容: ${record.content}`,\n `属性: kind=${record.kind}, status=${record.status}, importance=${record.importance.toFixed(2)}, confidence=${record.confidence.toFixed(2)}, 访问 ${record.accessCount} 次`,\n `来源: ${record.sourceSessionId ?? '显式保存'}${record.sourceRound === null ? '' : ` 第${String(record.sourceRound)}轮`}`,\n ]\n for (const key of ['supersededBy', 'supersedes', 'contradicts', 'related'] as const) {\n const ids = view[key] as string[] | undefined\n if (ids !== undefined && ids.length > 0) lines.push(`${key}: ${ids.join(', ')}`)\n }\n const operations = view.operations as { at: number; op: string; detail: string | null }[] | undefined\n if (operations !== undefined && operations.length > 0) {\n lines.push('最近操作:')\n operations.forEach(op => lines.push(` ${fmtTime(op.at)} ${op.op}${op.detail ? ` ${op.detail}` : ''}`))\n }\n setText(lines.join('\\n'))\n })\n .catch((error: Error) => { if (!cancelled) setText(`加载失败:${error.message}`) })\n return () => { cancelled = true }\n }, [recordId, scope])\n if (text === null) return <div className={styles.expandLoading}>加载中…</div>\n return <pre className={styles.review}>{text}</pre>\n}\n\n/** 行内编辑表单:内容/种类/重要性 → POST update(旧条目归档,取代链保留)。 */\nfunction EditForm({ record, onClose, onSaved }: {\n record: MemoryRow\n onClose: () => void\n onSaved: () => void\n}): React.ReactElement {\n const [content, setContent] = useState(record.content)\n const [kind, setKind] = useState(record.kind)\n const [importance, setImportance] = useState(record.importance)\n const save = (): void => {\n api('update', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: record.id, scope: record.scope, content, kind, importance }),\n })\n .then(onSaved)\n .catch((error: Error) => { alert(error.message) })\n }\n return (\n <div className={styles.editForm}>\n <label className={styles.fieldLabel}>内容</label>\n <textarea className={styles.input} rows={3} value={content} onChange={event => setContent(event.target.value)} />\n <label className={styles.fieldLabel}>种类</label>\n <select className={styles.input} value={kind} onChange={event => setKind(event.target.value)}>\n {KINDS.map(option => <option key={option} value={option}>{option}</option>)}\n </select>\n <label className={styles.fieldLabel}>重要性 {importance.toFixed(2)}</label>\n <input type=\"range\" min={0} max={1} step={0.05} value={importance}\n onChange={event => setImportance(Number(event.target.value))} />\n <div className={styles.modalFoot}>\n <button type=\"button\" className={styles.button} onClick={onClose}>取消</button>\n <button type=\"button\" className={`${styles.button} ${styles.primary}`} onClick={save}>保存(旧条目归档)</button>\n </div>\n </div>\n )\n}\n\n/** 设置页「记忆库」section 主组件。 */\nexport function EngramSection(): React.ReactElement {\n const [scope, setScope] = useState<'user' | 'project'>('user')\n const [status, setStatus] = useState('all')\n const [kind, setKind] = useState('all')\n const [q, setQ] = useState('')\n const [offset, setOffset] = useState(0)\n const [list, setList] = useState<ListResult | null>(null)\n const [stats, setStats] = useState<StatsPart[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n /** 行内展开态:互斥的唯一展开条目(review 或 edit)。 */\n const [expanded, setExpanded] = useState<{ kind: 'review' | 'edit'; record: MemoryRow } | null>(null)\n const [reloadTick, setReloadTick] = useState(0)\n\n const reload = useCallback((): void => { setReloadTick(tick => tick + 1) }, [])\n\n useEffect(() => {\n let cancelled = false\n const qs = new URLSearchParams({\n scope, status, kind, limit: String(PAGE_SIZE), offset: String(offset),\n })\n if (q !== '') qs.set('q', q)\n api<ListResult>(`list?${qs.toString()}`)\n .then((data) => { if (!cancelled) { setList(data); setError(null) } })\n .catch((loadError: Error) => { if (!cancelled) setError(loadError.message) })\n api<{ parts: StatsPart[] }>('stats')\n .then((data) => { if (!cancelled) setStats(data.parts) })\n .catch(() => { /* 统计失败不影响列表 */ })\n return () => { cancelled = true }\n }, [scope, status, kind, q, offset, reloadTick])\n\n const act = (route: string, record: MemoryRow): void => {\n api(route, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: record.id, scope: record.scope }),\n })\n .then(reload)\n .catch((actError: Error) => { alert(actError.message) })\n }\n\n const toggle = (kind: 'review' | 'edit', record: MemoryRow): void => {\n setExpanded(current => (current !== null && current.kind === kind && current.record.id === record.id)\n ? null\n : { kind, record })\n }\n\n const page = Math.floor(offset / PAGE_SIZE) + 1\n const pages = list === null ? 1 : Math.max(1, Math.ceil(list.total / PAGE_SIZE))\n\n return (\n <div className={styles.panel}>\n <div className={styles.toolbar}>\n <div className={styles.scopeTabs}>\n {(['user', 'project'] as const).map(option => (\n <button key={option} type=\"button\"\n className={scope === option ? styles.scopeOn : styles.scopeOff}\n onClick={() => { setScope(option); setOffset(0); setExpanded(null) }}>\n {option === 'user' ? '用户级' : '项目级'}\n </button>\n ))}\n </div>\n <button type=\"button\" className={styles.button} onClick={reload}>刷新</button>\n <button type=\"button\" className={styles.button}\n onClick={() => { location.href = `/api/engram/export?scope=${scope}&format=markdown` }}>导出 MD</button>\n <button type=\"button\" className={styles.button}\n onClick={() => { location.href = `/api/engram/export?scope=${scope}&format=json` }}>导出 JSON</button>\n </div>\n <div className={styles.cards}>\n {(stats ?? []).map(part => (\n <button key={part.scope} type=\"button\" className={styles.card}\n style={part.scope === scope ? { borderColor: 'var(--dsw-alias-brand-primary)' } : undefined}\n onClick={() => { setScope(part.scope); setOffset(0) }}>\n <b>{part.stats.total}</b>\n <span>{part.scope} · active {part.stats.active} · 信噪比 {Math.round(part.stats.signalRatio * 100)}%</span>\n </button>\n ))}\n </div>\n <div className={styles.filters}>\n <select className={styles.input} value={status}\n onChange={event => { setStatus(event.target.value); setOffset(0); setExpanded(null) }}>\n <option value=\"all\">全部状态</option>\n <option value=\"active\">active</option>\n <option value=\"archived\">archived</option>\n <option value=\"forgotten\">forgotten</option>\n </select>\n <select className={styles.input} value={kind}\n onChange={event => { setKind(event.target.value); setOffset(0); setExpanded(null) }}>\n <option value=\"all\">全部种类</option>\n {KINDS.map(option => <option key={option} value={option}>{option}</option>)}\n </select>\n <input className={styles.input} placeholder=\"按内容搜索…\" value={q}\n onChange={event => { setQ(event.target.value.trim()); setOffset(0); setExpanded(null) }} />\n </div>\n {error !== null && <div className={styles.empty}>{`加载失败:${error}`}</div>}\n {error === null && list !== null && list.records.length === 0 && (\n <div className={styles.empty}>没有符合条件的记忆</div>\n )}\n {(list?.records ?? []).map(record => (\n <div key={record.id} className={styles.item}>\n <div className={styles.row1}>\n <span className={`${styles.badge} ${styles[record.status]}`}>{record.status}</span>\n <span className={styles.badge}>{record.kind}</span>\n <span className={styles.badge}>{record.scope}</span>\n </div>\n <div className={styles.content}>{record.content}</div>\n <div className={styles.meta}>\n <span>重要性 {record.importance.toFixed(2)}</span>\n <span>置信 {record.confidence.toFixed(2)}</span>\n <span>访问 {record.accessCount} 次</span>\n <span>{record.sourceSessionId === null\n ? '显式保存'\n : `来源 ${record.sourceSessionId.slice(0, 16)}…${record.sourceRound === null ? '' : ` 第${String(record.sourceRound)}轮`}`}</span>\n <span>{fmtTime(record.createdAt)}</span>\n </div>\n <div className={styles.ops}>\n <button type=\"button\" className={styles.button}\n onClick={() => { toggle('review', record) }}>详情</button>\n <button type=\"button\" className={styles.button}\n onClick={() => { toggle('edit', record) }}>编辑</button>\n {record.status === 'active'\n ? <button type=\"button\" className={styles.button}\n onClick={() => { act('forget', record) }}>遗忘</button>\n : <button type=\"button\" className={styles.button}\n onClick={() => { act('restore', record) }}>恢复</button>}\n </div>\n {expanded !== null && expanded.record.id === record.id && (\n <div className={styles.expand}>\n {expanded.kind === 'review'\n ? <ReviewBody recordId={record.id} scope={record.scope} />\n : <EditForm record={record} onClose={() => { setExpanded(null) }}\n onSaved={() => { setExpanded(null); reload() }} />}\n </div>\n )}\n </div>\n ))}\n <div className={styles.pager}>\n <button type=\"button\" className={styles.button} disabled={offset === 0}\n onClick={() => { setOffset(Math.max(0, offset - PAGE_SIZE)) }}>上一页</button>\n <span>第 {page} / {pages} 页 · 共 {list?.total ?? 0} 条</span>\n <button type=\"button\" className={styles.button}\n disabled={list === null || offset + PAGE_SIZE >= list.total}\n onClick={() => { setOffset(offset + PAGE_SIZE) }}>下一页</button>\n </div>\n </div>\n )\n}\n","/**\n * dsh-engram client half: 注册设置页「记忆库」section。\n * 数据经回环 API(/api/engram/*)读取与操作,与 host 半的路由对齐。\n * @module @kenz1117/dsh-engram/client\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\n// Type-only: ctx.slots(renderer 的 SlotMap 渲染接线)。\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: settings.section 槽位的 SlotMap merge(ui-settings 契约)。\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\nimport { EngramSection } from './EngramPanel.tsx'\n\n/** 必需服务:slots 渲染接线。 */\nexport const inject = ['slots']\n\n/**\n * Client plugin body: 设置页注册「记忆库」section(order 20,排在\n * general/models/plugins 之后)。\n * @param ctx - client root context.\n */\nexport function apply(ctx: Context): void {\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'engram',\n order: 20,\n label: () => '记忆库',\n }, EngramSection))\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"client.js","names":["useState","styles","useCallback"],"sources":["../src/client/EngramPanel.tsx","../src/client/locales.ts","../src/client/index.ts"],"sourcesContent":["/**\n * 记忆库设置面板:统计卡片、过滤列表、编辑(取代链)、遗忘/恢复、导出。\n * 数据经回环 API(/api/engram/*)读写;详情与编辑为条目下方行内展开\n * (不嵌套弹窗);文案全部走宿主 locale 词典(zh/en),语言切换自动\n * 重渲染;数据层英文枚举(status/kind/op)只在显示层映射;内容节点\n * 一律 DOM/JSX 构建(防 XSS)。\n * @module @kenz1117/dsh-engram/client/EngramPanel\n */\n\nimport { useCallback, useEffect, useState } from 'react'\nimport type { PropsLocale } from '@deepseek-ai/dsh-client-ui-slots'\nimport styles from './panel.module.css'\nimport { NS, type EngramKey } from './locales.ts'\n\n/** 列表/详情用的记忆视图(宿主 /api/engram/list 的行结构)。 */\ninterface MemoryRow {\n readonly id: string\n readonly scope: 'user' | 'project'\n readonly kind: string\n readonly content: string\n readonly importance: number\n readonly confidence: number\n readonly status: 'active' | 'archived' | 'forgotten'\n readonly createdAt: number\n readonly accessCount: number\n readonly sourceSessionId: string | null\n readonly sourceRound: number | null\n}\n\ninterface ListResult {\n readonly records: MemoryRow[]\n readonly total: number\n}\n\ninterface StatsPart {\n readonly scope: 'user' | 'project'\n readonly stats: {\n readonly total: number\n readonly active: number\n readonly archived: number\n readonly forgotten: number\n readonly signalRatio: number\n }\n}\n\n/** 面板内部传递的翻译函数(渲染器按注册的 locale 声明合成的 t 的窄化签名)。 */\ntype T = (key: EngramKey, params?: Record<string, unknown>) => string\n\n/** kind 数据值(存储层英文枚举,编辑表单下拉的 value 保持英文)。 */\nconst KINDS = ['fact', 'preference', 'decision', 'episode', 'skill'] as const\nconst PAGE_SIZE = 20\n\n/** 数据层枚举 → 词典键映射(显示层本地化,存储值不变)。 */\nconst STATUS_KEY: Record<MemoryRow['status'], EngramKey> = {\n active: 'statusActive',\n archived: 'statusArchived',\n forgotten: 'statusForgotten',\n}\nconst KIND_KEY: Record<string, EngramKey> = {\n fact: 'kindFact',\n preference: 'kindPreference',\n decision: 'kindDecision',\n episode: 'kindEpisode',\n skill: 'kindSkill',\n}\nconst SCOPE_KEY: Record<MemoryRow['scope'], EngramKey> = {\n user: 'scopeUser',\n project: 'scopeProject',\n}\nconst OP_KEY: Record<string, EngramKey> = {\n write: 'opWrite',\n update: 'opUpdate',\n forget: 'opForget',\n restore: 'opRestore',\n decay: 'opDecay',\n 'ingest-request': 'opIngestRequest',\n 'distill-request': 'opDistillRequest',\n}\nconst REL_KEY: Record<string, EngramKey> = {\n supersededBy: 'relSupersededBy',\n supersedes: 'relSupersedes',\n contradicts: 'relContradicts',\n related: 'relRelated',\n}\n\n/** kind 数据值 → 本地化标签(未知值回退原文)。 */\nfunction kindLabel(t: T, kind: string): string {\n const key = KIND_KEY[kind]\n return key === undefined ? kind : t(key)\n}\n\n/** op 数据值 → 本地化标签(未知值回退原文)。 */\nfunction opLabel(t: T, op: string): string {\n const key = OP_KEY[op]\n return key === undefined ? op : t(key)\n}\n\n/** 来源会话展示片段:截短的会话 id + 可选轮次。 */\nfunction sourceLabel(t: T, record: Pick<MemoryRow, 'sourceSessionId' | 'sourceRound'>): string {\n if (record.sourceSessionId === null) return t('sourceExplicit')\n const id = `${record.sourceSessionId.slice(0, 16)}…`\n const withRound = record.sourceRound === null\n ? id\n : `${id} ${t('round', { n: record.sourceRound })}`\n return t('sourceSession', { id: withRound })\n}\n\nasync function api<T>(path: string, init?: RequestInit): Promise<T> {\n const response = await fetch(`/api/engram/${path}`, init)\n const body = (await response.json()) as T & { error?: string }\n if (!response.ok) throw new Error(body.error ?? `HTTP ${String(response.status)}`)\n return body\n}\n\nfunction fmtTime(ms: number): string {\n // 时间格式跟随浏览器环境语言(宿主界面语言通常与浏览器一致)。\n return new Date(ms).toLocaleString(navigator.language || 'zh-CN', { hour12: false })\n}\n\n/** 行内审计区:来源链、关系与最近操作日志(review API)。 */\nfunction ReviewBody({ t, recordId, scope }: {\n t: T\n recordId: string\n scope: 'user' | 'project'\n}): React.ReactElement {\n const [text, setText] = useState<string | null>(null)\n useEffect(() => {\n let cancelled = false\n api<Record<string, unknown>>(`review?scope=${scope}&id=${encodeURIComponent(recordId)}`)\n .then((view) => {\n if (cancelled) return\n const record = view.record as MemoryRow\n const lines = [\n `${t('labelContent')}: ${record.content}`,\n `${t('detailAttributes')}: ${t('labelKind')}=${kindLabel(t, record.kind)}, ${t('labelStatus')}=${t(STATUS_KEY[record.status])}, ${t('importance')}=${record.importance.toFixed(2)}, ${t('confidence')}=${record.confidence.toFixed(2)}, ${t('accessCount', { n: record.accessCount })}`,\n `${t('detailSource')}: ${sourceLabel(t, record)}`,\n ]\n for (const key of ['supersededBy', 'supersedes', 'contradicts', 'related'] as const) {\n const ids = view[key] as string[] | undefined\n const relKey = REL_KEY[key]\n if (ids !== undefined && ids.length > 0) lines.push(`${relKey === undefined ? key : t(relKey)}: ${ids.join(', ')}`)\n }\n const operations = view.operations as { at: number; op: string; detail: string | null }[] | undefined\n if (operations !== undefined && operations.length > 0) {\n lines.push(`${t('detailOperations')}:`)\n operations.forEach(op => lines.push(` ${fmtTime(op.at)} ${opLabel(t, op.op)}${op.detail ? ` ${op.detail}` : ''}`))\n }\n setText(lines.join('\\n'))\n })\n .catch((error: Error) => { if (!cancelled) setText(t('loadFailed', { msg: error.message })) })\n return () => { cancelled = true }\n }, [recordId, scope, t])\n if (text === null) return <div className={styles.expandLoading}>{t('loading')}</div>\n return <pre className={styles.review}>{text}</pre>\n}\n\n/** 行内编辑表单:内容/种类/重要性 → POST update(旧条目归档,取代链保留)。 */\nfunction EditForm({ t, record, onClose, onSaved }: {\n t: T\n record: MemoryRow\n onClose: () => void\n onSaved: () => void\n}): React.ReactElement {\n const [content, setContent] = useState(record.content)\n const [kind, setKind] = useState(record.kind)\n const [importance, setImportance] = useState(record.importance)\n const save = (): void => {\n api('update', {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: record.id, scope: record.scope, content, kind, importance }),\n })\n .then(onSaved)\n .catch((error: Error) => { alert(error.message) })\n }\n return (\n <div className={styles.editForm}>\n <label className={styles.fieldLabel}>{t('labelContent')}</label>\n <textarea className={styles.input} rows={3} value={content} onChange={event => setContent(event.target.value)} />\n <label className={styles.fieldLabel}>{t('labelKind')}</label>\n {/* option 的 value 保持数据层英文枚举,仅显示文本本地化。 */}\n <select className={styles.input} value={kind} onChange={event => setKind(event.target.value)}>\n {KINDS.map(option => <option key={option} value={option}>{kindLabel(t, option)}</option>)}\n </select>\n <label className={styles.fieldLabel}>{`${t('importance')} ${importance.toFixed(2)}`}</label>\n <input type=\"range\" min={0} max={1} step={0.05} value={importance}\n onChange={event => setImportance(Number(event.target.value))} />\n <div className={styles.modalFoot}>\n <button type=\"button\" className={styles.button} onClick={onClose}>{t('cancel')}</button>\n <button type=\"button\" className={`${styles.button} ${styles.primary}`} onClick={save}>{t('saveWithHint')}</button>\n </div>\n </div>\n )\n}\n\n/** 设置页「记忆库」section 主组件(t 由渲染器按 locale: NS 声明合成)。 */\nexport function EngramSection({ t }: PropsLocale<typeof NS>): React.ReactElement {\n const [scope, setScope] = useState<'user' | 'project'>('user')\n const [status, setStatus] = useState('all')\n const [kind, setKind] = useState('all')\n const [q, setQ] = useState('')\n const [offset, setOffset] = useState(0)\n const [list, setList] = useState<ListResult | null>(null)\n const [stats, setStats] = useState<StatsPart[] | null>(null)\n const [error, setError] = useState<string | null>(null)\n /** 行内展开态:互斥的唯一展开条目(review 或 edit)。 */\n const [expanded, setExpanded] = useState<{ kind: 'review' | 'edit'; record: MemoryRow } | null>(null)\n const [reloadTick, setReloadTick] = useState(0)\n\n const reload = useCallback((): void => { setReloadTick(tick => tick + 1) }, [])\n\n useEffect(() => {\n let cancelled = false\n const qs = new URLSearchParams({\n scope, status, kind, limit: String(PAGE_SIZE), offset: String(offset),\n })\n if (q !== '') qs.set('q', q)\n api<ListResult>(`list?${qs.toString()}`)\n .then((data) => { if (!cancelled) { setList(data); setError(null) } })\n .catch((loadError: Error) => { if (!cancelled) setError(loadError.message) })\n api<{ parts: StatsPart[] }>('stats')\n .then((data) => { if (!cancelled) setStats(data.parts) })\n .catch(() => { /* 统计失败不影响列表 */ })\n return () => { cancelled = true }\n }, [scope, status, kind, q, offset, reloadTick])\n\n const act = (route: string, record: MemoryRow): void => {\n api(route, {\n method: 'POST',\n headers: { 'content-type': 'application/json' },\n body: JSON.stringify({ id: record.id, scope: record.scope }),\n })\n .then(reload)\n .catch((actError: Error) => { alert(actError.message) })\n }\n\n const toggle = (expandKind: 'review' | 'edit', record: MemoryRow): void => {\n setExpanded(current => (current !== null && current.kind === expandKind && current.record.id === record.id)\n ? null\n : { kind: expandKind, record })\n }\n\n const page = Math.floor(offset / PAGE_SIZE) + 1\n const pages = list === null ? 1 : Math.max(1, Math.ceil(list.total / PAGE_SIZE))\n\n return (\n <div className={styles.panel}>\n <div className={styles.toolbar}>\n <div className={styles.scopeTabs}>\n {(['user', 'project'] as const).map(option => (\n <button key={option} type=\"button\"\n className={scope === option ? styles.scopeOn : styles.scopeOff}\n onClick={() => { setScope(option); setOffset(0); setExpanded(null) }}>\n {t(SCOPE_KEY[option])}\n </button>\n ))}\n </div>\n <button type=\"button\" className={styles.button} onClick={reload}>{t('refresh')}</button>\n <button type=\"button\" className={styles.button}\n onClick={() => { location.href = `/api/engram/export?scope=${scope}&format=markdown` }}>{t('exportMd')}</button>\n <button type=\"button\" className={styles.button}\n onClick={() => { location.href = `/api/engram/export?scope=${scope}&format=json` }}>{t('exportJson')}</button>\n </div>\n <div className={styles.cards}>\n {(stats ?? []).map(part => (\n <button key={part.scope} type=\"button\" className={styles.card}\n style={part.scope === scope ? { borderColor: 'var(--dsw-alias-brand-primary)' } : undefined}\n onClick={() => { setScope(part.scope); setOffset(0) }}>\n <b>{part.stats.total}</b>\n <span>{`${t(SCOPE_KEY[part.scope])} · ${t('cardActive', { n: part.stats.active })} · ${t('signalRatio', { n: Math.round(part.stats.signalRatio * 100) })}`}</span>\n </button>\n ))}\n </div>\n <div className={styles.filters}>\n <select className={styles.input} value={status}\n onChange={event => { setStatus(event.target.value); setOffset(0); setExpanded(null) }}>\n <option value=\"all\">{t('allStatuses')}</option>\n <option value=\"active\">{t('statusActive')}</option>\n <option value=\"archived\">{t('statusArchived')}</option>\n <option value=\"forgotten\">{t('statusForgotten')}</option>\n </select>\n <select className={styles.input} value={kind}\n onChange={event => { setKind(event.target.value); setOffset(0); setExpanded(null) }}>\n <option value=\"all\">{t('allKinds')}</option>\n {KINDS.map(option => <option key={option} value={option}>{kindLabel(t, option)}</option>)}\n </select>\n <input className={styles.input} placeholder={t('searchPlaceholder')} value={q}\n onChange={event => { setQ(event.target.value.trim()); setOffset(0); setExpanded(null) }} />\n </div>\n {error !== null && <div className={styles.empty}>{t('loadFailed', { msg: error })}</div>}\n {error === null && list !== null && list.records.length === 0 && (\n <div className={styles.empty}>{t('empty')}</div>\n )}\n {(list?.records ?? []).map(record => (\n <div key={record.id} className={styles.item}>\n <div className={styles.row1}>\n <span className={`${styles.badge} ${styles[record.status]}`}>{t(STATUS_KEY[record.status])}</span>\n <span className={styles.badge}>{kindLabel(t, record.kind)}</span>\n <span className={styles.badge}>{t(SCOPE_KEY[record.scope])}</span>\n </div>\n <div className={styles.content}>{record.content}</div>\n <div className={styles.meta}>\n <span>{`${t('importance')} ${record.importance.toFixed(2)}`}</span>\n <span>{`${t('confidence')} ${record.confidence.toFixed(2)}`}</span>\n <span>{t('accessCount', { n: record.accessCount })}</span>\n <span>{sourceLabel(t, record)}</span>\n <span>{fmtTime(record.createdAt)}</span>\n </div>\n <div className={styles.ops}>\n <button type=\"button\" className={styles.button}\n onClick={() => { toggle('review', record) }}>{t('detail')}</button>\n <button type=\"button\" className={styles.button}\n onClick={() => { toggle('edit', record) }}>{t('edit')}</button>\n {record.status === 'active'\n ? <button type=\"button\" className={styles.button}\n onClick={() => { act('forget', record) }}>{t('forget')}</button>\n : <button type=\"button\" className={styles.button}\n onClick={() => { act('restore', record) }}>{t('restore')}</button>}\n </div>\n {expanded !== null && expanded.record.id === record.id && (\n <div className={styles.expand}>\n {expanded.kind === 'review'\n ? <ReviewBody t={t} recordId={record.id} scope={record.scope} />\n : <EditForm t={t} record={record} onClose={() => { setExpanded(null) }}\n onSaved={() => { setExpanded(null); reload() }} />}\n </div>\n )}\n </div>\n ))}\n <div className={styles.pager}>\n <button type=\"button\" className={styles.button} disabled={offset === 0}\n onClick={() => { setOffset(Math.max(0, offset - PAGE_SIZE)) }}>{t('prevPage')}</button>\n <span>{t('pagerInfo', { page, pages, total: list?.total ?? 0 })}</span>\n <button type=\"button\" className={styles.button}\n disabled={list === null || offset + PAGE_SIZE >= list.total}\n onClick={() => { setOffset(offset + PAGE_SIZE) }}>{t('nextPage')}</button>\n </div>\n </div>\n )\n}\n","/**\n * 「记忆库」client 词典:zh/en 键并集必须完全一致(类型化注册的编译约束)。\n * 数据层英文枚举(status/kind/op 等)只在显示层映射,存储值保持英文不变。\n * @module @kenz1117/dsh-engram/client/locales\n */\n\n/** 词典命名空间(同时用作 LocaleNamespaceMap 的声明合并键)。 */\nexport const NS = 'engram'\n\n/** 「记忆库」全部界面文案键。 */\nexport type EngramKey =\n | 'nav'\n | 'scopeUser'\n | 'scopeProject'\n | 'refresh'\n | 'exportMd'\n | 'exportJson'\n | 'allStatuses'\n | 'allKinds'\n | 'searchPlaceholder'\n | 'statusActive'\n | 'statusArchived'\n | 'statusForgotten'\n | 'kindFact'\n | 'kindPreference'\n | 'kindDecision'\n | 'kindEpisode'\n | 'kindSkill'\n | 'labelContent'\n | 'labelKind'\n | 'labelStatus'\n | 'importance'\n | 'confidence'\n | 'accessCount'\n | 'sourceExplicit'\n | 'sourceSession'\n | 'round'\n | 'cardActive'\n | 'signalRatio'\n | 'detail'\n | 'edit'\n | 'forget'\n | 'restore'\n | 'empty'\n | 'loadFailed'\n | 'loading'\n | 'pagerInfo'\n | 'prevPage'\n | 'nextPage'\n | 'cancel'\n | 'saveWithHint'\n | 'detailAttributes'\n | 'detailSource'\n | 'detailRelations'\n | 'detailOperations'\n | 'relSupersededBy'\n | 'relSupersedes'\n | 'relContradicts'\n | 'relRelated'\n | 'opWrite'\n | 'opUpdate'\n | 'opForget'\n | 'opRestore'\n | 'opDecay'\n | 'opIngestRequest'\n | 'opDistillRequest'\n\n/** 中文词典(宿主默认语言)。 */\nexport const zh: Record<EngramKey, string> = {\n nav: '记忆库',\n scopeUser: '用户级',\n scopeProject: '项目级',\n refresh: '刷新',\n exportMd: '导出 MD',\n exportJson: '导出 JSON',\n allStatuses: '全部状态',\n allKinds: '全部种类',\n searchPlaceholder: '按内容搜索…',\n statusActive: '生效中',\n statusArchived: '已归档',\n statusForgotten: '已遗忘',\n kindFact: '事实',\n kindPreference: '偏好',\n kindDecision: '决策',\n kindEpisode: '事件',\n kindSkill: '技能',\n labelContent: '内容',\n labelKind: '种类',\n labelStatus: '状态',\n importance: '重要性',\n confidence: '置信',\n accessCount: '访问 {n} 次',\n sourceExplicit: '显式保存',\n sourceSession: '来源 {id}',\n round: '第 {n} 轮',\n cardActive: '生效 {n}',\n signalRatio: '信噪比 {n}%',\n detail: '详情',\n edit: '编辑',\n forget: '遗忘',\n restore: '恢复',\n empty: '没有符合条件的记忆',\n loadFailed: '加载失败:{msg}',\n loading: '加载中…',\n pagerInfo: '第 {page} / {pages} 页 · 共 {total} 条',\n prevPage: '上一页',\n nextPage: '下一页',\n cancel: '取消',\n saveWithHint: '保存(旧条目归档)',\n detailAttributes: '属性',\n detailSource: '来源',\n detailRelations: '关系',\n detailOperations: '最近操作',\n relSupersededBy: '被取代',\n relSupersedes: '取代',\n relContradicts: '矛盾',\n relRelated: '关联',\n opWrite: '写入',\n opUpdate: '更新',\n opForget: '遗忘',\n opRestore: '恢复',\n opDecay: '衰减归档',\n opIngestRequest: '摄取请求',\n opDistillRequest: '蒸馏请求',\n}\n\n/** 英文词典。 */\nexport const en: Record<EngramKey, string> = {\n nav: 'Memory Library',\n scopeUser: 'User',\n scopeProject: 'Project',\n refresh: 'Refresh',\n exportMd: 'Export MD',\n exportJson: 'Export JSON',\n allStatuses: 'All statuses',\n allKinds: 'All kinds',\n searchPlaceholder: 'Search content…',\n statusActive: 'active',\n statusArchived: 'archived',\n statusForgotten: 'forgotten',\n kindFact: 'fact',\n kindPreference: 'preference',\n kindDecision: 'decision',\n kindEpisode: 'episode',\n kindSkill: 'skill',\n labelContent: 'Content',\n labelKind: 'Kind',\n labelStatus: 'Status',\n importance: 'Importance',\n confidence: 'Confidence',\n accessCount: '{n} accesses',\n sourceExplicit: 'Explicitly saved',\n sourceSession: 'Source {id}',\n round: 'round {n}',\n cardActive: '{n} active',\n signalRatio: 'signal {n}%',\n detail: 'Details',\n edit: 'Edit',\n forget: 'Forget',\n restore: 'Restore',\n empty: 'No matching memories',\n loadFailed: 'Failed to load: {msg}',\n loading: 'Loading…',\n pagerInfo: 'Page {page} / {pages} · {total} records',\n prevPage: 'Prev',\n nextPage: 'Next',\n cancel: 'Cancel',\n saveWithHint: 'Save (archives old entry)',\n detailAttributes: 'Attributes',\n detailSource: 'Source',\n detailRelations: 'Relations',\n detailOperations: 'Recent operations',\n relSupersededBy: 'Superseded by',\n relSupersedes: 'Supersedes',\n relContradicts: 'Contradicts',\n relRelated: 'Related',\n opWrite: 'write',\n opUpdate: 'update',\n opForget: 'forget',\n opRestore: 'restore',\n opDecay: 'decay',\n opIngestRequest: 'ingest-request',\n opDistillRequest: 'distill-request',\n}\n","/**\n * dsh-engram client half: 注册设置页「记忆库」section。\n * 数据经回环 API(/api/engram/*)读取与操作,与 host 半的路由对齐。\n * 文案经宿主 locale 服务(zh/en 词典,语言切换由渲染器自动重渲染)。\n * @module @kenz1117/dsh-engram/client\n */\n\nimport type { Context } from '@deepseek-ai/cordis'\n// Type-only: ctx.locale 服务声明(宿主 locale 插件的 Context merge)。\nimport type {} from '@deepseek-ai/dsh-client-locale/client'\n// Type-only: ctx.slots(renderer 的 SlotMap 渲染接线)。\nimport type {} from '@deepseek-ai/dsh-client-ui-renderer/client'\n// Type-only: settings.section 槽位的 SlotMap merge(ui-settings 契约)。\nimport type {} from '@deepseek-ai/dsh-client-ui-settings/client'\n// Type-only: LocaleNamespaceMap 声明合并目标(t 席位的键类型检查)。\nimport type {} from '@deepseek-ai/dsh-client-ui-slots'\nimport { EngramSection } from './EngramPanel.tsx'\nimport { NS, en, zh, type EngramKey } from './locales.ts'\n\ndeclare module '@deepseek-ai/dsh-client-ui-slots' {\n interface LocaleNamespaceMap {\n [NS]: EngramKey\n }\n}\n\n/** 必需服务:slots 渲染接线 + locale 词典注册。 */\nexport const inject = ['slots', 'locale']\n\n/**\n * Client plugin body: 注册 zh/en 词典与设置页「记忆库」section(order 20,\n * 排在 general/models/plugins 之后)。\n * @param ctx - client root context.\n */\nexport function apply(ctx: Context): void {\n ctx.effect(() => ctx.locale.register(NS, { zh, en }), 'dsh-engram: dictionaries')\n const t = ctx.locale.bind(NS)\n ctx.slots.inject('settings.section', () => ctx.slots.register({\n name: 'settings.section',\n id: 'engram',\n order: 20,\n label: () => t('nav'),\n locale: NS,\n }, EngramSection))\n}\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@kenz1117/dsh-engram",
3
3
  "description": "Cross-session long-term memory for DeepSeek Harness: dual-scope SQLite memory graph, hybrid retrieval, provenance audit, and a knowledge flywheel.",
4
- "version": "0.4.3",
4
+ "version": "0.4.5",
5
5
  "publishConfig": {
6
6
  "access": "public"
7
7
  },
@@ -31,7 +31,9 @@
31
31
  "patch": "./cordis.patch.yml"
32
32
  },
33
33
  "client": {
34
- "inject": [],
34
+ "inject": [
35
+ "@deepseek-ai/dsh-client-locale"
36
+ ],
35
37
  "platform": "web"
36
38
  }
37
39
  },
@@ -44,6 +46,16 @@
44
46
  "backfill": "node scripts/backfill-embeddings.mjs"
45
47
  },
46
48
  "license": "MIT",
49
+ "keywords": [
50
+ "dsh-plugin",
51
+ "deepseek-harness",
52
+ "dsh",
53
+ "memory",
54
+ "cross-session",
55
+ "sqlite",
56
+ "embeddings",
57
+ "agent"
58
+ ],
47
59
  "engines": {
48
60
  "node": "^22.19.0 || >=24.0.0"
49
61
  },
@@ -29,8 +29,10 @@ const LINKED_PACKAGES = [
29
29
  ['@deepseek-ai/dsh-system-prompt', 'packages/core/system-prompt'],
30
30
  ['@deepseek-ai/dsh-session', 'packages/core/session'],
31
31
  ['@deepseek-ai/dsh-host-webserver', 'packages/host/webserver'],
32
+ ['@deepseek-ai/dsh-client-locale', 'packages/client/locale'],
32
33
  ['@deepseek-ai/dsh-client-ui-settings', 'packages/client/ui-settings'],
33
34
  ['@deepseek-ai/dsh-client-ui-renderer', 'packages/client/ui-renderer'],
35
+ ['@deepseek-ai/dsh-client-ui-slots', 'packages/client/ui-slots'],
34
36
  ['@deepseek-ai/dsh-llm', 'packages/llm/llm'],
35
37
  ['@deepseek-ai/dsh-agent', 'packages/core/agent'],
36
38
  ['@deepseek-ai/dsh-scope', 'packages/core/scope'],