dsh-data-domain-knowledge 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
Files changed (117) hide show
  1. package/README.md +96 -113
  2. package/cordis.patch.yml +11 -5
  3. package/lib/client.js +5557 -3056
  4. package/lib/doc-page.js +7740 -0
  5. package/lib/types/convert/host.d.ts.map +1 -1
  6. package/lib/types/convert/host.js +36 -9
  7. package/lib/types/convert/host.js.map +1 -1
  8. package/lib/types/diagnosis/cards.d.ts +8 -7
  9. package/lib/types/diagnosis/cards.d.ts.map +1 -1
  10. package/lib/types/diagnosis/cards.js +119 -49
  11. package/lib/types/diagnosis/cards.js.map +1 -1
  12. package/lib/types/diagnosis/run.d.ts +51 -0
  13. package/lib/types/diagnosis/run.d.ts.map +1 -0
  14. package/lib/types/diagnosis/run.js +239 -0
  15. package/lib/types/diagnosis/run.js.map +1 -0
  16. package/lib/types/diagnosis/schema.d.ts +25 -2
  17. package/lib/types/diagnosis/schema.d.ts.map +1 -1
  18. package/lib/types/diagnosis/schema.js +64 -25
  19. package/lib/types/diagnosis/schema.js.map +1 -1
  20. package/lib/types/diagnosis/seed-source.d.ts +17 -2
  21. package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
  22. package/lib/types/diagnosis/seed-source.js +18 -1
  23. package/lib/types/diagnosis/seed-source.js.map +1 -1
  24. package/lib/types/diagnosis/seed.d.ts +32 -42
  25. package/lib/types/diagnosis/seed.d.ts.map +1 -1
  26. package/lib/types/diagnosis/seed.js +187 -51
  27. package/lib/types/diagnosis/seed.js.map +1 -1
  28. package/lib/types/diagnosis/store.d.ts +114 -26
  29. package/lib/types/diagnosis/store.d.ts.map +1 -1
  30. package/lib/types/diagnosis/store.js +336 -167
  31. package/lib/types/diagnosis/store.js.map +1 -1
  32. package/lib/types/diagnosis/types.d.ts +168 -16
  33. package/lib/types/diagnosis/types.d.ts.map +1 -1
  34. package/lib/types/diagnosis/types.js +1 -1
  35. package/lib/types/diagnosis/types.js.map +1 -1
  36. package/lib/types/document-page.d.ts +39 -0
  37. package/lib/types/document-page.d.ts.map +1 -0
  38. package/lib/types/document-page.js +43 -0
  39. package/lib/types/document-page.js.map +1 -0
  40. package/lib/types/fs-atomic.d.ts +12 -0
  41. package/lib/types/fs-atomic.d.ts.map +1 -0
  42. package/lib/types/fs-atomic.js +42 -0
  43. package/lib/types/fs-atomic.js.map +1 -0
  44. package/lib/types/index.d.ts.map +1 -1
  45. package/lib/types/index.js +214 -24
  46. package/lib/types/index.js.map +1 -1
  47. package/lib/types/knowledge-bases.d.ts +10 -2
  48. package/lib/types/knowledge-bases.d.ts.map +1 -1
  49. package/lib/types/knowledge-bases.js +20 -18
  50. package/lib/types/knowledge-bases.js.map +1 -1
  51. package/lib/types/proposals.d.ts +166 -0
  52. package/lib/types/proposals.d.ts.map +1 -0
  53. package/lib/types/proposals.js +238 -0
  54. package/lib/types/proposals.js.map +1 -0
  55. package/lib/types/runtime.d.ts +199 -7
  56. package/lib/types/runtime.d.ts.map +1 -1
  57. package/lib/types/runtime.js +728 -145
  58. package/lib/types/runtime.js.map +1 -1
  59. package/lib/types/settings-schema.d.ts +2 -2
  60. package/lib/types/types.d.ts +61 -10
  61. package/lib/types/types.d.ts.map +1 -1
  62. package/lib/types/types.js +56 -8
  63. package/lib/types/types.js.map +1 -1
  64. package/lib/types/wiki-types.d.ts +8 -1
  65. package/lib/types/wiki-types.d.ts.map +1 -1
  66. package/lib/types/wiki-types.js +16 -25
  67. package/lib/types/wiki-types.js.map +1 -1
  68. package/package.json +4 -3
  69. package/preset/tita-data-agent/agent.cordis.yml +11 -5
  70. package/src/client/DirectoryPicker.tsx +12 -12
  71. package/src/client/InfoHint.tsx +21 -8
  72. package/src/client/IntentTab.tsx +20 -26
  73. package/src/client/KnowledgeBaseTab.tsx +190 -80
  74. package/src/client/KnowledgePanel.tsx +42 -9
  75. package/src/client/KnowledgeStepRail.tsx +3 -0
  76. package/src/client/NodeSettings.tsx +20 -22
  77. package/src/client/PreviewSections.tsx +87 -33
  78. package/src/client/ProposalReview.tsx +313 -0
  79. package/src/client/TypeDefinitionDrawer.tsx +72 -38
  80. package/src/client/WikiTypeEditor.tsx +193 -77
  81. package/src/client/arch/AgentArchitectureGraph.tsx +143 -14
  82. package/src/client/arch/NodeLogicPanel.tsx +6 -3
  83. package/src/client/arch/model.ts +164 -26
  84. package/src/client/architecture.css +29 -9
  85. package/src/client/architecture.tsx +54 -29
  86. package/src/client/attribution/AttributionGraph.tsx +481 -34
  87. package/src/client/attribution/AttributionTab.tsx +507 -106
  88. package/src/client/attribution/CaseView.tsx +9 -14
  89. package/src/client/attribution/GraphStage.tsx +25 -18
  90. package/src/client/attribution/RunView.tsx +27 -9
  91. package/src/client/attribution/StageChrome.tsx +296 -140
  92. package/src/client/attribution/graph-options.ts +611 -47
  93. package/src/client/attribution/model.ts +559 -30
  94. package/src/client/convert/ConvertPanel.tsx +6 -1
  95. package/src/client/doc-page.css +134 -0
  96. package/src/client/doc-page.tsx +263 -0
  97. package/src/client/panel-state.ts +105 -0
  98. package/src/client/request.ts +36 -0
  99. package/src/client/styles.css +317 -43
  100. package/src/client/useFitScale.ts +15 -2
  101. package/src/client/wiki-type-groups.ts +86 -0
  102. package/src/convert/host.ts +41 -7
  103. package/src/diagnosis/cards.ts +120 -50
  104. package/src/diagnosis/run.ts +245 -0
  105. package/src/diagnosis/schema.ts +67 -26
  106. package/src/diagnosis/seed-source.ts +28 -3
  107. package/src/diagnosis/seed.ts +187 -85
  108. package/src/diagnosis/store.ts +358 -174
  109. package/src/diagnosis/types.ts +181 -16
  110. package/src/document-page.ts +51 -0
  111. package/src/fs-atomic.ts +42 -0
  112. package/src/index.ts +209 -22
  113. package/src/knowledge-bases.ts +20 -18
  114. package/src/proposals.ts +337 -0
  115. package/src/runtime.ts +736 -135
  116. package/src/types.ts +113 -19
  117. package/src/wiki-types.ts +17 -27
package/README.md CHANGED
@@ -8,33 +8,24 @@
8
8
  模型不可用时明确降级到 FTS,不会伪造向量结果。Markdown 中的
9
9
  本地图片会与正文一起交给 WeMM,因此图片不是独立知识类型。
10
10
 
11
- ## 承载范围(五个视图 = 一个挂载单元)
11
+ ## 承载范围(五个视图)
12
12
 
13
- 侧边栏 `TitaAgent` 面板声明的五个视图就是本插件的全部范围,`src/` 的目录结构按它们组织:
13
+ 侧边栏 `TitaAgent` 面板的五个视图就是本插件的全部范围:
14
14
 
15
- | 视图 | 面板标签 | 宿主实现 | 冻结契约 |
16
- | --- | --- | --- | --- |
17
- | `overview` | Agent 概览 | `src/index.ts` | 各设置的挂载节点 |
18
- | `intent` | 意图 | `src/intent/`(0020 自 `data-domain-intent` 并入);配置与留痕落 `<agentRoot>/intent/` | `intent_*` 工具名、`/intent/*` 路由、`tita-data-agent-intent` 设置、`titaDataAgentIntent` 服务 |
19
- | `knowledge` | 知识库 | `src/runtime.ts` + `src/knowledge-bases.ts` + `src/convert/`(0018 并入) | `recall_knowledge` 等工具名、`/knowledge/*` 与 `/convert` 路由、`tita-data-agent-knowledge` / `-convert` 设置 |
20
- | `memory` | 记忆 | `src/runtime.ts` 的 `memoryPath()`(版本快照、`feedback.ndjson`、`access-audit.ndjson`),根在 `<agentRoot>/memory/<库 id>` | 磁盘目录布局 |
21
- | `eval` | 评测 | `eval/evaluate-retrieval.mjs`(开发期能力,不注册运行时入口) | 问题集格式 |
22
-
23
- **架构原理:插件的数量应当等于用户入口的数量,而不是概念的数量;而插件的名字应当指向主干概念,不指向子域。**
24
- 意图/知识库/记忆/评测是四个概念,但它们共享同一条流水线(记录意图 → 录入来源 → 编译知识 →
25
- 沉淀记忆 → 评测质量)和同一个入口(面板的五个 Tab)。0020 之前,「意图」Tab 已经长在本插件里、
26
- 宿主实现却在第二个挂载单元里——**声明的信息架构与代码布局不一致**,这才是要修的东西。
27
-
28
- 意图的**配置与留痕**落在 `<agentRoot>/intent/`(0033):`registry.json` 是分类 / act 词表的
15
+ | 视图 | 面板标签 | 冻结契约 |
16
+ | --- | --- | --- |
17
+ | `overview` | Agent 概览 | 各设置的挂载节点 |
18
+ | `intent` | 意图 | `intent_*` 工具名、`/intent/*` 路由、`tita-data-agent-intent` 设置、`titaDataAgentIntent` 服务;配置与留痕落 `<agentRoot>/intent/` |
19
+ | `knowledge` | 知识库 | `recall_knowledge` 等工具名、`/knowledge/*` 与 `/convert` 路由、`tita-data-agent-knowledge` / `-convert` 设置 |
20
+ | `memory` | 记忆 | 版本快照、`feedback.ndjson`、`access-audit.ndjson`,根在 `<agentRoot>/memory/<库 id>`;磁盘目录布局 |
21
+ | `eval` | 评测 | 问题集格式(开发期能力,不注册运行时入口) |
22
+
23
+ 意图的**配置与留痕**落在 `<agentRoot>/intent/`:`registry.json` 是分类 / act 词表的
29
24
  真源(面板的「意图配置」直接读写这个文件,不再写宿主的 settings 文档),`records.ndjson`
30
25
  是每轮一条记录的 append-only 留痕(按 `sessionKey#turn` 读时取最后一条,所以内存里
31
26
  「一轮一条」的语义不变)。读接口 `/intent/store` 与 `/intent/overview` 读的是**留痕**:
32
27
  引擎内存里那份有 600 秒 TTL,重启或隔一段时间回来不该让面板上的历史凭空消失。
33
28
 
34
- `src/intent/` 的宿主半与 `src/client/IntentTab.tsx` 的浏览器半之间**必须保留 HTTP 边界**(`/intent/*`):
35
- 两者分处两个 bundle,无法直接互相 import。这与 0018 删掉的 `POST /knowledge/sources` 不同——
36
- 那次是宿主→宿主的回环,可以改成进程内调用。
37
-
38
29
  ## 五段链路
39
30
 
40
31
  | 段 | 名称 | 产物 | 关键配置 |
@@ -45,14 +36,14 @@
45
36
  | 4 | 召回 Recall | 多路(关键词 + 向量 + 知识图谱)召回 wikiNotes,取 `recallTopK`(默认 10),再解析父级 wiki 与该 wiki 的 1 跳知识图谱 | `recallTopK` / `recallMode` |
46
37
  | 5 | 重排 Rerank | BERT cross-encoder 对候选 wikiNotes 精排,取 `contextTopN`(默认 5)组成 `context` | `contextTopN` / `rerankProvider` / `rerankModel` |
47
38
 
48
- > 0033:第 4、5 段在**界面上是同一栏**(知识库 → 召回重排)。它们在流水线上仍是两步
49
- > ——架构图里也仍是两个节点——但「跑一次真实召回长什么样」是一个问题,分两栏会让用户
50
- > 先跑召回、再切到重排才能看到同一次运行的全貌,而且两栏各有一个测试按钮。
39
+ > 第 4、5 段在**界面上是同一栏**(知识库 → 召回重排)。它们在流水线上仍是两步,
40
+ > 但「跑一次真实召回长什么样」是一个问题,分两栏会让用户先跑召回、再切到重排才能
41
+ > 看到同一次运行的全貌。
51
42
 
52
43
  > 术语迁移:SourceRaw 曾叫 SourceRow / SourceTruth。磁盘目录 `01_source_truth`、工具名
53
44
  > `add_source` / `query_source_truth`、HTTP `/knowledge/sources`、设置键 `allowSourceTruth`
54
45
  > 与错误码 `SOURCE_TRUTH_DISABLED` 作为兼容名保留,语义等同 SourceRaw。
55
- > 本轮的「编译 / 构建」仍是同一次 `rebuild()`:编译的**按主题聚合**由 Agent 完成,
46
+ > 「编译 / 构建」是同一次 `rebuild()`:编译的**按主题聚合**由 Agent 完成,
56
47
  > **wiki↔wiki 双向链接**与**分阶段重建**是设计目标,图谱当前只有 `supported_by` 溯源边。
57
48
 
58
49
  - 设置项**不再有独立入口**:它们挂在 Agent 概览架构图的对应节点上(SourceRaw → 收录开关与
@@ -72,15 +63,15 @@
72
63
 
73
64
  ## 本地启用 WeMM
74
65
 
75
- 在 Apple Silicon 上执行一次:
66
+ 在 Apple Silicon 上执行一次(在插件包目录内):
76
67
 
77
68
  ```bash
78
- ./plugins/dsh-data-domain-knowledge/scripts/setup-embedding.sh
69
+ bash scripts/setup-embedding.sh
79
70
  ```
80
71
 
81
72
  脚本会把 Python 环境放到 `~/.tita-data-agent/embedding-venv`,把量化模型放到
82
- `~/.tita-data-agent/models/WeMM-Embedding-2B-Quantized`,不会把 1.8GB 权重提交
83
- 进仓库。新配置会自动发现这两个路径;如果 DSH 中已经保存过旧配置,则在
73
+ `~/.tita-data-agent/models/WeMM-Embedding-2B-Quantized`。新配置会自动发现这两个路径;
74
+ 如果 DSH 中已经保存过旧配置,则在
84
75
  Tita Data Agent → Agent 概览 →「构建」节点的设置中确认:
85
76
 
86
77
  - Provider:`WeMM Python(本地)`
@@ -90,7 +81,7 @@ Tita Data Agent → Agent 概览 →「构建」节点的设置中确认:
90
81
  - 设备:`自动(优先 MPS)`
91
82
 
92
83
  知识库默认保存在 `~/.tita-data-agent/knowledge`;设置了 `DSH_KNOWLEDGE_ROOT`
93
- 或已有旧的仓库内 `domain_knowledge` 时会优先沿用。点击「编译」后,
84
+ 或已有旧的 `domain_knowledge` 目录时会优先沿用。点击「编译」后,
94
85
  `03_compiled` 会生成 `wiki_notes.json`、`knowledge_graph.json`、
95
86
  `vector-manifest.json`、`vectors.binary` 和 `vectors.int8`。未修改的 wikiNote
96
87
  会复用已有向量,只有新增或变更的 note 重新编码;编码与重排各自使用常驻 Python
@@ -100,8 +91,7 @@ BERT 重排默认使用 `BAAI/bge-reranker-base`;如需完全离线,可把
100
91
  `~/.tita-data-agent/models/bge-reranker-base`(会被自动发现),或设置
101
92
  `DSH_RERANK_MODEL` / 在设置里填写本地路径。未安装时召回会降级到融合分数并给出警告。
102
93
  **目录必须含分词器**(`tokenizer.json` 或 `sentencepiece.bpe.model`)——只有权重时模型会
103
- 加载成功但给所有输入同一个分(`RERANK_TOKENIZER_DEGENERATE`),见
104
- `docs/0024-本地模型运行时.md`。
94
+ 加载成功但给所有输入同一个分(`RERANK_TOKENIZER_DEGENERATE`)。
105
95
 
106
96
  知识写入、编译、SourceRaw 读取和召回支持统一的
107
97
  `sessionId/turnId/intentId/taskId/subtaskId/runId/traceId/domain` lineage。卡片把
@@ -112,20 +102,12 @@ BERT 重排默认使用 `BAAI/bge-reranker-base`;如需完全离线,可把
112
102
  可以通过工具 `check_knowledge_embeddings` 或 `GET /knowledge/embedding/health`
113
103
  查看 provider、模型、设备、索引条数和最后一次错误。
114
104
 
115
- ## 格式转换(convert seam,0018)
105
+ ## 格式转换(convert)
116
106
 
117
- 多格式转换的 **DSH 宿主层**自 0018 起就在本插件里:不再是第二个挂载插件,而是本插件的一个
118
- 子模块(`src/convert/`),共用同一次挂载、同一个侧边栏 Tab、同一套 runtime。
119
-
120
- ```
121
- plugins/dsh-data-domain-knowledge/src/convert/
122
- ├─ host.ts 工具 /convert_*、路由 /convert/**、设置、面板
123
- ├─ ingest.ts 唯一写入口:ingestDelivery() → runtime.addSource()
124
- ├─ core-loader.ts 可选内核的懒加载与降级
125
- └─ types.ts
126
- packages/dsh-convert-core 内核(不挂载,可独立运行:node bin/tita-convert.mjs)
127
- └─ @firecrawl/anydoc MIT / Rust / 预编译绑定:doc docx ppt pptx xls xlsx odt ods odp rtf epub csv 文本层 PDF
128
- ```
107
+ 多格式转换是本插件的一个子模块(`src/convert/`),与知识库共用同一次挂载、同一个
108
+ 侧边栏 Tab、同一套 runtime。解析由可选内核 `dsh-convert-core` 完成(内核不挂载,可独立
109
+ 运行),底层是 `@firecrawl/anydoc`(MIT / Rust / 预编译绑定),支持 doc、docx、ppt、
110
+ pptx、xls、xlsx、odt、ods、odp、rtf、epub、csv 与文本层 PDF。
129
111
 
130
112
  | 项 | 值 |
131
113
  | --- | --- |
@@ -134,40 +116,30 @@ packages/dsh-convert-core 内核(不挂载,可独立运行:node bin/tita
134
116
  | 设置命名空间 | `tita-data-agent-convert`(冻结契约,由本插件安装) |
135
117
  | 交付目录 | `~/.tita-data-agent/convert-out/<taskId>/`(`index.md` / `assets/` / `manifest.json`) |
136
118
 
137
- **依赖是可选的。** `dsh-convert-core` 声明在 `optionalDependencies`,由
138
- `core-loader.ts` 在首次调用时 `import()`。平台没有 anydoc 预编译绑定、或用户显式不装时,
119
+ **依赖是可选的。** `dsh-convert-core` 声明在 `optionalDependencies`,首次调用时才加载。
120
+ 平台没有 anydoc 预编译绑定、或用户显式不装时,
139
121
  `convert_*` 与 `/convert` 面板返回 `DEPENDENCY_MISSING` 并给出安装建议,**knowledge 本体
140
- (`add_source` / 编译 / 召回)照常可用**。不要把它改成静态 import:有一条测试守着这件事。
122
+ (`add_source` / 编译 / 召回)照常可用**。
141
123
 
142
- **入库是进程内调用。** `ingestDelivery()` 直接调 `runtime.addSource()`,不再走
143
- `POST /knowledge/sources` 回环。少了一次 JSON 序列化,也因此没有旧的 ~1.2 MB body 上限。
144
- 单条 SourceRaw **没有字符数上限**:`maxSourceBytes` 在 0029 撤掉了(先撤面板上的字段,
145
- 后来连运行层那道闸门一起撤 —— 只撤面板等于留一个用户改不了、也找不到的硬上限)。
124
+ **单条 SourceRaw 没有字符数上限**:`maxSourceBytes` 已撤除。
146
125
  真正的边界是转换内核的 `maxInputBytes`(管入口大小)与嵌入分批(管模型输入)。
147
126
 
148
- 三条红线(改这一块前先读):
149
-
150
- 1. 内核不 import knowledge,产物只写 `convert-out/`;
151
- 2. 转换代码写知识库只能经 `ingest.ts` 的 `IngestTarget.addSource`,别处不许直接落 `01_source_truth`;
152
- 3. 0017 的网页 / 视频 / OCR 应做成**可选适配器包**注册进内核,不要继续往本插件里塞 ffmpeg / whisper。
153
-
154
- ## 多知识库与文档(0021)
127
+ ## 多知识库与文档
155
128
 
156
129
  一个**知识库 = 独立配置 + 独立内容**(产品参照 WeKnora)。注册表持久化在
157
130
  `<dataHome>/knowledge-bases.json`,`dataHome = process.env.TITA_DATA_AGENT_HOME || ~/.tita-data-agent`。
158
- 默认库 id 固定为 `default`,其 `rootDir` 始终来自插件设置 `rootDir`(缺省
159
- `defaultKnowledgeRoot()`);**不传 `kb` 的旧接口与旧工具一律作用于默认库,行为不变**。
131
+ 默认库 id 固定为 `default`,其 `rootDir` 始终来自插件设置 `rootDir`;
132
+ **不传 `kb` 的旧接口与旧工具一律作用于默认库,行为不变**。
160
133
 
161
134
  ```ts
162
135
  interface KnowledgeBase { id; name; description; rootDir; isDefault; sourceTypes; wikiTypes; wikiSchemaMode; createdAt; updatedAt; sourceCount; cardCount; noteCount; buildVersion }
163
136
  ```
164
137
 
165
138
  `sourceCount` / `cardCount` / `noteCount` / `buildVersion` 和每条分类/类型的计数都是
166
- **计算字段**,由 `KnowledgeBases.refresh()` 从真实 runtime 重新统计并缓存进同一个 JSON,
167
- 所以 `list()` / `get()` 保持同步签名,HTTP `GET /knowledge/bases` 在返回前先
168
- `await refresh()`。
139
+ **计算字段**,由真实 runtime 重新统计并缓存进同一个 JSON,
140
+ HTTP `GET /knowledge/bases` 在返回前先刷新。
169
141
 
170
- 0029 / 0032:**根路径下是六段**:
142
+ **根路径下是六段**:
171
143
 
172
144
  | 段 | 装什么 | 删掉的后果 |
173
145
  | --- | --- | --- |
@@ -178,40 +150,43 @@ interface KnowledgeBase { id; name; description; rootDir; isDefault; sourceTypes
178
150
  | `tmp/` | 运行期临时文件,二级按日期分桶 | 只影响未完成的任务 |
179
151
  | `resource/` | 模型、Python 环境、`knowledge-bases.json` | 失去语义检索(嵌入/重排不可用) |
180
152
 
181
- **记忆与意图为什么与知识树平级**(0032 的架构调整):记忆装的是版本快照与审计流水 ——
153
+ **记忆与意图为什么与知识树平级**:记忆装的是版本快照与审计流水 ——
182
154
  那是**关于这个 Agent 的动作留痕**,不是关于知识的断言;留在知识树里会让「备份知识树」
183
155
  被迫连审计一起备份,也让「知识树有哪几段」这个问题混进一个横切层。意图则是**会话级**的
184
- (同一句话在哪个知识库下说,理解方式不该变),所以它不分库;它的两份东西原来一个住在
185
- 宿主的 settings 文档里、一个只在内存里,都不是「这个 Agent 自己的文件」。搬出来之后,
186
- 知识、记忆、意图三段都是可以直接备份、直接 diff 的用户文件。
156
+ (同一句话在哪个知识库下说,理解方式不该变),所以它不分库。这样知识、记忆、意图三段
157
+ 都是可以直接备份、直接 diff 的用户文件。
187
158
 
188
- 知识树内部(`domain_knowledge/<知识库 id>/`)0032 起是**四段**:`00_schema`(0030 撤,
189
- 它从建出来起就是空目录)与 `04_memory`(0032 搬去 `memory/`)都已不在。
159
+ 知识树内部(`domain_knowledge/<知识库 id>/`)现在是**三段**:schema 段与记忆段都已不在
160
+ (后者搬去了 `memory/`)。
190
161
 
191
162
  | 段 | 装什么 | 删掉的后果 |
192
163
  | --- | --- | --- |
193
164
  | `01_source_truth/<source_type>/` | SourceRaw,一级是**来源分类**,二级是文档 | 丢数据 |
194
165
  | `02_wiki/<entity_type>/` | Wiki 卡片,一级是卡片类型 | 丢结论 |
195
- | `03_compiled/` | wikiNotes / 向量 / 知识图谱 | 可重建 |
196
- | `05_diagnosis/` | 智能归因图谱 | 可重建 |
166
+ | `03_compiled/` | wikiNotes / 向量 / 知识图谱 / 归因图谱 | 可重建 |
167
+
168
+ **归因图谱也在这三段里**(v1.1):一次排查的产物是一条来源分类为 `agent_diagnosis_case`
169
+ 的 SourceRaw(落 `01_source_truth/`),模板卡由类型定义派生、不落盘,字段级的归因图
170
+ 落 `03_compiled/diagnosis_graph.json`。旧的 `05_diagnosis/` 撤销了;它里面的 `*.jsonl`
171
+ 如果还在,角色是**只读的迁移输入与历史快照** —— 不再被写,但仍然读得出来(证据与根因
172
+ 就从那里来),所以旧知识根不会因为这次改动而读不出内容。
197
173
 
198
- `00_schema` 撤掉的依据:它从建出来那天起就是一个**空目录**。分类注册表与 Wiki 类型
199
- 注册表一直住在 `<agentRoot>/resource/knowledge-bases.json`(那是配置,跟着根路径走);
200
- `00_schema` 里没有一行代码写过东西。「磁盘上有一个文件夹」本身是一种承诺,空着就是
201
- 误导。`ensureLayout` 会在启动时把空的 `00_schema` 删掉 —— **非空则保留**:那就不属于
202
- 「没用的空文件夹」了,删掉别人的文件比留一个空目录危险得多。
174
+ schema 段已撤掉:它从建出来那天起就是一个**空目录**,分类注册表与 Wiki 类型注册表
175
+ 一直住在 `<agentRoot>/resource/knowledge-bases.json`(那是配置,跟着根路径走)。
176
+ 启动时会把空的那个目录删掉 —— **非空则保留**:那就不属于「没用的空文件夹」了,
177
+ 删掉别人的文件比留一个空目录危险得多。
203
178
 
204
179
  `01_source_truth` 按分类分目录(而不是平铺)的理由与 `02_wiki` 一致:分类是每份来源的
205
180
  必备元数据(frontmatter 的 `source_type`),提到目录名上之后「按分类找东西」在文件系统
206
181
  层面直接成立,不依赖界面。平铺在真实使用里会先变成垃圾场 —— 几十份来源混在一层,`ls`
207
182
  读不出「我的指标口径在哪」。启动时一次性把平铺的文档搬进分类目录;读路径不依赖这个层级
208
- (`sourceIndex` 递归扫全段建 slug → path 索引),所以旧布局、新布局与两者并存的中间态
183
+ (启动时会递归扫全段建 slug → path 索引),所以旧布局、新布局与两者并存的中间态
209
184
  都能读。**改分类 = 换目录**:`add_source` 用同一 `sourceId` 配新 `sourceType` 时,旧目录
210
185
  里那份会被删掉,不会留下孤儿。
211
186
 
212
- `resource/` 是 0029 新增的:模型与虚拟环境原来住在 `~/.tita-data-agent`,现在搬进
213
- 根路径 —— 「这个 Agent 的全部磁盘足迹」因此是一个目录,备份 / 迁移 / 整个删掉都只
214
- 需要认一个路径。启动时一次性搬迁(先 `rename`,跨卷**不自动复制**,因为可能有几个
187
+ `resource/` 装模型、虚拟环境与 `knowledge-bases.json` —— 「这个 Agent 的全部磁盘足迹」
188
+ 因此是一个目录,备份 / 迁移 / 整个删掉都只需要认一个路径。模型与虚拟环境原先住在
189
+ `~/.tita-data-agent`,启动时一次性搬迁(先 `rename`,跨卷**不自动复制**,因为可能有几个
215
190
  GB);旧的 `TITA_DATA_AGENT_HOME` 留在解析链上作为兜底,所以搬与没搬都能跑。
216
191
  搬迁**按条目合并**:`resource/models/` 里已经有用户自己放的模型时,仍会把旧的嵌入模型
217
192
  搬进来,只跳过同名的那个 —— 否则「目标目录存在」会让另一样东西永远搬不过来。
@@ -221,31 +196,24 @@ GB);旧的 `TITA_DATA_AGENT_HOME` 留在解析链上作为兜底,所以搬
221
196
  的顺序解析。设置面板用占位符显示解析后的真实路径,并在**家当不在 `resource/` 下时
222
197
  逐条告警**(「还在旧位置、重启会搬」和「根本不是路径、迁移搬不了」要做的事不同)。
223
198
 
224
- > ⚠️ 这三个字段的 schema **不能写 `.min(1)`**。schemastery 与 zod 不同,它会**校验默认值
225
- > 本身**,`z.string().min(1).default('')` 会让 `settings.installSection()` 在登记命名空间
226
- > 时直接抛 —— 后果是整个设置分区注册不上(面板「设置服务不可用」)、家当迁移不执行、
227
- > `settings.yaml` 里的值一个都不生效,而界面上没有任何原因。见
228
- > `src/settings-schema.ts` 的模块注释与 `tests/settings-schema.test.mjs`。
229
-
230
- 向量清单里记的模型名也从绝对路径改成了 `local:<模型目录名>` —— **身份与位置解耦**,
199
+ 向量清单里记的模型名是 `local:<模型目录名>` —— **身份与位置解耦**,
231
200
  否则把模型搬一次就让整个索引判成「配置不匹配」而作废。
232
201
 
233
- 0029:**「文件类型 → 解析引擎」那张规则表已撤。** 它唯一被读的地方是浏览器决定
202
+ **「文件类型 → 解析引擎」那张规则表已撤。** 它唯一被读的地方是浏览器决定
234
203
  「这份文件直读还是送转换内核」,而那个判断现在只看扩展名本身(`DIRECT_READ_EXT`);
235
204
  真正决定用哪个适配器的是**转换内核自己的格式表**(`ANYDOC/SCANNED/IDDOC/TEXT_EXTENSIONS`),
236
205
  它从来不读这张表 —— 也就是说「把 .docx 设成直读文本」这类配置**改了不生效**。撤掉
237
206
  之后:上传选择框的 `accept` 问内核要(`/convert/api/capabilities`),文件类型筛选项
238
207
  和计数从**这一库真实的文档**推导。两边都不可能再与内核漂移。
239
208
 
240
- **文档扩展名 `ext` 的推导是有损的**(`deriveSourceExt`,代码注释同):优先取
209
+ **文档扩展名 `ext` 的推导是有损的**:优先取
241
210
  `metadata.source_path` 的 basename 扩展名;否则 `source_format === 'markdown'` → `.md`,
242
- `'office'` → `.docx`,其余 → `.` + `source_format`;都没有 → `.md`。只有上游
243
- `convert/ingest.ts` 写了 `source_path` 时才精确。
211
+ `'office'` → `.docx`,其余 → `.` + `source_format`;都没有 → `.md`。只有来源写了
212
+ `source_path` 时才精确。
244
213
 
245
- 文档的 `parser` 字段说的是**谁解析了这份文档**:经转换内核进来的由
246
- `convert/ingest.ts` 写 `metadata.parser`(如 `anydoc@1/pdf`),直接写进来的
247
- (`add_source`、浏览器直读文本)是 `direct` —— 没有解析器。0029 之前这里会按扩展名
248
- 猜一个引擎名(`.pdf` → `anydoc`),那是在陈述一件没有发生过的事。
214
+ 文档的 `parser` 字段说的是**谁解析了这份文档**:经转换内核进来的会写
215
+ `metadata.parser`(如 `anydoc@1/pdf`),直接写进来的
216
+ (`add_source`、浏览器直读文本)是 `direct` —— 没有解析器。
249
217
 
250
218
  ### HTTP API
251
219
 
@@ -255,13 +223,32 @@ POST /knowledge/bases { name, description?, rootDir? } → { base }
255
223
  PATCH /knowledge/bases/:id { name?, description?, sourceTypes?, wikiTypes?, wikiSchemaMode? } → { base }
256
224
  DELETE /knowledge/bases/:id → { deleted: true }
257
225
  GET /knowledge/documents?kb=&query=&source_type=&limit=&offset= → { documents, total }
258
- GET /knowledge/documents/:docId?kb= → { document }
226
+ GET /knowledge/documents/:docId?kb= → { document }(正文截前 4000 字符)
227
+ GET /knowledge/documents/:docId/content?kb=&offset=&limit= → { text, offset, nextOffset, totalChars, eof }
228
+ GET /knowledge/documents/:docId/view?kb= → 独立整页(HTML,读全文)
259
229
  GET /knowledge/documents/:docId/versions?kb= → { versions }
260
230
  POST /knowledge/documents/:docId/revert?kb= { versionId } → { document }
261
231
  DELETE /knowledge/documents/:docId?kb= → { deleted: true }
262
232
  GET /knowledge/assets/:kb/* → 知识库根目录下的文件流
233
+ GET /knowledge/doc-page.js → 独立整页的脚本(构建产物)
234
+ GET /knowledge/card-proposals?kb=&status= → { total, proposals }(默认只看待审)
235
+ POST /knowledge/card-proposals/review { kb, proposal_id, action, reason? } → { proposalId, status, written }
236
+ POST /diagnosis/run { kb, record } → { sourceId, proposalId }
263
237
  ```
264
238
 
239
+ **`/knowledge/card-proposals/review` 是「人的确认动作」,刻意不做成 Agent 工具。**
240
+ 类型层的写入现在有两段:Agent 与编译只**提议**(`propose_wiki_cards`、Pass 0),
241
+ 未确认的提议不落 `02_wiki/`、不进编译、不参与召回、不在归因图上出现;人在面板上
242
+ 逐条 review(通过并写入 / 仅确认 / 拒绝),确认之后才走 `write_knowledge_card` ——
243
+ 而那个工具现在要求一条**已确认且尚未落卡**的 `proposal_id`,确认凭证是一次性的。
244
+ 提议落在记忆段 `<agentRoot>/memory/<库 id>/card-proposals/`,不在知识树里。
245
+
246
+ **原文预览只发前 4000 个字符。** `GET /knowledge/documents/:docId` 返回的 `content` 是
247
+ 截断过的,全文长度在 `contentChars`、是否截断在 `contentTruncated`。理由是抽屉只有
248
+ 380px 宽,读不完一份几十万字的文档 —— 而把整份正文发过去正是「打开弹层要等半天」的
249
+ 成因。要读全文走 `.../view` 那个独立整页:它按 `offset`/`limit` **一页一段**地取
250
+ (默认 20000 字符一页,切点对齐空行),所以任何一次响应都不与文档大小成正比。
251
+
265
252
  `/knowledge/stats`、`/knowledge/search`、`/knowledge/rebuild`、`/knowledge/cards`、
266
253
  `/knowledge/sources`、`/knowledge/graph`、`/knowledge/embedding/health` 与全部
267
254
  `*_knowledge*` 工具都接受**可选** `kb`(query 或 body 字段)。`docId` 含 `:`
@@ -293,7 +280,7 @@ GET /knowledge/assets/:kb/* → 知识库
293
280
  **回滚 = 用快照内容重新调用 `addSource`**:因此回滚前会把当时的 head 再快照一次,
294
281
  历史只增不减,**回滚本身也可回滚**。回滚 `current-*` 是 no-op。
295
282
 
296
- ### 召回链路(0031 起:两路 + 图谱 boost)
283
+ ### 召回链路(两路 + 图谱 boost)
297
284
 
298
285
  ```text
299
286
  query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
@@ -303,22 +290,21 @@ query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
303
290
  → R5 弃答(insufficientEvidence)
304
291
  ```
305
292
 
306
- - **R0 分词**(`fts.ts`):中文按**单字 + 相邻双字**切分(不用词典),ASCII 按 `unicode61`
307
- 的口径切开。0030 的 `tokens()` 只按非字母数字切,整句中文会变成一个 token ——
308
- 实测 `排查`→6 命中、`怎么排查指标异常`→**0**。
293
+ - **R0 分词**:中文按**单字 + 相邻双字**切分(不用词典),ASCII 按 `unicode61`
294
+ 的口径切开。
309
295
  - **R1 关键词路**:SQLite FTS5 + **BM25**(`node:sqlite`,零新依赖;不可用时如实降级)。
310
296
  中文之所以不用内置 tokenizer:`unicode61` 不切中文,`trigram` 要求查询 ≥3 字符且会把
311
297
  整句变成互相 AND 的三字窗口。分数按本次最高分归一到 0..1,再过 `keywordThreshold`;
312
298
  另有**多字词覆盖率**门槛(只数双字词与 ASCII 词,单字在中文里不是证据)。
313
299
  - **R1 向量路**:**编码器健康门**(两段无关文本必须余弦 < 0.99,否则整路停用)→
314
- 查询编码一次 → binary/Hamming 预筛 → int8 精算**原始余弦**(`dot / 127²`,0030 多除了
315
- 一个 dimension,把余弦压进 ±0.001、排序完全没有区分度)→ `vectorThreshold`。
316
- - **R2 融合**:**RRF**(`k=60`,关键词 0.3 / 向量 0.7)——只吃**名次**,所以三路分数
317
- 量纲不同这件事不再影响排序(0030 的加权和让向量输出常数时所有命中同分)。
300
+ 查询编码一次 → binary/Hamming 预筛 → int8 精算**原始余弦**(`dot / 127²`)→
301
+ `vectorThreshold`。
302
+ - **R2 融合**:**RRF**(`k=60`,关键词 0.3 / 向量 0.7)——只吃**名次**,所以各路分数
303
+ 量纲不同这件事不再影响排序。
318
304
  - **R2 图谱**:**boost,不是第三路**。它给已经在候选池里的条目加一个 RRF 项,**不能自己
319
305
  制造候选**。代价是「只提到某份来源、没提到任何卡片正文」的查询没有第二条路 ——
320
306
  由关键词索引收录 **note 的溯源身份**(sourceRefs + 来源标题)来补偿。
321
- - **R3 重排**:作用在**候选池**上(不再是已截断的 topK,0014 点名的「分层只做了一半」),
307
+ - **R3 重排**:作用在**候选池**上(不是已截断的 topK),
322
308
  低于 `rerankThreshold` 的一律丢弃并报出丢了几条。阈值与 WeKnora 同刻度(**原始 logit**)。
323
309
  worker 用 `predict_raw()`(`activation_fn=Identity`)返回 logit —— `CrossEncoder.predict`
324
310
  对 `num_labels == 1` 默认套 sigmoid,直接拿返回值比 0.2 会让阈值永远不生效;展示用的
@@ -331,8 +317,7 @@ query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
331
317
  并标注判据强度(`rerank-threshold` / `vector-threshold` / `relative-only` / `not-needed`)。
332
318
  宁可说「没有」,也不凑满 topN 让模型编。
333
319
  - **降级不谎报**:`routes` 只列**通过健康门且真的跑起来**的路;每一路的状态、候选数、
334
- 最高分、阈值与降级原因都在 `diagnostics.routes` 里。0030 的向量路坏掉时照样上报
335
- `routes: ["fts","vector","graph"]`,是这次要修的核心问题之一。
320
+ 最高分、阈值与降级原因都在 `diagnostics.routes` 里。
336
321
  - `mode: 'graph'` 与 `hybrid` **同义**(图谱不能制造候选,「只靠图谱」不成立)。
337
322
  - 阈值初值:`keywordThreshold 0.3` / `rerankThreshold 0.2` 沿用 WeKnora 的出厂值;
338
323
  **`vectorThreshold` 是 0.5,不是它的 0.15** —— 那个数是它自己 embedding 模型的刻度,
@@ -344,14 +329,12 @@ query → R0 分词 → R1 两路各自超采并过阈值(候选池 50)
344
329
  余弦 < 0.99),重排器查分词器词表(`vocab_size ≥ 1000` 且探针句有非 `<unk>` 条目,否则
345
330
  `RERANK_TOKENIZER_DEGENERATE`)。原因是「能加载」不等于「可用」:本地快照缺分词器文件时,
346
331
  重排器照样加载、照样出分(实测三条无关文本 `0.999 / 0.998 / 0.9994`),只有探针能发现。
347
- 修法与运维见 `docs/0024-本地模型运行时.md`。
348
332
  - **编码器指纹** `ENCODER_FINGERPRINT` 写在向量清单里:同一个模型换了「怎么调它」也必须让
349
- 已有向量作废,否则 `rebuild all` 会按 contentHash 把旧向量原样复用(0031 实测过:
350
- 修好编码器后构建仍报 `reused: 44`,修复等于没发生)。
333
+ 已有向量作废,否则 `rebuild all` 会按 contentHash 把旧向量原样复用。
351
334
 
352
335
  `listDocuments()` / `readDocument(docId)` / `listVersions(docId)` /
353
- `revertDocument(docId, versionId)` / `deleteDocument(docId)` 是 `KnowledgeRuntime` 上
354
- 新增的文档接口;详情页的 `assets[].url` 即 `/knowledge/assets/<kbId>/<rel>`。
336
+ `revertDocument(docId, versionId)` / `deleteDocument(docId)` 是文档接口;
337
+ 详情页的 `assets[].url` 即 `/knowledge/assets/<kbId>/<rel>`。
355
338
 
356
339
  ## 评测
357
340
 
package/cordis.patch.yml CHANGED
@@ -112,11 +112,17 @@
112
112
  3. **Never invent a number.** Every figure you report must come from a tool
113
113
  result in this turn, and you must say which one. "I don't have that data" is
114
114
  a correct answer; a made-up number is not.
115
- 4. **Put it in writing while you still have the evidence.** A definition, a
116
- metric's exact calculation, a process, a diagnosed cause — if it will be
117
- asked again, write it as a card with `write_knowledge_card`, with
118
- `source_refs` that already exist in the store. A card whose refs point
119
- nowhere is rejected, and rightly so.
115
+ 4. **Put it in writing while you still have the evidence — as a proposal, not
116
+ a card.** A definition, a metric's exact calculation, a process, a diagnosed
117
+ cause — if it will be asked again, call `propose_wiki_cards` with the
118
+ `source_id` you just wrote and drafts whose `source_refs` already exist in
119
+ the store. A draft whose refs point nowhere is rejected, and rightly so.
120
+ **You cannot write a card yourself**: `write_knowledge_card` only accepts a
121
+ proposal a human has already confirmed, and unconfirmed proposals never
122
+ reach the wiki — they are not compiled, not recalled and not on the graph.
123
+ So say what you proposed and that it is waiting for review; do not claim the
124
+ card exists. Call `list_card_proposals` first so you do not re-propose
125
+ something a human already turned down.
120
126
  5. **Correct a card, do not quietly overwrite it.** When something in the store
121
127
  is wrong, set its status to `problem` and say what is wrong with it. Cards
122
128
  marked `problem` drop out of recall by default — that is how a wrong