@avantf/dsh-mem 0.6.2 → 0.6.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # @avantf/dsh-mem
2
2
 
3
- DSH(DeepSeek Harness)原生记忆插件:给 agent 一份可长期检索的**记忆**和一个**文档知识库**,
4
- 以进程内 Cordis 插件的形式提供 8 个模型工具、三段用法提示和两个主窗口标签页。
3
+ DSH(DeepSeek Harness)原生记忆插件:给 agent 一份可长期检索的**记忆**和一个**文档知识库**——
4
+ 它跨会话记得你说过的事,也能把文档收进库里按语义查回来。装上后提供 8 个模型工具、三段用法提示
5
+ 和两个主窗口标签页(记忆 / 知识)。
5
6
 
6
7
  ## 主要功能
7
8
 
@@ -13,216 +14,110 @@ DSH(DeepSeek Harness)原生记忆插件:给 agent 一份可长期检索的
13
14
  - `kb_add` / `kb_list` / `kb_remove` / `kb_reindex`:新增文档(文本 / 文件 / URL,或批量导入目录,
14
15
  同名拒绝)、列表(带出受管 `.md` 的绝对路径与切片详情)、删除、重建索引;
15
16
  **改一篇既有文档就是改那份 `.md`**(用文件工具改,索引自动跟随)
16
- - `kb_query`:跨「记忆事实 + 文档切片」的统一检索,结果带来源标注
17
- - **混合检索 + 联合融合**:库内三路(语义 / FTS bm25 / 实体 Jaccard,另有 HRR 实体探查腿),
18
- 跨库在合并候选池上做联合归一化——记忆与文档切片因此处于同一分数刻度。
19
- - **记忆是活的**:活跃使用日时钟,结算 / TTL / 遗忘 / 闲置 / 清理五段生命周期,去重与修订链,
20
- 写入即做的矛盾检测;派生状态(实体 / 三元组 / HRR bundle)跟着规则版本走,可批量重抽。
21
- - **文档知识库**:Markdown 标题感知分块、摄入期抽实体(检索期零重扫)、可增量重建索引;
22
- `domain` 受 `knowledge.domains` 清单约束(默认 `design/api/ops/research/notes`,显式 `[]` = 不限制),
23
- `source` 可留空(缺省 `default`)。可直接摄入文本与 **PDF**(抽取文本层,中文 CID 字体可用;
24
- 扫描件会明确报错),并把 **pandoc 能读的格式**(`.docx`/`.odt`/`.epub`/`.html`/`.tex`/`.rst`/`.csv`
25
- 等)与 **`.xlsx`** 先转成 Markdown 再入库(用了哪条转换器随 `converter` 回报且带版本,如
26
- `pandoc-3.11`;没能带过来的内容随 `warnings` 透出)。其他二进制(图片 / pptx / 旧版
27
- `.doc`/`.xls`/`.ppt` / 可执行文件)会被拒绝并说明检测到的类型;GBK/GB18030 等中文旧编码会自动解码
28
- 并在结果里标出 `encoding`。
29
- - **知识可维护**:每篇文档在 `~/.avantf/knowledge/docs/<domain>/<source>/<title>.md` 留一份可编辑副本
30
- (frontmatter 记着它属于哪篇文档)——用你自己的编辑器改,插件会在**其后任一工具调用结束时**自动把改动同步
31
- 回索引(先一次 `stat` 判漂移、再用正文哈希裁决,只重摄入真正改过的),「知识」页的「重新摄入」是手动兜底;
32
- 删除文档会连同这份副本一起删除,`source_uri` 指向的原文件始终不被改动。
33
- - **可插拔、可降级**:语义后端 / 向量库都走内部注册表;模型、hnswlib、分词器任一缺失都自动降级
34
- (FTS + 实体 / numpy 向量库 / 正则抽取),不会因此退出。
35
- - **0.5.0 起不再有结果重排**:`Reranker` 接口、`rerank.*` 配置键、公开面的 `registerReranker` 与结果/统计里的
36
- `rerank_*` 字段已整体移除(旧配置里的 `rerank:` 段会被当作未知键告警并忽略);可注册的检索面只剩语义后端与向量库。
37
- - **两个主窗口标签页**(排在 对话 / 轨迹 之后):**记忆**(浏览、编辑、归档恢复、反馈、矛盾裁决)与
38
- **知识**(文档列表,每行可查看切片 / 编辑 / 打开目录 / 重新摄入 / 删除;另有跨库查询与入库——
17
+ - `kb_query`:跨「记忆事实 + 文档切片」的统一检索,结果带来源标注,可据此引用原文
18
+ - **记忆与知识一起检索**:对记忆事实与文档切片做统一排序,两类结果处在同一分数刻度上,
19
+ 所以"我之前定过的约定"和"文档里怎么写的"能一次问出来。
20
+ - **记忆是活的**:按活跃使用结算,支持有效期、遗忘、去重与修订链;同一主题的改动会被识别成
21
+ 对旧事实的**修订**而不是简单叠加,写入时顺带做矛盾检测。
22
+ - **资料随手入库**:文本、本地文件、目录、URL 都能收;Markdown 按标题分块,PDF、`.docx`、`.xlsx`
23
+ 等常见格式也能直接入库(转换过程有问题会明确回报,不静默丢内容)。
24
+ - **知识可维护**:每篇文档在数据目录里留一份可编辑的 Markdown 副本,用你自己的编辑器改,
25
+ 插件会在其后自动把改动同步回索引;删除文档只删这份副本,你原来的文件不动。
26
+ - **两个主窗口标签页**(排在 对话 / 轨迹 之后):**记忆**(浏览、编辑、归档恢复、反馈、矛盾裁决)
27
+ 与**知识**(文档列表,每行可查看切片 / 编辑 / 打开目录 / 重新摄入 / 删除;另有跨库查询与入库——
39
28
  URL / 本地文件 / 本地目录 / 粘贴文本共用一个输入)。
40
29
  - 系统提示词里只加**三段**用法提示(何时该主动记忆、何时该先查记忆、何时该先查知识库 / 怎么改一篇
41
- 既有文档),不解释内部保留策略。
30
+ 既有文档)。
42
31
 
43
32
  ## 接入 dsh
44
33
 
45
34
  ```bash
46
- # 1) 装这两个包:插件本身,以及家族底座。底座是插件的 peer;不开 autoInstallPeers 的包管理器
47
- # (pnpm / yarn)不会跟着装上,所以要显式装。
48
- # 版本号建议写死:pnpm ≥ 10 的 minimumReleaseAge 会避开刚发布的版本,解析到的旧版本可能不认识
49
- # 当前宿主(它的 peer 区间不含这个 dsh),那一单会被宿主的安装门禁直接拒绝并把 profile 回滚。
50
- dsh plugin --profile <PROFILE> add @avantf/dsh-plugin-base@<version>
35
+ # 把插件装进你要用的 profile(依赖会自动装好,不用管别的)
51
36
  dsh plugin --profile <PROFILE> add @avantf/dsh-mem@<version>
52
37
 
53
- # 2) 原生依赖只剩**可选**的加速件(分词 / 向量索引 / 嵌入模型)。它们缺失只会降级检索质量,
54
- # 不影响记忆库本身——存储层用的是运行时自带的 node:sqlite,没有需要编译的数据库模块。
55
- cd ~/.dsh/profiles/<PROFILE>
56
- pnpm approve-builds --all # 勾选 nodejieba / hnswlib-node / onnxruntime,可按需
38
+ # 然后重启 dsh 生效
57
39
  ```
58
40
 
59
- ```yaml
60
- # 3) 挂载插件:装完通常什么都不用做。
61
- #
62
- # 本包声明了组合包(dsh.bundle.patch → 随包的 cordis.patch.yml),所以 `dsh plugin add` 装它时会
63
- # 顺手把这个包选进 profile 的 dsh.profile.bundles(实测),挂载行由随包的 patch 插入;DSH Desktop
64
- # 的「插件」页走的也是这条。只有两种情况才需要自己写那一行:
65
- # · 用 npm / yarn 装(它们不认识 dsh.profile.bundles);
66
- # · 手工编辑 profile 的包清单,而没有把本包选进 bundles。
67
- # 两条路都做也不会挂两次:当前 dsh(0.1.7 / 0.2.0 线)的 loader 按条目 id 去重,只挂载一次(实测);
68
- # 但 0.1.5 线(仍在本包 peer 区间内)上重复的行 id 会让 profile 树加载失败——所以始终二选一。
69
- # · 前提:自动选中只在 `dsh plugin add` **跑完**那一刻发生。若它中途被构建提示(pnpm 的
70
- # approve-builds)拦下(ERR_PNPM_IGNORED_BUILDS、非零退出),依赖装上了但组合包**不会被选中**,
71
- # 事后再 add / install 也不会补选——先答完提示再装,或到 DSH Desktop 的「插件」页启用这个组合包。
72
- #
73
- # 手写的那一行,放在 ~/.dsh/profiles/<PROFILE>/cordis.patch.yml(不存在就新建):
74
- - insert:
75
- - id: avantf-mem
76
- name: '@avantf/dsh-mem'
77
- config:
78
- mode: cordis # 进程内插件
79
- # dataHome 建议省略(默认 ~/.avantf)。它是配置值,会被 AVANTF_HOME 压过。
80
- ```
81
-
82
- ```bash
83
- # 4) 重启 dsh(宿主半边需重启,浏览器半边会热重载)
84
- dsh web
85
- ```
86
-
87
- ### DSH Desktop
41
+ 版本号可以不写,但建议指定一个明确版本。
88
42
 
89
- Desktop 用的是同一套 profile(profile 名 `desktop`),差别只有三点:
90
-
91
- - **界面上只能装组合包**。Desktop 的「插件」页与 `plugin_manager` 只接受声明了 `dsh.bundle.patch`
92
- 的包(本包已声明):在页面上装它,它会把这个包选进 `dsh.profile.bundles`,并应用随包的
93
- `cordis.patch.yml`——不用手写挂载行。
94
- - **用它自带的 dsh**:`<安装目录>/resources/runtime/cli/bin/dsh.cmd`(或在设置里把 `dsh` 装进 PATH)。
95
- - **重启应用**才生效:Desktop 的宿主半边与浏览器半边一起启动,不像 Web 那样热更。
96
-
97
- Desktop 的宿主进程是 **Electron**(`NODE_MODULE_VERSION` 与同版本 Node 不同)。这正是本插件不依赖任何
98
- SQLite 绑定的原因:存储层用运行时自带的 `node:sqlite`,它就在跑插件的那个进程里,没有预编译产物要匹配、
99
- 也不需要用户装 C++ 工具链。启动日志里 `sqlite=node:sqlite <版本>` 就是它;两个宿主写的是同一个 SQLite
100
- 文件格式,换宿主不需要迁移数据。
101
-
102
- - 插件的 `@deepseek-ai/*` peer 由宿主提供(profile 上层的运行时解析表),`dsh plugin add` 不会另装一份。
103
- **不要在 profile 里再装** `cordis` / `schemastery` / `dsh-tools`——两份对象身份会让工具与 typert
104
- 注册表对不上。
105
- - `zod` 同样是**宿主提供的 required peer**(区间 `>=4.4.3 <5`,宿主的 dsh 自带):工具的 wire 与参数
106
- schema 都用**这一份**。**不要在 profile 里单独装一份 `zod`**——两个副本的 schema 类型身份不同,
107
- 跨副本传 schema 对象会在运行期对不上。
43
+ **DSH Desktop**:在「插件」页里安装同一个包即可,装完重启应用;不用动命令行。
108
44
 
109
45
  ## 怎么用
110
46
 
111
- 工具是给模型用的,通常不用你手写;这里是最常见的几种用法。
47
+ 工具是给模型用的,通常不用你手写;用日常说法提要求就行。最常见的几种:
112
48
 
113
49
  ```text
114
50
  记住这条:发布窗口是每周四 20:00;验收标准是灰度零回滚。
115
51
  ```
116
52
 
117
53
  ```text
118
- 我之前定过的数据目录约定是什么?(→ mem_recall,返回命中事实与来源)
54
+ 我之前定过的数据目录约定是什么?(返回命中事实与来源)
119
55
  ```
120
56
 
121
57
  ```text
122
- 把 ~/docs/cgroup-v2.pdf 收到知识库的 ops 域下。(→ kb_add source_uri,转换器与告警随结果回报)
58
+ 把 ~/docs/cgroup-v2.pdf 收到知识库的 ops 域下。
123
59
  ```
124
60
 
125
61
  ```text
126
- 看看知识库里关于「内存保护」的原文。(→ kb_query,结果带 source_ref,可据此引用)
62
+ 看看知识库里关于「内存保护」的原文。
127
63
  ```
128
64
 
129
- 改一篇已经入库的文档:先用 `kb_list` 拿到那份 `.md` 的路径,再用**任意文件工具**改它——改完不必手动回填,
130
- 插件会在其后任一工具调用结束时自动同步(「重新摄入」是手动兜底);或直接对模型说"更新这篇文档"。
65
+ ```text
66
+ 把「每周四发布」改成「每周三发布」,并说明依据。
67
+ ```
68
+
69
+ 改一篇已经入库的文档:让模型"更新这篇文档"即可;也可以用任意文件工具直接编辑那份受管
70
+ `.md`(先用 `kb_list` 拿到路径),改完会自动同步,「知识」页的「重新摄入」是手动兜底。
131
71
 
132
72
  常见维护动作:
133
73
 
134
74
  ```text
135
- 检查记忆库的检索与索引状态。(→ mem_admin trust_diagnose / vectors_diagnose / stats)
136
- 把矛盾检测的积压扫一遍。(→ mem_admin contradict_check,再对每条判 true_positive / false_positive)
75
+ 检查记忆库的检索与索引状态。
76
+ 把矛盾检测的积压扫一遍,逐条裁决。
137
77
  ```
138
78
 
139
- ### 相关性门槛(严格 / 宽松)
79
+ ### 相关性门槛:严格 / 宽松
140
80
 
141
- 检索对每条腿都有一条**绝对**相关性门槛(打在每条腿的原始分上、融合之前):语义余弦
142
- `min_semantic_similarity`(默认 `0.5`)、FTS 腿逐行命中的**不同查询词元数** `min_fts_terms`(默认 `2`)、
143
- 实体 Jaccard `min_jaccard`(默认 `0.2`)——分数等于门槛保留,`0` = 关闭该腿(改在
144
- `~/.avantf/configs/common.yaml` 的 `retriever` 节)。
81
+ 「记忆」「知识」页的查询面板有**严格 / 宽松**选择(工具面的 `mem_recall` / `kb_query` 也接受
82
+ `floors: 'strict' | 'loose'`):
145
83
 
146
- - **两个档位(UI 选择器 / `floors` 参数)**:「知识」「记忆」页的查询面板有「严格 / 宽松」选择,`mem_recall.search`
147
- 与 `kb_query` 也接受 `floors: 'strict' | 'loose'`。`strict` = 用配置值、**不**自动放宽(面板默认它,好让空结果
148
- 有明确说法);`loose` = 直接宽松档,换成一组仍有绝对底线的值(`0.40 / ⌈词元数/2⌉ / 0.15`)——所以宽松档
149
- 一样答得出"确实没有相关结果",不是"无门槛"。
150
- - **不传 `floors` 时的默认策略:严格档为空就自动放宽一次**:先按严格档跑;**仅当**它一条都没命中、且确实有
151
- 条目被门槛丢掉时,才用宽松档补跑一遍。只有宽松档真的产出结果才替换答案,结果里置 `relaxed: true`,面板显示
152
- 「(已自动放宽门槛)」;放宽也救不回时**原样返回严格档结果**(含"被门槛丢掉 N 条"的报告)。跨库查询只对
153
- **合并后**的结果做这个决定,免得一条空库把放宽后的尾部混进另一条库的严格命中。
154
- - **可解释性**:结果带生效门槛 `floors` 与每腿丢弃数 `dropped_by_floor`,所以「门槛挡掉的空」与「本来就没有」
155
- 能分开说——面板因此分别给出「严格门槛丢弃了 N 条,可切「宽松」重试」与「(宽松档下)确实没有相关结果」。
84
+ - **严格**(面板默认):只有明确够相关的才返回,所以"没有结果"是一个可信的答案。
85
+ - **宽松**:门槛更低,用来找回边角相关的条目——仍有一条底线,不是无门槛。
86
+ - **不选时**:先按严格跑;严格档一条都没命中、且确实有条目被门槛挡掉时,自动用宽松档补跑一次,
87
+ 结果会标明「已自动放宽门槛」。门槛挡掉的空与真的没有,面板和结果里的数字能区分开。
156
88
 
157
89
  ## 配置、数据与提示词
158
90
 
159
- - **数据目录**:默认 `~/.avantf`。记忆库、知识库(含 `knowledge/docs` 下的受管文档副本)与模型缓存
160
- 都在其中。
91
+ - **数据目录**:默认 `~/.avantf`。记忆库、知识库(含受管文档副本)与模型缓存都在其中;
92
+ 环境变量 `AVANTF_HOME` 可以改变它。
161
93
  - **配置**:`~/.avantf/configs/` 下的三份 YAML —— `common.yaml`(公共项,缺失时按注释模板自动创建)、
162
- `memory.yaml`、`knowledge.yaml`;只写你要改的项,其余取内置默认。`dataHome` 也可以写在 profile 的
163
- config 里,但环境变量 `AVANTF_HOME` 会压过它。
164
- - **提示词文件**:`~/.avantf/prompts/mem-memory-usage.md`、`mem-knowledge-usage.md`、`mem-kb-edit.md`。
165
- 缺失或为空时插件写入默认内容,**只在启动时读一次**,改完重启生效。正文可以**按领域改写**——内置默认
166
- 刻意不出现任何领域名词(它同时服务开发、投研、日常等场景),只给判据与"事实型 / 规则型"两种形状:
94
+ `memory.yaml`、`knowledge.yaml`。只写你要改的项,其余取内置默认。
95
+ - **提示词文件**:`~/.avantf/prompts/` 下的 `mem-memory-usage.md`、`mem-knowledge-usage.md`、
96
+ `mem-kb-edit.md`。缺失或为空时插件写入默认内容,**只在启动时读一次**,改完重启生效。
97
+ 可以按自己的领域改写:内置默认刻意不出现任何领域名词,只给判据与两种形状——
167
98
  - 事实型:`用户不吃香菜;订餐默认备注不要香菜。`(谁 / 什么范围 / 偏好或要求或术语是什么)
168
- - 规则型:`只有「注销式回购」减少总股本,库存股回购不减少——比较 EPS 影响时要区分。`(当……时,应……,附条件与例外)
169
- - 反例(不该写成事实):一次调查的过程叙述、数据表、命令与链接——依据属于知识库文档,记忆里只留**结论 + 位置**
170
- (路径 / 来源 / 名称);敏感信息同理,只记存放位置,不记明文。
171
- - **自定义过提示词文件的话,升级时需手动补**:提示词**文件优先于内置正文**(文件存在且非空时内置正文完全不参与渲染)。
172
- 0.6.0 起内置正文多了三条行为口径,若你改过 `mem-memory-usage.md`,请把下面三行追加到你的文件末尾;没改过则无需操作:
173
- ```text
174
- 记忆是过去的记录、不是当下指令,无关时可完全忽略。
175
- 同一主题用 `update` 纠正并附核实依据,不同事实新增。
176
- 不要把 `recall` 结果原样回记。
177
- ```
178
- - **可选:中文时间窗检索腿**(0.6.0 起,**默认关闭**)。它在查询含中文时间表达时,把**事件时间**(写入时用
179
- `event_date` 给出)落在窗口内的活跃事实作为额外一条腿参与融合——只增加候选、不删已有命中,也不会回退到写入时间。
180
- 在 `~/.avantf/configs/common.yaml` 打开:
181
- ```yaml
182
- retriever:
183
- time_window: true
184
- ```
185
- 打开前先看 `mem_admin stats` 的 `validity.coverage`(带事件时间的事实占比):覆盖率接近 0 时这条腿等于没开。
186
-
187
- - **语义模型**:默认从 `hf-mirror.com` 下载并缓存,也可指向本地模型目录离线运行。
188
- - **升级前先备份 `~/.avantf`**:数据库迁移是单向的,用旧版本打开新库会被明确拒绝。
189
-
190
- ## 升级 / 卸载 / 换版本
99
+ - 规则型:`只有「注销式回购」减少总股本,库存股回购不减少——比较 EPS 影响时要区分。`
100
+ - 不适合写进记忆:一次调查的过程叙述、数据表、命令与链接——依据归知识库文档,记忆里只留
101
+ **结论 + 位置**(路径 / 来源 / 名称);敏感信息同理,只记存放位置,不记明文。
102
+ - **升级前建议先备份 `~/.avantf`**:数据库迁移是单向的,用旧版本打开新库会被明确拒绝。
103
+
104
+ ## 升级 / 卸载
191
105
 
192
106
  ```bash
193
- # 升级(或重装)到某个版本:同一条命令,换版本号即可
107
+ # 升级:换版本号重装同一个包
194
108
  dsh plugin --profile <PROFILE> add @avantf/dsh-mem@<version>
195
109
 
196
- # 卸载:先删掉 profile 依赖(这一步同时会取消该组合包的选中)
110
+ # 卸载
197
111
  dsh plugin --profile <PROFILE> remove @avantf/dsh-mem
198
- # 再清掉挂载项:用组合包装的,包名已随上一步从 profile 的 `dsh.profile.bundles` 里去掉,无需再动;
199
- # 自己手写过那一行的,删掉 ~/.dsh/profiles/<PROFILE>/cordis.patch.yml 里那段 `- id: avantf-mem`
200
- #(该文件顶层必须是 YAML 数组:删空就写 `[]`,否则 dsh 启动会拒绝加载)。
201
- # DSH Desktop 的「插件」页上卸载会连着取消选中与依赖一起做。
202
112
  ```
203
113
 
204
- 两者都需要重启 dsh 才生效。卸载**不动 `~/.avantf`**:记忆库、知识库、模型缓存全部保留。
114
+ 两者都要重启 dsh 生效。卸载**不动 `~/.avantf`**:记忆库、知识库、模型缓存全部保留。DSH Desktop 在
115
+ 「插件」页上卸载。
205
116
 
206
117
  ## 环境要求
207
118
 
208
- - **Node `>=22.15.0 <23 || >=23.11.0`**、**pnpm**。记忆与知识两个库都建在运行时自带的 `node:sqlite`
209
- 上,这个区间是它的两个下限的交集:模块免 flag(22.13 / 23.4)与"忽略语句未用到的命名参数"
210
- (22.15 / 23.11)。低于该下限(或该构建缺 FTS5)时插件照常挂载,但 8 个工具都回
211
- `memory unavailable` 并给出原因,不会拖垮宿主启动
212
- - `nodejieba` / `hnswlib-node` / `@huggingface/transformers` 都可选:缺失时分别降级为正则抽取 /
213
- numpy 向量库 / 纯词法检索
214
- - 插件自包含:检索与知识引擎在构建时已内联进产物,安装不需要额外的家族包。宿主提供的 peer 有两个:
215
- 底座 `@avantf/dsh-plugin-base`(安装时随插件一起装)与 `zod`(宿主的 dsh 自带那份)
216
-
217
- ## 从源码构建
218
-
219
- 构建只对着**已安装的全局 dsh**(`npm i -g @deepseek-ai/dsh`),不需要 harness 源码;在仓库根执行:
220
-
221
- ```bash
222
- pnpm install
223
- pnpm build:dsh mem # 插件两半(tsc + tsdown → lib/index.js + lib/client.js),随后自动跑挂载冒烟
224
- pnpm pack:plugin:mem # 打包成"用户安装的那一个包"(断言引擎已内联、依赖版本已落成真实范围)
225
- ```
119
+ - **Node `>=22.15.0 <23 || >=23.11.0`**、**pnpm**
120
+ - 可选的原生加速件(分词 / 向量索引 / 本地嵌入模型)缺失时只会降低检索质量,不影响使用
226
121
 
227
122
  ## 许可
228
123
 
@@ -115,6 +115,12 @@ export declare class ChunksDao {
115
115
  * This is what lets the shared repair flow read "what is stale" cheaply per slice instead of
116
116
  * re-scanning and hashing the corpus (`corpusState` reads every text; a batched migration must
117
117
  * not).
118
+ *
119
+ * The JS rule itself is `store/vector_space.ts` (`classifyVectorSpace` / `countVectorSpace`), and
120
+ * this projection is bound to it by the cross assertions in
121
+ * `test/knowledge_vector_migration.spec.ts` (the startup warning's count, the health count and the
122
+ * migration's `remaining` all have to be the same number). NOTHING here changes when the JS rule
123
+ * changes: the mirror is checked, not shared.
118
124
  */
119
125
  vectorSpaceCounts(space: string, expectedBytes: number): {
120
126
  stale: number;
@@ -8,10 +8,28 @@
8
8
  * The SQL lives here like every other aggregate. The store decides WHICH source a write carries
9
9
  * (and refuses to invent one when the caller gave none); the DAO only records and reads rows.
10
10
  */
11
+ import type { FactSourceView } from '../../../mem-contract/index.js';
11
12
  import type { Db } from '../port.js';
12
- /** The closed `kind` vocabulary, spelled once so the store and the tests share it. */
13
+ /**
14
+ * The kind axis is the CONTRACT's, not a second list: `FactSourceView['kind']` is what `admin detail`
15
+ * renders and what the writer below records, so deriving the type from it is what makes "the closed
16
+ * vocabulary" one thing. (Before, the array here and the union in `types.ts` were two independent
17
+ * spellings that happened to agree.)
18
+ */
19
+ export type FactSourceKind = FactSourceView['kind'];
20
+ /**
21
+ * The closed `kind` vocabulary, spelled once so the store and the tests share it.
22
+ *
23
+ * The `satisfies` pins every member to the contract's union; the `Exclude` below pins the other
24
+ * direction, so a kind the contract ADDS fails to compile here instead of silently never being
25
+ * recorded.
26
+ */
13
27
  export declare const FACT_SOURCE_KINDS: readonly ["session", "kb_doc", "tool", "manual"];
14
- export type FactSourceKind = (typeof FACT_SOURCE_KINDS)[number];
28
+ /**
29
+ * The FALLBACK kind: a `source_ref` without a recognized `kind:` prefix is `manual` with the
30
+ * reference kept VERBATIM (the tool contract says so, and "no shape guessing" is the point).
31
+ */
32
+ export declare const FACT_SOURCE_FALLBACK_KIND: "manual";
15
33
  /** One provenance row as the read surfaces render it. */
16
34
  export interface FactSource {
17
35
  kind: FactSourceKind;
@@ -21,10 +39,14 @@ export interface FactSource {
21
39
  * Derive `(kind, ref)` from the single `source_ref` the write face accepts.
22
40
  *
23
41
  * The tool takes ONE string (the plan adds no second parameter), so the kind axis is spelled by a
24
- * `kind:` prefix — `session:…` / `kb_doc:…` / `tool:…` — and anything else is `manual` with the
25
- * reference kept VERBATIM. No shape guessing: a bare `domain:source:title` becomes a `manual`
26
- * source whose ref is exactly what the caller wrote, which is strictly more honest than inventing
27
- * a category from how the string looks.
42
+ * `kind:` prefix — `session:…` / `kb_doc:…` / `tool:…` — and anything else, INCLUDING the fallback
43
+ * kind's own name, is `manual` with the reference kept VERBATIM. No shape guessing: a bare
44
+ * `domain:source:title` becomes a `manual` source whose ref is exactly what the caller wrote, which
45
+ * is strictly more honest than inventing a category from how the string looks.
46
+ *
47
+ * The prefix table comes from {@link FACT_SOURCE_KINDS} (see {@link FACT_SOURCE_PREFIX_KINDS}) rather
48
+ * than from a hand-written alternation, which is what keeps the recognized set and the recorded set
49
+ * from being two lists.
28
50
  */
29
51
  export declare function classifySourceRef(sourceRef: string): {
30
52
  kind: FactSourceKind;
@@ -0,0 +1,25 @@
1
+ /**
2
+ * The two page/result defaults the engine falls back to when neither the caller nor the config says
3
+ * anything — spelled ONCE, because each of them used to be written in two or three places.
4
+ *
5
+ * They are the ENGINE's consumption points of numbers the CONTRACT documents:
6
+ *
7
+ * - `DEFAULT_SEARCH_LIMIT` — `query`'s own input schema carries `.default(10)`
8
+ * (`contract/src/tools.ts`), and the recall actions' descriptions say "默认 10"; the recall union
9
+ * itself is `.optional()`, so the engine is what applies it. It reaches `hybridSearch` (a
10
+ * non-finite limit) and the cross-store router.
11
+ * - `DEFAULT_LIST_LIMIT` — the documented page for `admin list` and `kb_list` ("默认 50" /
12
+ * "缺省 50", same file).
13
+ *
14
+ * The cross-layer equation is pinned by `test/hybrid.spec.ts` against the contract's parsed schema,
15
+ * so a contract default moving and this constant not moving is a red test rather than a silent
16
+ * off-by-page behaviour. (These live here rather than in the contract because `@avantf/mem-contract`
17
+ * is consumed as built output: a constant added there is only visible to this engine after the
18
+ * contract is rebuilt, which a source-only change may not assume.)
19
+ *
20
+ * @module defaults
21
+ */
22
+ /** Page of a recall/query result when the caller gave no usable `limit`. */
23
+ export declare const DEFAULT_SEARCH_LIMIT = 10;
24
+ /** Page of a listing surface (`admin list`, `kb_list`) when the caller gave no `limit`. */
25
+ export declare const DEFAULT_LIST_LIMIT = 50;
@@ -16,10 +16,12 @@
16
16
  import type { KbAddRequest, ToolEnvelope, ToolSpec } from '../mem-contract/index.js';
17
17
  import type { AvantfRuntime } from './runtime.js';
18
18
  /**
19
- * The default page for a model-facing `kb_list`.
19
+ * The default page for a model-facing `kb_list` — the SAME page the operator's `admin list` uses.
20
20
  *
21
21
  * Without one, `kb_list` on a large library read and parsed EVERY managed file (resolving a path
22
- * needs the file's frontmatter to detect collisions) to answer a question nobody asked in full.
22
+ * needs the file's frontmatter to detect collisions) to answer a question nobody asked in full. The
23
+ * two surfaces were separate literals; the contract documents both as 50, so they are one constant
24
+ * (`src/defaults.ts`).
23
25
  */
24
26
  export declare const KB_LIST_DEFAULT_LIMIT = 50;
25
27
  /**
@@ -55,22 +55,6 @@ export interface WarmOptions {
55
55
  * path keeps it, because there the caller only sees this logger.
56
56
  */
57
57
  export declare function warmModels(rt: AvantfRuntime, options?: WarmOptions, reportFinal?: boolean): Promise<boolean>;
58
- /**
59
- * Warm ONLY the embedding model: load it from `semantic.cache_dir` and mark the engine ready.
60
- *
61
- * Split out of {@link warmModels} for the plugin's envinit path. There the framework fetches the
62
- * model in the BACKGROUND, so the warm has to wait for the item to settle instead of running at
63
- * mount — and the runtime's own constructor warm is deferred for exactly that reason
64
- * (`RuntimeOptions.managedRoots` → `deferWarm`), which is what stops it racing the install and
65
- * leaving a second copy behind. While the item is unsettled NOTHING warms; this function is the
66
- * explicit "files are there now, look" step, and it still runs when the item skipped/failed so
67
- * `semantic.auto_download` can decide between the runtime's own fetch and degradation.
68
- *
69
- * @param rt - the runtime whose memory store owns the semantic backend.
70
- * @param reportFinal - log the one-line verdict (default `true`).
71
- * @returns whether the semantic path is available.
72
- */
73
- export declare function warmSemantic(rt: AvantfRuntime, reportFinal?: boolean): Promise<boolean>;
74
58
  /**
75
59
  * Wait out the startup gate — the host readiness signal (bounded by {@link WAIT_FOR_HOST_MS}) and,
76
60
  * when asked, one quiet turn of the event loop.
@@ -88,18 +72,6 @@ export declare function warmSemantic(rt: AvantfRuntime, reportFinal?: boolean):
88
72
  * @returns how long the idle gate waited (ms), or `0` when it was not requested.
89
73
  */
90
74
  export declare function awaitStartupGate(options?: WarmOptions): Promise<number>;
91
- /**
92
- * Warm ONLY the tokenizer, after the host-readiness signal and (when asked) an idle event loop.
93
- *
94
- * nodejieba loads its dictionary lazily on the FIRST `tag()` (measured ~950 ms, after which a call
95
- * is 0.1 ms), so without this the first write/query of a session pays it while the user waits. Its
96
- * cost belongs to startup, and an unavailable jieba is worth saying out loud — entity extraction
97
- * silently degrades to the regex fallback (weaker entity/jaccard legs).
98
- *
99
- * @param rt - the runtime whose logger reports the outcome.
100
- * @param options - the host-readiness signal and the idle gate (see {@link WarmOptions}).
101
- */
102
- export declare function warmTokenizer(rt: AvantfRuntime, options?: WarmOptions): Promise<void>;
103
75
  /**
104
76
  * Non-blocking embedding warm, for a caller that knows the model files are already on disk.
105
77
  *
@@ -160,48 +132,7 @@ export interface ProvisionSweepResult {
160
132
  * @returns whether the sweep ran or was skipped for incompatibility.
161
133
  */
162
134
  export declare function provisionToolchainAsync(rt: AvantfRuntime, options?: ProvisionSweepOptions): Promise<ProvisionSweepResult>;
163
- /** The artifact id, used in the startup log and in the model artifact's errors. */
164
- export declare const MODEL_ARTIFACT_ID = "model";
165
135
  /** The environment a model-artifact call runs under: the runtime that owns the model cache. */
166
136
  export interface ModelArtifactEnv {
167
137
  runtime: AvantfRuntime;
168
138
  }
169
- /** The slice of an artifact call this module needs (`@avantf/mem-provision`'s InstallContext). */
170
- interface ModelInstallContext {
171
- logger: {
172
- info(message: string): void;
173
- warn(message: string): void;
174
- error(message: string): void;
175
- };
176
- artifactEnv?: unknown;
177
- config: {
178
- auto_install: boolean;
179
- };
180
- }
181
- /**
182
- * The embedding model (plus the nodejieba tokenizer it shares its warm-up budget with) as an
183
- * ARTIFACT, so the startup sweep in `@avantf/mem-provision` is the only initialization path.
184
- *
185
- * It used to be a parallel model warm-up call beside the tools sweep, which meant two
186
- * startup mechanisms with two logging vocabularies and no single place that answered "is this host
187
- * fully provisioned?". The model has no pinned version and no managed directory of its own ON THIS
188
- * (the flat `<repo>/<file>` layout the transformers.js cache reads), so it
189
- * declares neither; its `isPresent` is `false` because "is the model warm?" is not a question that
190
- * can be answered without attempting the warm-up, and the warm-up is cheap when the cache is
191
- * already there (measured ~1.2 s, in the background). The DSH plugin uses this sweep only when the
192
- * family framework is unavailable — with the framework up, `mem:model` owns the model root instead
193
- * and this artifact is not registered.
194
- */
195
- export declare const modelArtifact: {
196
- id: string;
197
- title: string;
198
- verb: string;
199
- isPresent(): Promise<boolean>;
200
- install(ctx: ModelInstallContext): Promise<void>;
201
- verify(ctx: {
202
- logger: ModelInstallContext["logger"];
203
- }): Promise<void>;
204
- };
205
- /** Register the model artifact once per process (idempotent for a test that mounts twice). */
206
- export declare function registerModelArtifact(): void;
207
- export {};
@@ -26,8 +26,6 @@
26
26
  import { type Config, type FloorProfile, type RecallResult, type RetrievalFloorDrops, type RetrievalFloors } from '../../mem-contract/index.js';
27
27
  import { type BudgetInput, type Hit, type SemanticBackend } from '../../mem-retrieval/index.js';
28
28
  import { type FloorLeg } from './floors.js';
29
- /** The `limit` a caller gets when it passes nothing usable. */
30
- export declare const DEFAULT_SEARCH_LIMIT = 10;
31
29
  /**
32
30
  * A retrieval input the CALLER got wrong, as opposed to a backend that failed at runtime.
33
31
  *
@@ -39,8 +37,6 @@ export declare const DEFAULT_SEARCH_LIMIT = 10;
39
37
  */
40
38
  export declare class RetrievalInputError extends Error {
41
39
  }
42
- /** …and the over-fetch factor when the config does not set one (matches the contract default). */
43
- export declare const DEFAULT_OVER_FETCH_FACTOR = 5;
44
40
  /** The three weights a `RecallResult` reports. */
45
41
  export type RetrievalWeights = RecallResult['weights'];
46
42
  /** One retrieval leg's contribution to the fusion. */
@@ -0,0 +1,60 @@
1
+ import type { StaleVectorCounts } from './common.js';
2
+ /** What one persisted vector IS, in the current space. */
3
+ export type VectorSpaceVerdict = 'current' | 'stale' | 'space_stale';
4
+ /**
5
+ * A persisted vector's width, in whichever shape the CALLER already has it — the discriminant is the
6
+ * shape, never the truth:
7
+ *
8
+ * - `blob` — the raw BLOB (`Uint8Array`), for a caller that has read the bytes;
9
+ * - `byteLength` — a projected `length(semantic_vector)`, for a caller that must not read them;
10
+ * - `decoded` — an already decoded `Float32Array`, for a caller that had to decode anyway.
11
+ *
12
+ * All three answer the same question (`width === dim`), which is the point: the caller with the
13
+ * cheapest possible evidence is not forced to re-read the row in another shape. A `null`/`undefined`
14
+ * width is "no usable bytes" and classifies as `stale`, like a wrong one.
15
+ */
16
+ export type PersistedVectorWidth = {
17
+ readonly source: 'blob';
18
+ readonly vec: Uint8Array | null | undefined;
19
+ } | {
20
+ readonly source: 'byteLength';
21
+ readonly byteLength: number | null | undefined;
22
+ } | {
23
+ readonly source: 'decoded';
24
+ readonly vec: Float32Array | null | undefined;
25
+ };
26
+ /** One width probe plus the space RECORDED beside it (the second element of the rule). */
27
+ export type VectorSpaceProbe = PersistedVectorWidth & {
28
+ /** `facts.embedding_model` / `doc_chunks.embedding_model` of the same row; `null` = unrecorded. */
29
+ readonly embeddingModel: string | null | undefined;
30
+ };
31
+ /** The space the store writes into RIGHT NOW. */
32
+ export interface VectorSpaceIdentity {
33
+ /** The representation fingerprint (`vectorSpaceOf`). */
34
+ readonly space: string;
35
+ /** The width the store writes now, in floats. */
36
+ readonly dim: number;
37
+ }
38
+ /** One row's classification. */
39
+ export interface VectorSpaceClassification {
40
+ readonly verdict: VectorSpaceVerdict;
41
+ /** Of a `space_stale` verdict: the recorded id predates the representation fingerprint. */
42
+ readonly legacy: boolean;
43
+ }
44
+ /**
45
+ * Classify ONE persisted vector. Pure: no store, no database, no logging — a caller that disagrees
46
+ * with the result has to disagree with the rule, not with a copy of it.
47
+ *
48
+ * A caller-supplied vector is trusted to come from this backend, but its DIMENSION is still checked
49
+ * — the same asymmetry `legs.ts` applies to a caller-supplied `queryVector`: provenance is the
50
+ * caller's to assert, width is cheap and decides whether the bytes can be compared at all.
51
+ */
52
+ export declare function classifyVectorSpace(probe: VectorSpaceProbe, identity: VectorSpaceIdentity): VectorSpaceClassification;
53
+ /**
54
+ * {@link classifyVectorSpace} over a corpus: the `{ stale, space_stale, legacy }` triple the warning
55
+ * and the `/mem` status surface report, counted by the same rule that classified each row.
56
+ *
57
+ * `legacy` counts only rows already counted in `space_stale` (`reportStaleVectors` clamps it there
58
+ * too) — it is a REASON split, never a fourth bucket.
59
+ */
60
+ export declare function countVectorSpace(probes: readonly VectorSpaceProbe[], identity: VectorSpaceIdentity): StaleVectorCounts;
package/lib/index.js CHANGED
@@ -3777,8 +3777,19 @@ const modelArtifact = {
3777
3777
  };
3778
3778
  /** Register the model artifact once per process (idempotent for a test that mounts twice). */
3779
3779
  function registerModelArtifact() {
3780
- if (!hasArtifact("model")) registerArtifact(modelArtifact);
3780
+ if (!hasArtifact(MODEL_ARTIFACT_ID)) registerArtifact(modelArtifact);
3781
3781
  }
3782
+ //#endregion
3783
+ //#region ../core/lib/dispatch.js
3784
+ /**
3785
+ * The default page for a model-facing `kb_list` — the SAME page the operator's `admin list` uses.
3786
+ *
3787
+ * Without one, `kb_list` on a large library read and parsed EVERY managed file (resolving a path
3788
+ * needs the file's frontmatter to detect collisions) to answer a question nobody asked in full. The
3789
+ * two surfaces were separate literals; the contract documents both as 50, so they are one constant
3790
+ * (`src/defaults.ts`).
3791
+ */
3792
+ const KB_LIST_DEFAULT_LIMIT = 50;
3782
3793
  /** A document view with the managed file's path — what makes "edit it yourself" actionable. */
3783
3794
  function withFilePath(rt, doc) {
3784
3795
  return {
@@ -3821,7 +3832,7 @@ const DISPATCH = {
3821
3832
  const detail = rt.knowledge.detail(data.doc_id);
3822
3833
  return detail === null ? { error: `文档不存在:doc_id=${String(data.doc_id)}` } : withFilePath(rt, detail);
3823
3834
  }
3824
- return rt.knowledge.list(data.domain, data.source, data.limit ?? 50, data.offset).map((doc) => withFilePath(rt, doc));
3835
+ return rt.knowledge.list(data.domain, data.source, data.limit ?? KB_LIST_DEFAULT_LIMIT, data.offset).map((doc) => withFilePath(rt, doc));
3825
3836
  }),
3826
3837
  kb_remove: ((rt, data) => rt.kb({
3827
3838
  action: "remove",
@@ -7428,6 +7439,33 @@ function mergeFloorDrops(...parts) {
7428
7439
  function totalFloorDrops(drops) {
7429
7440
  return drops.semantic + drops.fts + drops.jaccard + drops.hrr;
7430
7441
  }
7442
+ //#endregion
7443
+ //#region ../core/lib/store/hybrid.js
7444
+ /**
7445
+ * The retrieval orchestration BOTH stores run.
7446
+ *
7447
+ * `MemoryStore.search` and `KnowledgeStore.search` used to carry parallel copies of this flow
7448
+ * (~80 lines each: limit normalization → weights → over-fetch → leg cap → fuse → live filter →
7449
+ * slice → output budget → health event). The legs themselves are legitimately store-specific —
7450
+ * different tables, different filters (`category` vs `domain`/`source`), and only memory has the
7451
+ * HRR probe — but the ORCHESTRATION is one algorithm, and two copies of it drifted:
7452
+ *
7453
+ * - the `NaN` limit guard existed only in memory, so a non-finite limit reached knowledge's SQL
7454
+ * `LIMIT` and took the whole cross-store query down with it (`Promise.all` has no per-leg catch);
7455
+ * - `retriever.over_fetch_factor` was read only by memory, while knowledge hardcoded `limit * 3`,
7456
+ * so one config knob silently meant two different things;
7457
+ * - `recordLegCapped()` was called only by memory, so a capped knowledge leg was unobservable —
7458
+ * exactly the blindness DESIGN §20.17 says the counter exists to remove.
7459
+ *
7460
+ * Fixing those one at a time would leave the next drift a matter of when. The stores now supply
7461
+ * their legs and their hit mapping; everything that can drift lives here once.
7462
+ *
7463
+ * A leg that THROWS is isolated: it contributes nothing, the query still answers from the others,
7464
+ * and the failure is logged. The read path used to have no per-leg catch at all (the write path
7465
+ * did), so an ONNX error at inference cost the caller the FTS and entity legs too.
7466
+ *
7467
+ * @module store/hybrid
7468
+ */
7431
7469
  /**
7432
7470
  * A retrieval input the CALLER got wrong, as opposed to a backend that failed at runtime.
7433
7471
  *
@@ -7488,7 +7526,7 @@ async function hybridSearch(deps, plan) {
7488
7526
  fts: retriever.weight_fts,
7489
7527
  jaccard: retriever.weight_jaccard
7490
7528
  } : DEGRADED_WEIGHTS;
7491
- const overFetch = Math.max(limit, plan.overFetch ?? limit * (retriever.over_fetch_factor || 5));
7529
+ const overFetch = Math.max(limit, plan.overFetch ?? limit * (retriever.over_fetch_factor || defaultConfig.retriever.over_fetch_factor));
7492
7530
  const legCap = legCapFor(deps.config, overFetch);
7493
7531
  const termCount = gradedTerms(query).length;
7494
7532
  /**
@@ -8251,37 +8289,82 @@ async function forEachYielding(items, visit, every = 200) {
8251
8289
  if ((index + 1) % every === 0) await yieldToEventLoop();
8252
8290
  }
8253
8291
  }
8292
+ //#endregion
8293
+ //#region ../core/lib/store/vector_space.js
8254
8294
  /**
8255
- * Rebuild one store's live vstore from its persisted rows, count what is unusable in the current
8256
- * space, and say so once — the shared "open for business" step both stores used to write twice.
8295
+ * The ONE rule that decides whether a persisted vector can serve the store's CURRENT embedding
8296
+ * space — and therefore the one place the three counts (`stale` / `space_stale` / `legacy`) mean
8297
+ * anything.
8257
8298
  *
8258
- * The owning store's ONLY half is the row read (`facts.activeVectorRows()` /
8259
- * `chunks.vectorRows()`); the reload, the count and the loud warning are one implementation, so a
8260
- * change to what counts as stale cannot land on one store only (the incident this exists for: the
8261
- * memory store checked the SPACE while knowledge checked only the WIDTH, so a model swap mixed two
8262
- * spaces in one index until someone happened to run `kb_reindex`).
8299
+ * The rule used to be written out five times (the shared open-time reload, the memory store's cheap
8300
+ * blob-length scan, the memory store's decode pass, the knowledge store's SQL projection and its
8301
+ * encode-source predicate), each in a slightly different shape: one over `Uint8Array`, one over a
8302
+ * projected `length(blob)`, one over a decoded `Float32Array`. They happened to agree; nothing made
8303
+ * them. A change to "what counts as stale" landing on one of them is exactly the silent two-spaces-
8304
+ * in-one-index failure `reportStaleVectors` exists to announce.
8305
+ *
8306
+ * The rule, as three elements (and nothing else):
8307
+ *
8308
+ * 1. `vec == null || byteLength !== dim * 4` → `stale` (the bytes cannot even decode into the
8309
+ * current store);
8310
+ * 2. `embedding_model !== space` → `space_stale` (same width, another model's coordinates);
8311
+ * 3. of those, `isPreFingerprintSpace(...)` → `legacy` (a one-time format upgrade, not a model
8312
+ * swap).
8313
+ *
8314
+ * WHAT IS NOT HERE: the SQL mirror (`ChunksDao.vectorSpaceCounts`) stays in SQL — it is what makes a
8315
+ * per-slice migration cheap (no blob is read) — and the cross assertions in
8316
+ * `test/vector_migration.spec.ts` / `test/knowledge_vector_migration.spec.ts` bind its counts to the
8317
+ * counts this module produces and to the startup warning. That is the direction the mirror has to be
8318
+ * checked in; a JS port of the SQL query would just be a sixth copy.
8319
+ *
8320
+ * @module store/vector_space
8263
8321
  */
8264
- function loadVectorIndex(opts) {
8265
- reloadVectorIndex(opts.vstore, opts.rows, opts.store);
8266
- const expectedBytes = opts.vstore.dim * 4;
8322
+ /** How many bytes one element of a persisted vector BLOB costs (`float32ToBytes` writes LE f32). */
8323
+ const BYTES_PER_FLOAT32 = 4;
8324
+ /**
8325
+ * Classify ONE persisted vector. Pure: no store, no database, no logging — a caller that disagrees
8326
+ * with the result has to disagree with the rule, not with a copy of it.
8327
+ *
8328
+ * A caller-supplied vector is trusted to come from this backend, but its DIMENSION is still checked
8329
+ * — the same asymmetry `legs.ts` applies to a caller-supplied `queryVector`: provenance is the
8330
+ * caller's to assert, width is cheap and decides whether the bytes can be compared at all.
8331
+ */
8332
+ function classifyVectorSpace(probe, identity) {
8333
+ if (!(probe.source === "blob" ? probe.vec !== null && probe.vec !== void 0 && probe.vec.byteLength === identity.dim * BYTES_PER_FLOAT32 : probe.source === "byteLength" ? probe.byteLength === identity.dim * BYTES_PER_FLOAT32 : probe.vec !== null && probe.vec !== void 0 && probe.vec.length === identity.dim)) return {
8334
+ verdict: "stale",
8335
+ legacy: false
8336
+ };
8337
+ if (probe.embeddingModel !== identity.space) return {
8338
+ verdict: "space_stale",
8339
+ legacy: isPreFingerprintSpace(probe.embeddingModel)
8340
+ };
8341
+ return {
8342
+ verdict: "current",
8343
+ legacy: false
8344
+ };
8345
+ }
8346
+ /**
8347
+ * {@link classifyVectorSpace} over a corpus: the `{ stale, space_stale, legacy }` triple the warning
8348
+ * and the `/mem` status surface report, counted by the same rule that classified each row.
8349
+ *
8350
+ * `legacy` counts only rows already counted in `space_stale` (`reportStaleVectors` clamps it there
8351
+ * too) — it is a REASON split, never a fourth bucket.
8352
+ */
8353
+ function countVectorSpace(probes, identity) {
8267
8354
  let stale = 0;
8268
8355
  let spaceStale = 0;
8269
8356
  let legacy = 0;
8270
- for (const row of opts.rows) {
8271
- if (row.vec === null || row.vec.byteLength !== expectedBytes) {
8357
+ for (const probe of probes) {
8358
+ const classified = classifyVectorSpace(probe, identity);
8359
+ if (classified.verdict === "stale") {
8272
8360
  stale++;
8273
8361
  continue;
8274
8362
  }
8275
- if (row.embedding_model !== opts.space) {
8363
+ if (classified.verdict === "space_stale") {
8276
8364
  spaceStale++;
8277
- if (isPreFingerprintSpace(row.embedding_model)) legacy++;
8365
+ if (classified.legacy) legacy++;
8278
8366
  }
8279
8367
  }
8280
- reportStaleVectors(opts.store, opts.space, {
8281
- stale,
8282
- space_stale: spaceStale,
8283
- legacy
8284
- }, opts.manualEntry);
8285
8368
  return {
8286
8369
  stale,
8287
8370
  space_stale: spaceStale,
@@ -8289,6 +8372,29 @@ function loadVectorIndex(opts) {
8289
8372
  };
8290
8373
  }
8291
8374
  /**
8375
+ * Rebuild one store's live vstore from its persisted rows, count what is unusable in the current
8376
+ * space, and say so once — the shared "open for business" step both stores used to write twice.
8377
+ *
8378
+ * The owning store's ONLY half is the row read (`facts.activeVectorRows()` /
8379
+ * `chunks.vectorRows()`); the reload, the count and the loud warning are one implementation, so a
8380
+ * change to what counts as stale cannot land on one store only (the incident this exists for: the
8381
+ * memory store checked the SPACE while knowledge checked only the WIDTH, so a model swap mixed two
8382
+ * spaces in one index until someone happened to run `kb_reindex`).
8383
+ */
8384
+ function loadVectorIndex(opts) {
8385
+ reloadVectorIndex(opts.vstore, opts.rows, opts.store);
8386
+ const counts = countVectorSpace(opts.rows.map((row) => ({
8387
+ source: "blob",
8388
+ vec: row.vec,
8389
+ embeddingModel: row.embedding_model
8390
+ })), {
8391
+ space: opts.space,
8392
+ dim: opts.vstore.dim
8393
+ });
8394
+ reportStaleVectors(opts.store, opts.space, counts, opts.manualEntry);
8395
+ return counts;
8396
+ }
8397
+ /**
8292
8398
  * Encode rows one at a time, add each to the live index, and persist them in groups.
8293
8399
  *
8294
8400
  * The shared skeleton: serial encode (a MEASURED decision for chunks — a padded batched forward pass
@@ -10479,22 +10585,53 @@ var FactsDao = class FactsDao {
10479
10585
  //#endregion
10480
10586
  //#region ../core/lib/db/dao/fact_sources.js
10481
10587
  /**
10588
+ * The closed `kind` vocabulary, spelled once so the store and the tests share it.
10589
+ *
10590
+ * The `satisfies` pins every member to the contract's union; the `Exclude` below pins the other
10591
+ * direction, so a kind the contract ADDS fails to compile here instead of silently never being
10592
+ * recorded.
10593
+ */
10594
+ const FACT_SOURCE_KINDS = [
10595
+ "session",
10596
+ "kb_doc",
10597
+ "tool",
10598
+ "manual"
10599
+ ];
10600
+ /**
10601
+ * The FALLBACK kind: a `source_ref` without a recognized `kind:` prefix is `manual` with the
10602
+ * reference kept VERBATIM (the tool contract says so, and "no shape guessing" is the point).
10603
+ */
10604
+ const FACT_SOURCE_FALLBACK_KIND = "manual";
10605
+ /**
10606
+ * The kinds that act as a PREFIX — the vocabulary minus its own fallback, so a kind the contract adds
10607
+ * joins the prefix table by itself (via {@link FACT_SOURCE_KINDS}) and cannot be forgotten here.
10608
+ */
10609
+ const FACT_SOURCE_PREFIX_KINDS = FACT_SOURCE_KINDS.filter((kind) => kind !== FACT_SOURCE_FALLBACK_KIND);
10610
+ /**
10482
10611
  * Derive `(kind, ref)` from the single `source_ref` the write face accepts.
10483
10612
  *
10484
10613
  * The tool takes ONE string (the plan adds no second parameter), so the kind axis is spelled by a
10485
- * `kind:` prefix — `session:…` / `kb_doc:…` / `tool:…` — and anything else is `manual` with the
10486
- * reference kept VERBATIM. No shape guessing: a bare `domain:source:title` becomes a `manual`
10487
- * source whose ref is exactly what the caller wrote, which is strictly more honest than inventing
10488
- * a category from how the string looks.
10614
+ * `kind:` prefix — `session:…` / `kb_doc:…` / `tool:…` — and anything else, INCLUDING the fallback
10615
+ * kind's own name, is `manual` with the reference kept VERBATIM. No shape guessing: a bare
10616
+ * `domain:source:title` becomes a `manual` source whose ref is exactly what the caller wrote, which
10617
+ * is strictly more honest than inventing a category from how the string looks.
10618
+ *
10619
+ * The prefix table comes from {@link FACT_SOURCE_KINDS} (see {@link FACT_SOURCE_PREFIX_KINDS}) rather
10620
+ * than from a hand-written alternation, which is what keeps the recognized set and the recorded set
10621
+ * from being two lists.
10489
10622
  */
10490
10623
  function classifySourceRef(sourceRef) {
10491
- const match = /^(session|kb_doc|tool):(.+)$/s.exec(sourceRef);
10492
- if (match !== null && match[2] !== void 0) return {
10493
- kind: match[1],
10494
- ref: match[2]
10495
- };
10624
+ const separator = sourceRef.indexOf(":");
10625
+ if (separator > 0) {
10626
+ const prefix = sourceRef.slice(0, separator);
10627
+ const ref = sourceRef.slice(separator + 1);
10628
+ if (ref !== "" && FACT_SOURCE_PREFIX_KINDS.includes(prefix)) return {
10629
+ kind: prefix,
10630
+ ref
10631
+ };
10632
+ }
10496
10633
  return {
10497
- kind: "manual",
10634
+ kind: FACT_SOURCE_FALLBACK_KIND,
10498
10635
  ref: sourceRef
10499
10636
  };
10500
10637
  }
@@ -11551,26 +11688,14 @@ var MemoryStore = class {
11551
11688
  * and every existing assertion on it — is unchanged; only the warning's wording uses it.
11552
11689
  */
11553
11690
  vectorSpaceCounts() {
11554
- const space = this.vectorSpace();
11555
- const expectedBytes = this.vstore.dim * 4;
11556
- let stale = 0;
11557
- let spaceStale = 0;
11558
- let legacy = 0;
11559
- for (const row of this.facts.activeVectorSpaces()) {
11560
- if (row.bytes !== expectedBytes) {
11561
- stale++;
11562
- continue;
11563
- }
11564
- if (row.embedding_model !== space) {
11565
- spaceStale++;
11566
- if (isPreFingerprintSpace(row.embedding_model)) legacy++;
11567
- }
11568
- }
11569
- return {
11570
- stale,
11571
- space_stale: spaceStale,
11572
- legacy
11573
- };
11691
+ return countVectorSpace(this.facts.activeVectorSpaces().map((row) => ({
11692
+ source: "byteLength",
11693
+ byteLength: row.bytes,
11694
+ embeddingModel: row.embedding_model
11695
+ })), {
11696
+ space: this.vectorSpace(),
11697
+ dim: this.vstore.dim
11698
+ });
11574
11699
  }
11575
11700
  async add(content, category, ttlDays, opts) {
11576
11701
  const normalized = normalizeWrite(content).trim();
@@ -12028,17 +12153,25 @@ var MemoryStore = class {
12028
12153
  const counts = this.facts.vectorCounts();
12029
12154
  const models = this.facts.embeddingModels();
12030
12155
  const rows = this.facts.activeVectorRows();
12031
- const space = this.vectorSpace();
12156
+ const identity = {
12157
+ space: this.vectorSpace(),
12158
+ dim: this.vstore.dim
12159
+ };
12032
12160
  const staleIds = [];
12033
12161
  const spaceStaleIds = [];
12034
12162
  const usable = [];
12035
12163
  for (const r of rows) {
12036
12164
  const vec = bytesToFloat32(r.semantic_vector);
12037
- if (!vec || vec.length !== this.vstore.dim) {
12165
+ const classified = classifyVectorSpace({
12166
+ source: "decoded",
12167
+ vec,
12168
+ embeddingModel: r.embedding_model
12169
+ }, identity);
12170
+ if (classified.verdict === "stale") {
12038
12171
  staleIds.push(r.fact_id);
12039
12172
  continue;
12040
12173
  }
12041
- if (r.embedding_model !== space) spaceStaleIds.push(r.fact_id);
12174
+ if (classified.verdict === "space_stale") spaceStaleIds.push(r.fact_id);
12042
12175
  usable.push({
12043
12176
  id: r.fact_id,
12044
12177
  vec
@@ -13069,6 +13202,12 @@ var ChunksDao = class {
13069
13202
  * This is what lets the shared repair flow read "what is stale" cheaply per slice instead of
13070
13203
  * re-scanning and hashing the corpus (`corpusState` reads every text; a batched migration must
13071
13204
  * not).
13205
+ *
13206
+ * The JS rule itself is `store/vector_space.ts` (`classifyVectorSpace` / `countVectorSpace`), and
13207
+ * this projection is bound to it by the cross assertions in
13208
+ * `test/knowledge_vector_migration.spec.ts` (the startup warning's count, the health count and the
13209
+ * migration's `remaining` all have to be the same number). NOTHING here changes when the JS rule
13210
+ * changes: the mirror is checked, not shared.
13072
13211
  */
13073
13212
  vectorSpaceCounts(space, expectedBytes) {
13074
13213
  const row = this.db.prepare(`SELECT
@@ -17078,7 +17217,10 @@ async function loadFramework(options) {
17078
17217
  }
17079
17218
  /**
17080
17219
  * Read the range a plugin declares in the nearest `package.json`: walk up from `from`,
17081
- * preferring `peerDependencies` over `devDependencies`; `undefined` when none is found.
17220
+ * preferring `dependencies` over `peerDependencies` over `devDependencies`; `undefined` when none is
17221
+ * found. `dependencies` is the live declaration — the base ships as a normal dependency of the
17222
+ * plugins — while `peerDependencies` stays as the fallback for plugins built when the base was
17223
+ * declared a peer, and `devDependencies` covers a local checkout.
17082
17224
  */
17083
17225
  async function readDependencyRange(from = process.cwd()) {
17084
17226
  let current = from;
@@ -17108,7 +17250,7 @@ async function readDependencyRange(from = process.cwd()) {
17108
17250
  const parsed = JSON.parse(text);
17109
17251
  if (typeof parsed !== "object" || parsed === null) return void 0;
17110
17252
  const manifest = parsed;
17111
- return rangeOf(manifest["peerDependencies"]) ?? rangeOf(manifest["devDependencies"]);
17253
+ return rangeOf(manifest["dependencies"]) ?? rangeOf(manifest["peerDependencies"]) ?? rangeOf(manifest["devDependencies"]);
17112
17254
  } catch {
17113
17255
  return;
17114
17256
  }
@@ -18469,7 +18611,7 @@ async function apply(ctx, config) {
18469
18611
  logger.info(wellFormed.text && wellFormed.deep ? "well-formed repair: the base kit's wellFormedText/wellFormedDeep (interface v2)" : "well-formed repair: the mem mirror (the base is absent, or predates the v2 helpers)");
18470
18612
  const compat = env?.compat;
18471
18613
  let verdict;
18472
- if (compat === void 0) logger.warn("plugin mount: the dsh compatibility gate is ABSENT (@avantf/dsh-plugin-base unavailable — see the WARNING above); mounting the full plugin anyway");
18614
+ if (compat === void 0) logger.warn("plugin mount: the dsh compatibility gate is UNAVAILABLE (@avantf/dsh-plugin-base is absent, or it is another interface generation — see the WARNING above); mounting the full plugin anyway");
18473
18615
  else {
18474
18616
  const run = provision(ctx, logger, compat);
18475
18617
  if (!run.verdict.load) {
@@ -18631,7 +18773,8 @@ async function apply(ctx, config) {
18631
18773
  new AvantfMemGateway(ctx, rt);
18632
18774
  logger.info("remote registered: avantfMem (client ctx.remote.avantfMem.*)");
18633
18775
  const disposeTypert = ctx.typert.register(hostContribution);
18634
- logger.info("typert registered: avantfMem host face (5 tools + 5 UI-only)");
18776
+ const toolBackedMethods = descriptors.filter((descriptor) => toolSpecs.some((spec) => spec.key === descriptor.method)).length;
18777
+ logger.info(`typert registered: avantfMem host face (${String(toolBackedMethods)} tool-backed + ${String(descriptors.length - toolBackedMethods)} UI-only Remote methods)`);
18635
18778
  if (compat !== void 0) verifyRegisteredFaces({
18636
18779
  ctx,
18637
18780
  toolNames: toolSpecs.map((spec) => spec.name),
@@ -1,4 +1,4 @@
1
1
  {
2
- "baseVersion": "0.4.1",
3
- "interfaceVersion": 3
2
+ "baseVersion": "0.5.2",
3
+ "interfaceVersion": 4
4
4
  }
@@ -3,18 +3,20 @@
3
3
  * @module bootstrap
4
4
  */
5
5
  /** The bootstrap version; the resolved framework must satisfy {@link supportedRange}. */
6
- export declare const VERSION = "0.4.1";
6
+ export declare const VERSION = "0.5.2";
7
7
  /**
8
8
  * The framework interval this bootstrap can launch.
9
9
  *
10
- * Kept equal to the plugins' peer range for the base: the plugin passes no override, so THIS is the
11
- * runtime gate. It is a plain comparator interval, NOT a caret: the package version is ordinary
12
- * semver again and no longer tracks the interface, so the interval stays as wide as the family's
13
- * compatibility promise — `>=0.3.0 <1.0.0` admits every 0.x base this generation of plugins can
14
- * consume, and stops at the major that invented a new interface generation. The interface itself is
15
- * the separate, runtime axis: a base inside this interval whose `INTERFACE_VERSION` differs is judged
16
- * by the base's own gate (INTERFACE.md §1, §3) — an OLDER base degrades, a NEWER one is `ok` + a
17
- * warning (generations are additive), and neither ever refuses the mount.
10
+ * This is the PACKAGE-version window, and nothing else: the interval is byte-for-byte the range the
11
+ * plugins declare in their `dependencies` for the base (the install-time gate; the base is a plain
12
+ * dependency, not a peer) and the plugin passes no override, so THIS is the runtime gate too. It is
13
+ * a plain comparator interval, NOT a caret: the package version is ordinary semver and does not
14
+ * track the interface, so the interval stays as wide as the family's compatibility promise —
15
+ * `>=0.3.0 <1.0.0` admits every 0.x base this generation of plugins can consume. Its upper bound
16
+ * marks no generation boundary. Generation is the separate, runtime axis, judged on its own: the
17
+ * base's `INTERFACE_VERSION` is compared by the base's gate (INTERFACE.md §1, §3) — an OLDER base
18
+ * degrades to `incompatible`, a NEWER one is `ok` + a WARNING (generations are additive), and
19
+ * neither ever refuses the mount.
18
20
  */
19
21
  export declare const supportedRange = ">=0.3.0 <1.0.0";
20
22
  /** Sink for the warnings that replace a throw. */
@@ -49,6 +51,9 @@ export declare function ensureFramework(options: EnsureFrameworkOptions): Promis
49
51
  export declare function loadFramework<T = unknown>(options: EnsureFrameworkOptions): Promise<T | undefined>;
50
52
  /**
51
53
  * Read the range a plugin declares in the nearest `package.json`: walk up from `from`,
52
- * preferring `peerDependencies` over `devDependencies`; `undefined` when none is found.
54
+ * preferring `dependencies` over `peerDependencies` over `devDependencies`; `undefined` when none is
55
+ * found. `dependencies` is the live declaration — the base ships as a normal dependency of the
56
+ * plugins — while `peerDependencies` stays as the fallback for plugins built when the base was
57
+ * declared a peer, and `devDependencies` covers a local checkout.
53
58
  */
54
59
  export declare function readDependencyRange(from?: string): Promise<string | undefined>;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AASlD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,OAAO,EAAE,mBAAmB,EAAU,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EASL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAaL,KAAK,aAAa,EAClB,KAAK,WAAW,EAEhB,KAAK,YAAY,EACjB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAG1B,MAAM,sBAAsB,CAAA;AAqB7B,eAAO,MAAM,IAAI,eAAe,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAQ7E,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AACD,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAO3B,CAAA;AAkCF,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAOvC;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,mBAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;gBACtB,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa;IAK3C;;;;;;;;;OASG;YACW,IAAI;IAclB;;;;OAIG;IAEH,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IAMlF,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAMnF,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAKhF;;;;;OAKG;IAEH,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAOpD,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAI5D;;;;OAIG;IAEG,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAC;IAUnF,qFAAqF;IAE/E,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAUvE;;;;OAIG;IAEG,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC;IAQvG;;;;OAIG;IAEG,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;IAUpG;;;;;OAKG;IAEG,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;CAiBpE;AAmHD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyYvE"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAA;AASlD,OAAO,CAAC,MAAM,0BAA0B,CAAA;AAExC,OAAO,EAAE,mBAAmB,EAAU,MAAM,kCAAkC,CAAA;AAE9E,OAAO,EASL,KAAK,aAAa,EAClB,KAAK,cAAc,EACnB,KAAK,WAAW,EAEhB,KAAK,WAAW,EACjB,MAAM,aAAa,CAAA;AACpB,OAAO,EAaL,KAAK,aAAa,EAClB,KAAK,WAAW,EAEhB,KAAK,YAAY,EACjB,KAAK,cAAc,EAEnB,KAAK,oBAAoB,EAG1B,MAAM,sBAAsB,CAAA;AAqB7B,eAAO,MAAM,IAAI,eAAe,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAA;AAGpC,OAAO,EAAE,wBAAwB,EAAE,qBAAqB,EAAE,MAAM,aAAa,CAAA;AAQ7E,OAAO,EACL,uBAAuB,EACvB,mBAAmB,EACnB,KAAK,eAAe,EACpB,KAAK,WAAW,GACjB,MAAM,aAAa,CAAA;AAEpB,MAAM,WAAW,MAAM;IACrB,IAAI,CAAC,EAAE,QAAQ,GAAG,KAAK,CAAA;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB;AACD,eAAO,MAAM,MAAM,EAAE,CAAC,CAAC,MAAM,CAO3B,CAAA;AAkCF,KAAK,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;AAOvC;;;;;;;;;;GAUG;AACH,qBAAa,gBAAiB,SAAQ,mBAAmB;IACvD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;gBACtB,GAAG,EAAE,OAAO,EAAE,EAAE,EAAE,aAAa;IAK3C;;;;;;;;;OASG;YACW,IAAI;IAclB;;;;OAIG;IAEH,QAAQ,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC;IAMlF,MAAM,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,QAAQ,CAAC,CAAC,CAAC;IAMnF,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,WAAW,EAAE,MAAM,CAAC,CAAC,CAAC;IAKhF;;;;;OAKG;IAEH,EAAE,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC;IAOpD,KAAK,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,YAAY,CAAC,CAAC;IAI5D;;;;OAIG;IAEG,cAAc,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,oBAAoB,CAAC,CAAC;IAUnF,qFAAqF;IAE/E,SAAS,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC;IAUvE;;;;OAIG;IAEG,SAAS,CAAC,KAAK,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,eAAe,CAAC,CAAC,CAAC;IAQvG;;;;OAIG;IAEG,WAAW,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,cAAc,EAAE,WAAW,CAAC,CAAC,CAAC;IAUpG;;;;;OAKG;IAEG,OAAO,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,cAAc,CAAC,WAAW,CAAC,CAAC;CAiBpE;AAmHD,wBAAsB,KAAK,CAAC,GAAG,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAyZvE"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@avantf/dsh-mem",
3
- "version": "0.6.2",
3
+ "version": "0.6.4",
4
4
  "description": "avantf-mem DSH native Cordis plugin: host service + tools + RPC + client tabs 记忆/知识",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -41,7 +41,6 @@
41
41
  "node": ">=22.15.0 <23 || >=23.11.0"
42
42
  },
43
43
  "peerDependencies": {
44
- "@avantf/dsh-plugin-base": ">=0.3.0 <1.0.0",
45
44
  "@deepseek-ai/cordis": "^4.0.0",
46
45
  "@deepseek-ai/dsh-system-prompt": "^0.1.5-rc.2 || ^0.1.7-rc.2 || ^0.2.0-rc.2",
47
46
  "@deepseek-ai/dsh-tools": "^0.1.5-rc.2 || ^0.1.7-rc.2 || ^0.2.0-rc.2",
@@ -67,6 +66,7 @@
67
66
  }
68
67
  },
69
68
  "dependencies": {
69
+ "@avantf/dsh-plugin-base": ">=0.3.0 <1.0.0",
70
70
  "exceljs": "^4.4.0",
71
71
  "unpdf": "^1.8.1",
72
72
  "yaml": "^2.5.0"
@@ -78,10 +78,10 @@
78
78
  },
79
79
  "devDependencies": {
80
80
  "@avantf/dsh-plugin-base": ">=0.3.0 <1.0.0",
81
- "@avantf/mem": "0.6.2",
82
- "@avantf/mem-contract": "0.6.2",
83
- "@avantf/mem-convert": "0.6.2",
84
- "@avantf/mem-provision": "0.6.2",
81
+ "@avantf/mem": "0.6.4",
82
+ "@avantf/mem-contract": "0.6.4",
83
+ "@avantf/mem-convert": "0.6.4",
84
+ "@avantf/mem-provision": "0.6.4",
85
85
  "@types/node": "^22.0.0",
86
86
  "@types/react": "^18.3.0",
87
87
  "lightningcss": "^1.32.0",