dsh-data-domain-knowledge 0.1.0-alpha.2 → 0.1.0-alpha.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.
Files changed (180) hide show
  1. package/README.md +147 -19
  2. package/cordis.patch.yml +25 -10
  3. package/lib/client.js +6400 -3326
  4. package/lib/doc-page.js +7761 -0
  5. package/lib/types/convert/host.d.ts.map +1 -1
  6. package/lib/types/convert/host.js +63 -11
  7. package/lib/types/convert/host.js.map +1 -1
  8. package/lib/types/convert/ingest.d.ts +7 -4
  9. package/lib/types/convert/ingest.d.ts.map +1 -1
  10. package/lib/types/convert/ingest.js +52 -14
  11. package/lib/types/convert/ingest.js.map +1 -1
  12. package/lib/types/convert/types.d.ts +2 -2
  13. package/lib/types/convert/types.d.ts.map +1 -1
  14. package/lib/types/convert/types.js +2 -2
  15. package/lib/types/convert/types.js.map +1 -1
  16. package/lib/types/diagnosis/cards.d.ts +8 -7
  17. package/lib/types/diagnosis/cards.d.ts.map +1 -1
  18. package/lib/types/diagnosis/cards.js +119 -49
  19. package/lib/types/diagnosis/cards.js.map +1 -1
  20. package/lib/types/diagnosis/run.d.ts +57 -0
  21. package/lib/types/diagnosis/run.d.ts.map +1 -0
  22. package/lib/types/diagnosis/run.js +245 -0
  23. package/lib/types/diagnosis/run.js.map +1 -0
  24. package/lib/types/diagnosis/schema.d.ts +25 -2
  25. package/lib/types/diagnosis/schema.d.ts.map +1 -1
  26. package/lib/types/diagnosis/schema.js +64 -25
  27. package/lib/types/diagnosis/schema.js.map +1 -1
  28. package/lib/types/diagnosis/seed-source.d.ts +17 -2
  29. package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
  30. package/lib/types/diagnosis/seed-source.js +19 -2
  31. package/lib/types/diagnosis/seed-source.js.map +1 -1
  32. package/lib/types/diagnosis/seed.d.ts +32 -42
  33. package/lib/types/diagnosis/seed.d.ts.map +1 -1
  34. package/lib/types/diagnosis/seed.js +187 -51
  35. package/lib/types/diagnosis/seed.js.map +1 -1
  36. package/lib/types/diagnosis/store.d.ts +95 -25
  37. package/lib/types/diagnosis/store.d.ts.map +1 -1
  38. package/lib/types/diagnosis/store.js +297 -166
  39. package/lib/types/diagnosis/store.js.map +1 -1
  40. package/lib/types/diagnosis/types.d.ts +168 -16
  41. package/lib/types/diagnosis/types.d.ts.map +1 -1
  42. package/lib/types/diagnosis/types.js +1 -1
  43. package/lib/types/diagnosis/types.js.map +1 -1
  44. package/lib/types/document-page.d.ts +39 -0
  45. package/lib/types/document-page.d.ts.map +1 -0
  46. package/lib/types/document-page.js +43 -0
  47. package/lib/types/document-page.js.map +1 -0
  48. package/lib/types/extract-model.d.ts +67 -27
  49. package/lib/types/extract-model.d.ts.map +1 -1
  50. package/lib/types/extract-model.js +225 -97
  51. package/lib/types/extract-model.js.map +1 -1
  52. package/lib/types/extract.d.ts +16 -0
  53. package/lib/types/extract.d.ts.map +1 -1
  54. package/lib/types/extract.js +19 -2
  55. package/lib/types/extract.js.map +1 -1
  56. package/lib/types/fs-atomic.d.ts +11 -0
  57. package/lib/types/fs-atomic.d.ts.map +1 -0
  58. package/lib/types/fs-atomic.js +41 -0
  59. package/lib/types/fs-atomic.js.map +1 -0
  60. package/lib/types/index.d.ts.map +1 -1
  61. package/lib/types/index.js +466 -63
  62. package/lib/types/index.js.map +1 -1
  63. package/lib/types/intent/types.js +1 -1
  64. package/lib/types/intent/types.js.map +1 -1
  65. package/lib/types/knowledge-bases.d.ts +23 -12
  66. package/lib/types/knowledge-bases.d.ts.map +1 -1
  67. package/lib/types/knowledge-bases.js +37 -44
  68. package/lib/types/knowledge-bases.js.map +1 -1
  69. package/lib/types/layout.d.ts +3 -3
  70. package/lib/types/layout.d.ts.map +1 -1
  71. package/lib/types/layout.js +4 -4
  72. package/lib/types/layout.js.map +1 -1
  73. package/lib/types/migrate.d.ts +1 -1
  74. package/lib/types/migrate.js +2 -2
  75. package/lib/types/migrate.js.map +1 -1
  76. package/lib/types/model-text.d.ts +99 -0
  77. package/lib/types/model-text.d.ts.map +1 -0
  78. package/lib/types/model-text.js +103 -0
  79. package/lib/types/model-text.js.map +1 -0
  80. package/lib/types/present.d.ts +239 -0
  81. package/lib/types/present.d.ts.map +1 -0
  82. package/lib/types/present.js +255 -0
  83. package/lib/types/present.js.map +1 -0
  84. package/lib/types/recall-rank.d.ts +37 -0
  85. package/lib/types/recall-rank.d.ts.map +1 -1
  86. package/lib/types/recall-rank.js +41 -1
  87. package/lib/types/recall-rank.js.map +1 -1
  88. package/lib/types/runtime.d.ts +564 -31
  89. package/lib/types/runtime.d.ts.map +1 -1
  90. package/lib/types/runtime.js +2645 -292
  91. package/lib/types/runtime.js.map +1 -1
  92. package/lib/types/settings-schema.d.ts +4 -2
  93. package/lib/types/settings-schema.d.ts.map +1 -1
  94. package/lib/types/settings-schema.js +2 -0
  95. package/lib/types/settings-schema.js.map +1 -1
  96. package/lib/types/source-images.d.ts +118 -0
  97. package/lib/types/source-images.d.ts.map +1 -0
  98. package/lib/types/source-images.js +288 -0
  99. package/lib/types/source-images.js.map +1 -0
  100. package/lib/types/types.d.ts +356 -28
  101. package/lib/types/types.d.ts.map +1 -1
  102. package/lib/types/types.js +112 -18
  103. package/lib/types/types.js.map +1 -1
  104. package/lib/types/wiki-finalize.d.ts +140 -0
  105. package/lib/types/wiki-finalize.d.ts.map +1 -0
  106. package/lib/types/wiki-finalize.js +269 -0
  107. package/lib/types/wiki-finalize.js.map +1 -0
  108. package/lib/types/wiki-types.d.ts +10 -2
  109. package/lib/types/wiki-types.d.ts.map +1 -1
  110. package/lib/types/wiki-types.js +27 -30
  111. package/lib/types/wiki-types.js.map +1 -1
  112. package/package.json +4 -3
  113. package/preset/tita-data-agent/agent.cordis.yml +25 -10
  114. package/src/client/DirectoryPicker.tsx +12 -12
  115. package/src/client/DrawerSection.tsx +18 -0
  116. package/src/client/InfoHint.tsx +21 -8
  117. package/src/client/IntentTab.tsx +20 -26
  118. package/src/client/KnowledgeBaseTab.tsx +458 -158
  119. package/src/client/KnowledgePanel.tsx +44 -10
  120. package/src/client/KnowledgeStepRail.tsx +3 -0
  121. package/src/client/KnowledgeToolRow.tsx +331 -0
  122. package/src/client/NodeSettings.tsx +20 -22
  123. package/src/client/PreviewSections.tsx +178 -43
  124. package/src/client/RecallPanel.tsx +418 -127
  125. package/src/client/TypeDefinitionDrawer.tsx +154 -54
  126. package/src/client/WikiCardReview.tsx +266 -0
  127. package/src/client/WikiFlowCard.tsx +57 -95
  128. package/src/client/WikiTypeEditor.tsx +197 -95
  129. package/src/client/arch/AgentArchitectureGraph.tsx +30 -14
  130. package/src/client/arch/NodeLogicPanel.tsx +6 -3
  131. package/src/client/arch/model.ts +164 -26
  132. package/src/client/architecture.css +25 -10
  133. package/src/client/architecture.tsx +66 -41
  134. package/src/client/asset-images.ts +125 -0
  135. package/src/client/attribution/AttributionGraph.tsx +481 -34
  136. package/src/client/attribution/AttributionTab.tsx +501 -106
  137. package/src/client/attribution/CaseView.tsx +9 -14
  138. package/src/client/attribution/GraphStage.tsx +25 -18
  139. package/src/client/attribution/RunView.tsx +27 -9
  140. package/src/client/attribution/StageChrome.tsx +280 -140
  141. package/src/client/attribution/graph-options.ts +611 -47
  142. package/src/client/attribution/model.ts +559 -30
  143. package/src/client/convert/ConvertPanel.tsx +36 -4
  144. package/src/client/doc-page.css +134 -0
  145. package/src/client/doc-page.tsx +242 -0
  146. package/src/client/index.ts +18 -0
  147. package/src/client/panel-state.ts +105 -0
  148. package/src/client/problem-domains.ts +1 -1
  149. package/src/client/request.ts +36 -0
  150. package/src/client/styles.css +397 -54
  151. package/src/client/useFitScale.ts +15 -2
  152. package/src/client/wiki-type-groups.ts +86 -0
  153. package/src/convert/host.ts +68 -9
  154. package/src/convert/ingest.ts +61 -18
  155. package/src/convert/types.ts +2 -2
  156. package/src/diagnosis/cards.ts +120 -50
  157. package/src/diagnosis/run.ts +251 -0
  158. package/src/diagnosis/schema.ts +67 -26
  159. package/src/diagnosis/seed-source.ts +29 -4
  160. package/src/diagnosis/seed.ts +187 -85
  161. package/src/diagnosis/store.ts +310 -173
  162. package/src/diagnosis/types.ts +181 -16
  163. package/src/document-page.ts +51 -0
  164. package/src/extract-model.ts +254 -99
  165. package/src/extract.ts +35 -3
  166. package/src/fs-atomic.ts +41 -0
  167. package/src/index.ts +499 -62
  168. package/src/intent/types.ts +1 -1
  169. package/src/knowledge-bases.ts +50 -49
  170. package/src/layout.ts +4 -4
  171. package/src/migrate.ts +2 -2
  172. package/src/model-text.ts +126 -0
  173. package/src/present.ts +367 -0
  174. package/src/recall-rank.ts +57 -2
  175. package/src/runtime.ts +2802 -293
  176. package/src/settings-schema.ts +2 -0
  177. package/src/source-images.ts +326 -0
  178. package/src/types.ts +446 -45
  179. package/src/wiki-finalize.ts +310 -0
  180. package/src/wiki-types.ts +28 -32
@@ -131,7 +131,7 @@ export const DEFAULT_CATEGORIES: Array<IntentCategory & { examples: string[] }>
131
131
 
132
132
  export const DEFAULT_PREDICATES: string[] = [
133
133
  'query_babel_data', 'query_knowledge', 'query_source_truth', 'query_metadata',
134
- 'query_graph', 'add_source', 'write_knowledge_card', 'rebuild_knowledge',
134
+ 'query_graph', 'add_source', 'propose_wiki_cards', 'rebuild_knowledge',
135
135
  'send_message', 'export_artifact', 'unknown',
136
136
  ]
137
137
 
@@ -2,7 +2,7 @@
2
2
  * 多知识库注册表(0021)。
3
3
  *
4
4
  * 产品参照 WeKnora:**一个知识库 = 独立配置 + 独立内容**。每个库有自己的
5
- * `rootDir`(沿用既有的 `01_source_truth` / `02_wiki` / `03_compiled` /
5
+ * `rootDir`(沿用既有的 `01_source_raw` / `02_wiki` / `03_compiled` /
6
6
  * `04_memory` 布局)和一套「文件类型 → 解析引擎」规则;库下面挂文档列表、
7
7
  * 文档详情与版本历史。
8
8
  *
@@ -29,15 +29,15 @@ import { homedir } from 'node:os'
29
29
  import { dirname, join, resolve } from 'node:path'
30
30
  import {
31
31
  BUILTIN_SOURCE_TYPES, defaultSourceTypes, FALLBACK_SOURCE_TYPE, isBuiltinSourceType,
32
- MAX_SOURCE_TYPES, SOURCE_TYPE_ID_PATTERN, validateSourceTypeDraft, validateSourceTypeText, WIKI_TYPE_ID_PATTERN,
32
+ LEGACY_SOURCE_TYPE_IDS, MAX_SOURCE_TYPES, normalizeMetadataFields, SOURCE_TYPE_ID_PATTERN, validateSourceTypeDraft, validateSourceTypeText, WIKI_TYPE_ID_PATTERN,
33
33
  type Envelope, type KnowledgeBase, type KnowledgeConfig,
34
- type SourceTypeDef, type SourceTypeDraft, type WikiSchemaMode, type WikiTypeDef,
34
+ type SourceTypeDef, type SourceTypeDraft, type WikiTypeDef,
35
35
  } from './types.js'
36
36
  import {
37
- defaultWikiTypes, isBuiltinWikiType, parseFieldLines, parseSectionLines, templateCardFor,
37
+ defaultWikiTypes, isBuiltinWikiType, LEGACY_WIKI_TYPE_IDS, parseFieldLines, parseSectionLines, templateCardFor,
38
38
  validateWikiTypeDraft, validateWikiTypeText, type WikiTypeDraft,
39
39
  } from './wiki-types.js'
40
- import { defaultKnowledgeRoot, failure, KnowledgeRuntime, success } from './runtime.js'
40
+ import { defaultKnowledgeRoot, failure, KnowledgeRuntime, success, type KnowledgeRuntimeOptions } from './runtime.js'
41
41
  import { knowledgeBaseRoot, memoryRoot } from './layout.js'
42
42
 
43
43
  /** 持久化形态:配置 + 计算字段缓存分区。 */
@@ -50,8 +50,6 @@ interface KnowledgeBaseRecord {
50
50
  sourceTypes: SourceTypeDef[]
51
51
  /** Wiki 卡片类型注册表(0024)。 */
52
52
  wikiTypes: WikiTypeDef[]
53
- /** `report` 只标记问题,`strict` 拒绝不合规的写入。 */
54
- wikiSchemaMode: WikiSchemaMode
55
53
  createdAt: string
56
54
  updatedAt: string
57
55
  }
@@ -96,6 +94,16 @@ export interface KnowledgeBasesOptions {
96
94
  defaultRuntime?: () => KnowledgeRuntime
97
95
  /** 非默认库的 runtime 配置来源(embedding / rerank / topK 等跟随插件设置)。 */
98
96
  config?: () => Partial<Omit<KnowledgeConfig, 'rootDir' | 'agentRoot'>>
97
+ /**
98
+ * 非默认库的 runtime **附加能力**来源(0044:模型接缝)。
99
+ *
100
+ * 与 `config` 的分工:`config` 是「这个库自己的设置」(embedding / rerank / topK),
101
+ * 而这里给的是「宿主才有的能力」—— 典型是 `modelSeam`,它要问插件所在的 `ctx`
102
+ * 才拿得到 `llm` 与默认模型。之前**只有默认库**的 runtime 带了模型接缝(那一个在
103
+ * `index.ts` 里直接 new),新建的库一律退回规则抽取:正文按 `##` 小节切,
104
+ * 界面上只留一句「运行时没有注入模型接缝」。这跟写库 id 无关,纯粹是构造点漏了。
105
+ */
106
+ runtimeOptions?: () => Partial<KnowledgeRuntimeOptions>
99
107
  /**
100
108
  * 覆盖注册表文件位置(默认 `<dataHome>/knowledge-bases.json`)。
101
109
  *
@@ -124,7 +132,7 @@ function normalizeExt(value: unknown, fallback: string): string {
124
132
  function emptyBase(id: string): KnowledgeBase {
125
133
  return {
126
134
  id, name: id, description: '', rootDir: '', isDefault: false, sourceTypes: [],
127
- wikiTypes: [], wikiSchemaMode: 'report', wikiTypesCustomized: false,
135
+ wikiTypes: [], wikiTypesCustomized: false,
128
136
  createdAt: '', updatedAt: '', sourceCount: 0, cardCount: 0, noteCount: 0, buildVersion: 0,
129
137
  }
130
138
  }
@@ -236,10 +244,12 @@ export class KnowledgeBases {
236
244
  ? this.options.defaultRuntime()
237
245
  : new KnowledgeRuntime(base.rootDir, this.options.config?.() ?? {}, {
238
246
  sourceTypes: () => this.sourceTypesOf(base.id),
239
- wikiSchema: () => ({ types: this.wikiTypesOf(base.id), mode: base.wikiSchemaMode }),
247
+ wikiSchema: () => ({ types: this.wikiTypesOf(base.id) }),
240
248
  // 0032:记忆段跟着**这个库**走,二级用 id。`agentRoot` 没给时(只测注册表
241
249
  // 逻辑的单元测试)留空,runtime 会退回知识树里的 `04_memory`。
242
250
  ...(this.options.agentRoot ? { memoryRoot: memoryRoot(this.options.agentRoot(), base.id) } : {}),
251
+ // 0044:宿主级能力(模型接缝)必须一起带过来,否则非默认库静默退回规则抽取。
252
+ ...(this.options.runtimeOptions?.() ?? {}),
243
253
  })
244
254
  runtime.baseId = base.id
245
255
  this.runtimes.set(key, runtime)
@@ -333,7 +343,6 @@ export class KnowledgeBases {
333
343
  isDefault: false,
334
344
  sourceTypes: defaultSourceTypes(),
335
345
  wikiTypes: defaultWikiTypes(),
336
- wikiSchemaMode: 'report',
337
346
  createdAt: now,
338
347
  updatedAt: now,
339
348
  }
@@ -354,7 +363,7 @@ export class KnowledgeBases {
354
363
  * 恢复,并给出 warning)、兜底分类 `document` 不允许停用、新建自定义分类必须
355
364
  * 提交完整判定文案。
356
365
  */
357
- async update(id: string, patch: { name?: string; description?: string; sourceTypes?: SourceTypeDef[]; wikiTypes?: WikiTypeDef[]; wikiSchemaMode?: WikiSchemaMode; resetWikiTypes?: boolean }): Promise<Envelope<KnowledgeBase>> {
366
+ async update(id: string, patch: { name?: string; description?: string; sourceTypes?: SourceTypeDef[]; wikiTypes?: WikiTypeDef[]; resetWikiTypes?: boolean }): Promise<Envelope<KnowledgeBase>> {
358
367
  this.ensureLoaded()
359
368
  const base = this.records.find(item => item.id === id)
360
369
  if (!base) return failure(emptyBase(id), 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true)
@@ -387,7 +396,6 @@ export class KnowledgeBases {
387
396
  if (patch.description !== undefined) base.description = String(patch.description)
388
397
  if (sourceTypes !== undefined) base.sourceTypes = sourceTypes
389
398
  if (wikiTypes !== undefined) base.wikiTypes = wikiTypes
390
- if (patch.wikiSchemaMode !== undefined) base.wikiSchemaMode = patch.wikiSchemaMode === 'strict' ? 'strict' : 'report'
391
399
  base.updatedAt = new Date().toISOString()
392
400
  await this.refreshOne(base)
393
401
  await this.persist()
@@ -436,6 +444,7 @@ export class KnowledgeBases {
436
444
  enabled: draft.enabled !== false,
437
445
  builtin: false,
438
446
  fallback: false,
447
+ ...(normalizeMetadataFields(draft.metadataFields).length > 0 ? { metadataFields: normalizeMetadataFields(draft.metadataFields) } : {}),
439
448
  sourceCount: 0,
440
449
  }
441
450
  base.sourceTypes = [...base.sourceTypes, created]
@@ -467,10 +476,14 @@ export class KnowledgeBases {
467
476
  signals: patch.signals ?? existing.signals,
468
477
  excludes: patch.excludes ?? existing.excludes,
469
478
  enabled: patch.enabled === undefined ? existing.enabled : patch.enabled !== false,
479
+ // 0048:`metadataFields` 缺省继承磁盘上已有的(与 signals 同一条约定:
480
+ // 「没传」不等于「清空」,否则一次改文案就会把字段声明悄悄抹掉)。
481
+ metadataFields: patch.metadataFields ?? existing.metadataFields,
470
482
  }
471
483
  const known = new Set(base.sourceTypes.map(def => def.id))
472
484
  const problem = validateSourceTypeText(merged, known)
473
485
  if (!problem.ok) return failure({} as SourceTypeDef, problem.code, problem.message, false, problem.suggestion)
486
+ const declared = normalizeMetadataFields(merged.metadataFields)
474
487
  const next: SourceTypeDef = {
475
488
  ...existing,
476
489
  label: String(merged.label).trim(),
@@ -478,6 +491,7 @@ export class KnowledgeBases {
478
491
  signals: (merged.signals as unknown[]).map(item => String(item).trim()).filter(Boolean),
479
492
  excludes: (merged.excludes as unknown[]).map(item => String(item).trim()).filter(Boolean),
480
493
  enabled: merged.enabled === true,
494
+ ...(declared.length > 0 ? { metadataFields: declared } : { metadataFields: undefined }),
481
495
  }
482
496
  base.sourceTypes = base.sourceTypes.map(def => def.id === typeId ? next : def)
483
497
  base.updatedAt = new Date().toISOString()
@@ -549,12 +563,6 @@ export class KnowledgeBases {
549
563
  return base.wikiTypes.map(def => ({ ...def, cardCount: counts[def.id] ?? 0 }))
550
564
  }
551
565
 
552
- /** 卡片 schema 生效方式;未知 id 回退出厂值。 */
553
- wikiSchemaModeOf(id: string): WikiSchemaMode {
554
- this.ensureLoaded()
555
- return this.records.find(item => item.id === id)?.wikiSchemaMode ?? 'report'
556
- }
557
-
558
566
  /**
559
567
  * 新增一个卡片类型。
560
568
  *
@@ -592,7 +600,6 @@ export class KnowledgeBases {
592
600
  layer: patch.layer ?? existing.layer,
593
601
  sections: patch.sections ?? existing.sections,
594
602
  fields: patch.fields ?? existing.fields,
595
- sourceTypes: patch.sourceTypes ?? existing.sourceTypes,
596
603
  examples: patch.examples ?? existing.examples,
597
604
  }
598
605
  const problem = validateWikiTypeText(merged)
@@ -606,7 +613,6 @@ export class KnowledgeBases {
606
613
  layer: merged.layer === 'instance' ? 'instance' : 'type',
607
614
  sections,
608
615
  fields,
609
- sourceTypes: this.stringList(merged.sourceTypes),
610
616
  examples: this.stringList(merged.examples),
611
617
  template: templateCardFor(sections, fields),
612
618
  enabled: patch.enabled === undefined ? existing.enabled : patch.enabled !== false,
@@ -642,17 +648,6 @@ export class KnowledgeBases {
642
648
  return success({ deleted: true })
643
649
  }
644
650
 
645
- /** 切换卡片 schema 的生效方式(`report` 只标记,`strict` 拒绝不合规的写入)。 */
646
- async setWikiSchemaMode(id: string, mode: WikiSchemaMode): Promise<Envelope<{ mode: WikiSchemaMode }>> {
647
- this.ensureLoaded()
648
- const base = this.records.find(item => item.id === id)
649
- if (!base) return failure({ mode }, 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true)
650
- base.wikiSchemaMode = mode === 'strict' ? 'strict' : 'report'
651
- base.updatedAt = new Date().toISOString()
652
- await this.persist()
653
- return success({ mode: base.wikiSchemaMode })
654
- }
655
-
656
651
  /** 把一条 draft 转成完整类型(模板卡由小节派生,不手写第二份)。 */
657
652
  private wikiTypeFromDraft(typeId: string, draft: WikiTypeDraft): WikiTypeDef {
658
653
  const sections = parseSectionLines(draft.sections)
@@ -664,7 +659,6 @@ export class KnowledgeBases {
664
659
  layer: draft.layer === 'instance' ? 'instance' : 'type',
665
660
  sections,
666
661
  fields,
667
- sourceTypes: this.stringList(draft.sourceTypes),
668
662
  examples: this.stringList(draft.examples),
669
663
  template: templateCardFor(sections, fields),
670
664
  enabled: true,
@@ -683,15 +677,21 @@ export class KnowledgeBases {
683
677
  *
684
678
  * 与 `normalizeSourceTypes` 同一套语义:老配置(没有 `wikiTypes` 字段)升级时补齐内置,
685
679
  * 缺失的内置类型被恢复(内置只允许停用,不允许删除)。
680
+ *
681
+ * 0040 的两次改名(`check` → `check_point`、`failure-mode` → `failure_reason`)在这里
682
+ * 也认别名:存量配置里那一项写着旧 id,不认的话它会被当成一个**自定义类型**留下来,
683
+ * 而内置的那一项会被补回来 —— 于是注册表里同时有 `diagnosis/check` 与
684
+ * `diagnosis/check-point` 两个类型,卡片该归哪个就再也说不清了。
686
685
  */
687
686
  private normalizeWikiTypes(raw: unknown): WikiTypeDef[] {
688
687
  const persisted = Array.isArray(raw) ? raw : []
689
688
  const byId = new Map<string, Record<string, unknown>>()
690
689
  for (const item of persisted) {
691
690
  const value = record(item)
692
- const id = typeof value.id === 'string' ? value.id.trim() : ''
691
+ const rawId = typeof value.id === 'string' ? value.id.trim() : ''
692
+ const id = LEGACY_WIKI_TYPE_IDS[rawId] ?? rawId
693
693
  if (!id || !WIKI_TYPE_ID_PATTERN.test(id) || byId.has(id)) continue
694
- byId.set(id, value)
694
+ byId.set(id, { ...value, id })
695
695
  }
696
696
  const defaults = defaultWikiTypes()
697
697
  const builtins = defaults.map(def => {
@@ -703,7 +703,7 @@ export class KnowledgeBases {
703
703
  const customs: WikiTypeDef[] = []
704
704
  for (const [id, value] of byId) {
705
705
  if (isBuiltinWikiType(id)) continue
706
- const base: WikiTypeDef = { id, label: id, description: '', layer: 'type', sections: [], fields: [], sourceTypes: [], examples: [], template: '', enabled: true, builtin: false, cardCount: 0 }
706
+ const base: WikiTypeDef = { id, label: id, description: '', layer: 'type', sections: [], fields: [], examples: [], template: '', enabled: true, builtin: false, cardCount: 0 }
707
707
  const draft = this.wikiTypeFromRecord(base, value)
708
708
  // 只有能写出骨架的自定义类型才值得留下;否则它既没有模板也校验不了。
709
709
  if (validateWikiTypeText(draft).ok) customs.push(draft)
@@ -722,7 +722,6 @@ export class KnowledgeBases {
722
722
  layer: value.layer === 'instance' ? 'instance' : base.layer,
723
723
  sections: sections.length > 0 ? sections : base.sections.map(section => ({ ...section })),
724
724
  fields: Array.isArray(value.fields) ? fields : base.fields.map(field => ({ ...field })),
725
- sourceTypes: Array.isArray(value.sourceTypes) ? this.stringList(value.sourceTypes) : [...base.sourceTypes],
726
725
  examples: this.stringList(value.examples).length > 0 ? this.stringList(value.examples) : [...base.examples],
727
726
  template: textOr(value.template, base.template),
728
727
  enabled: value.enabled !== false,
@@ -735,7 +734,7 @@ export class KnowledgeBases {
735
734
  private wikiTypesDifferFromDefaults(types: WikiTypeDef[]): boolean {
736
735
  const signature = (list: WikiTypeDef[]): string => JSON.stringify(list.map(def => ({
737
736
  id: def.id, label: def.label, description: def.description, layer: def.layer, enabled: def.enabled,
738
- sections: def.sections, fields: def.fields, sourceTypes: def.sourceTypes, examples: def.examples,
737
+ sections: def.sections, fields: def.fields, examples: def.examples,
739
738
  })))
740
739
  const defaults = defaultWikiTypes()
741
740
  if (types.length !== defaults.length) return true
@@ -753,7 +752,6 @@ export class KnowledgeBases {
753
752
  const base = this.records.find(item => item.id === id)
754
753
  if (!base) return failure(emptyBase(id), 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true)
755
754
  base.wikiTypes = defaultWikiTypes()
756
- base.wikiSchemaMode = 'report'
757
755
  base.updatedAt = new Date().toISOString()
758
756
  await this.refreshOne(base)
759
757
  await this.persist()
@@ -783,7 +781,6 @@ export class KnowledgeBases {
783
781
  isDefault: base.isDefault,
784
782
  sourceTypes: base.sourceTypes.map(def => ({ ...def, sourceCount: stats?.sourceTypes?.[def.id] ?? 0 })),
785
783
  wikiTypes: base.wikiTypes.map(def => ({ ...def, cardCount: stats?.wikiTypes?.[def.id] ?? 0 })),
786
- wikiSchemaMode: base.wikiSchemaMode,
787
784
  wikiTypesCustomized: this.wikiTypesDifferFromDefaults(base.wikiTypes),
788
785
  createdAt: base.createdAt,
789
786
  updatedAt: base.updatedAt,
@@ -804,7 +801,6 @@ export class KnowledgeBases {
804
801
  isDefault: true,
805
802
  sourceTypes: defaultSourceTypes(),
806
803
  wikiTypes: defaultWikiTypes(),
807
- wikiSchemaMode: 'report',
808
804
  createdAt: now,
809
805
  updatedAt: now,
810
806
  }
@@ -869,7 +865,6 @@ export class KnowledgeBases {
869
865
  isDefault: value.isDefault === true || id === this.defaultId,
870
866
  sourceTypes: this.normalizeSourceTypes(value.sourceTypes),
871
867
  wikiTypes: this.normalizeWikiTypes(value.wikiTypes),
872
- wikiSchemaMode: value.wikiSchemaMode === 'strict' ? 'strict' : 'report',
873
868
  createdAt: typeof value.createdAt === 'string' && value.createdAt ? value.createdAt : now,
874
869
  updatedAt: typeof value.updatedAt === 'string' && value.updatedAt ? value.updatedAt : now,
875
870
  }
@@ -880,18 +875,22 @@ export class KnowledgeBases {
880
875
  /**
881
876
  * 把磁盘上的 `sourceTypes` 规整成一个**必定包含全部内置分类**的合法注册表。
882
877
  *
883
- * 两个关键行为:
878
+ * 三个关键行为:
884
879
  * 1. 老注册表(没有 `sourceTypes` 字段)升级时自动补上全部内置分类;
885
- * 2. 任何缺失的内置分类都会被补回来——内置分类只允许停用,不允许删除。
880
+ * 2. 任何缺失的内置分类都会被补回来——内置分类只允许停用,不允许删除;
881
+ * 3. **改名过的内置分类按别名认**(`LEGACY_SOURCE_TYPE_IDS`):存量配置里那一项写着
882
+ * 旧 id,不认的话它会被当成一个自定义分类留下来,而新的内置项同时被补回来 ——
883
+ * 于是「注册表里到底有什么」永远说不清。
886
884
  */
887
885
  private normalizeSourceTypes(raw: unknown): SourceTypeDef[] {
888
886
  const persisted = Array.isArray(raw) ? raw : []
889
887
  const byId = new Map<string, Record<string, unknown>>()
890
888
  for (const item of persisted) {
891
889
  const value = record(item)
892
- const id = typeof value.id === 'string' ? value.id.trim() : ''
890
+ const rawId = typeof value.id === 'string' ? value.id.trim() : ''
891
+ const id = LEGACY_SOURCE_TYPE_IDS[rawId] ?? rawId
893
892
  if (!id || !SOURCE_TYPE_ID_PATTERN.test(id) || byId.has(id)) continue
894
- byId.set(id, value)
893
+ byId.set(id, { ...value, id })
895
894
  }
896
895
  const defaults = defaultSourceTypes()
897
896
  const builtins = defaults.map(def => {
@@ -928,6 +927,8 @@ export class KnowledgeBases {
928
927
  enabled: base.fallback ? true : value.enabled !== false,
929
928
  builtin: base.builtin,
930
929
  fallback: base.fallback,
930
+ // 0048:元数据字段声明跟着分类走。缺省继承默认值(内置分类出厂没有声明)。
931
+ metadataFields: normalizeMetadataFields(value.metadataFields ?? base.metadataFields),
931
932
  sourceCount: 0,
932
933
  }
933
934
  }
@@ -983,6 +984,7 @@ export class KnowledgeBases {
983
984
  signals: (draft.signals as unknown[]).map(item => String(item).trim()).filter(Boolean),
984
985
  excludes: (draft.excludes as unknown[]).map(item => String(item).trim()).filter(Boolean),
985
986
  enabled: seed.fallback ? true : draft.enabled !== false,
987
+ metadataFields: normalizeMetadataFields(value.metadataFields ?? previous.metadataFields),
986
988
  sourceCount: 0,
987
989
  })
988
990
  }
@@ -1007,6 +1009,9 @@ export class KnowledgeBases {
1007
1009
  signals: (draft.signals as unknown[]).map(item => String(item).trim()).filter(Boolean),
1008
1010
  excludes: (draft.excludes as unknown[]).map(item => String(item).trim()).filter(Boolean),
1009
1011
  enabled: draft.enabled !== false, builtin: false, fallback: false, sourceCount: 0,
1012
+ ...(normalizeMetadataFields(value.metadataFields ?? previous?.metadataFields).length > 0
1013
+ ? { metadataFields: normalizeMetadataFields(value.metadataFields ?? previous?.metadataFields) }
1014
+ : {}),
1010
1015
  })
1011
1016
  }
1012
1017
  if (keptCustoms.length > Math.max(0, MAX_SOURCE_TYPES - BUILTIN_SOURCE_TYPES.length)) {
@@ -1065,7 +1070,6 @@ export class KnowledgeBases {
1065
1070
  layer: value.layer === 'instance' ? 'instance' : previous.layer,
1066
1071
  sections: Array.isArray(value.sections) ? value.sections : previous.sections,
1067
1072
  fields: Array.isArray(value.fields) ? value.fields : previous.fields,
1068
- sourceTypes: Array.isArray(value.sourceTypes) ? value.sourceTypes : previous.sourceTypes,
1069
1073
  examples: Array.isArray(value.examples) ? value.examples : previous.examples,
1070
1074
  }
1071
1075
  const problem = validateWikiTypeText(draft)
@@ -1079,7 +1083,6 @@ export class KnowledgeBases {
1079
1083
  layer: draft.layer === 'instance' ? 'instance' : 'type',
1080
1084
  sections,
1081
1085
  fields,
1082
- sourceTypes: this.stringList(draft.sourceTypes),
1083
1086
  examples: this.stringList(draft.examples),
1084
1087
  template: templateCardFor(sections, fields),
1085
1088
  enabled: value.enabled === undefined ? previous.enabled : value.enabled !== false,
@@ -1098,7 +1101,6 @@ export class KnowledgeBases {
1098
1101
  layer: value.layer === 'instance' ? 'instance' : previous?.layer ?? 'type',
1099
1102
  sections: Array.isArray(value.sections) ? value.sections : previous?.sections ?? [],
1100
1103
  fields: Array.isArray(value.fields) ? value.fields : previous?.fields ?? [],
1101
- sourceTypes: Array.isArray(value.sourceTypes) ? value.sourceTypes : previous?.sourceTypes ?? [],
1102
1104
  examples: Array.isArray(value.examples) ? value.examples : previous?.examples ?? [],
1103
1105
  }
1104
1106
  const problem = previous ? validateWikiTypeText(draft) : validateWikiTypeDraft(draft, new Set(existing.map(def => def.id)))
@@ -1112,7 +1114,6 @@ export class KnowledgeBases {
1112
1114
  layer: draft.layer === 'instance' ? 'instance' : 'type',
1113
1115
  sections,
1114
1116
  fields,
1115
- sourceTypes: this.stringList(draft.sourceTypes),
1116
1117
  examples: this.stringList(draft.examples),
1117
1118
  template: templateCardFor(sections, fields),
1118
1119
  enabled: value.enabled === undefined ? previous?.enabled ?? true : value.enabled !== false,
package/src/layout.ts CHANGED
@@ -14,7 +14,7 @@
14
14
  * <agentRoot>/
15
15
  * domain_knowledge/ 知识本身(唯一的真相来源,必须备份)
16
16
  * <知识库 id>/ 一个知识库 = 一棵独立的树(0021)
17
- * 01_source_truth/ SourceRaw:原始可追溯文档
17
+ * 01_source_raw/ SourceRaw:原始可追溯文档
18
18
  * 02_wiki/ Wiki 卡片(人可读的结论)
19
19
  * 03_compiled/ 编译产物:wikiNotes / 向量 / 知识图谱
20
20
  * 05_diagnosis/ 智能归因图谱(派生层,可重建)
@@ -33,7 +33,7 @@
33
33
  *
34
34
  * 1. **`domain_knowledge` 与 `output` / `tmp` 的边界是「能不能重建」**,不是
35
35
  * 「谁写的」。知识树里每一样东西要么是别的层重建出来的(`03_compiled`、
36
- * `05_diagnosis`),要么是不可再生的原始记录(`01_source_truth`、`02_wiki`)——
36
+ * `05_diagnosis`),要么是不可再生的原始记录(`01_source_raw`、`02_wiki`)——
37
37
  * 它们同属一个必须整体备份的单位,所以同在一棵树下、按知识库分叉。`output` 是
38
38
  * **交付物**:转换出来的 Markdown 与图片,用户会把它拷走或录入知识库;`tmp` 是
39
39
  * **中间态**:转换的工作目录与任务队列快照,删掉最多让一个未完成的任务失败。
@@ -319,10 +319,10 @@ export function intentRoot(agentRoot: string): string {
319
319
  * 的空壳(注册表 `create()` 就会先 `mkdir`),空壳不该让迁移误判为「目标已占用」。
320
320
  */
321
321
  export function looksLikeKnowledgeTree(path: string): boolean {
322
- return existsSync(join(path, '01_source_truth')) || existsSync(join(path, '03_compiled'))
322
+ return existsSync(join(path, '01_source_raw')) || existsSync(join(path, '03_compiled'))
323
323
  }
324
324
 
325
- /** 旧布局的判据:`01_source_truth` 直接躺在被检查的目录下。 */
325
+ /** 旧布局的判据:`01_source_raw` 直接躺在被检查的目录下。 */
326
326
  export function looksLikeLegacyKnowledgeRoot(path: string): boolean {
327
327
  if (!path) return false
328
328
  return basename(path) !== '' && looksLikeKnowledgeTree(path)
package/src/migrate.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * ## 为什么要迁移
5
5
  *
6
6
  * 0029 之前,「知识树在哪」由用户直接设的那个 `rootDir` 决定,而它指向的是**知识树
7
- * 本身** —— `01_source_truth` 直接躺在它下面。新布局多了一层:`rootDir` 变成
7
+ * 本身** —— `01_source_raw` 直接躺在它下面。新布局多了一层:`rootDir` 变成
8
8
  * `agentRoot`,知识树退到 `<agentRoot>/domain_knowledge/<知识库 id>`。
9
9
  *
10
10
  * 也就是说,对**改过** `rootDir` 的用户,磁盘上那棵树的位置变了。数据不能跟着
@@ -43,7 +43,7 @@ import { knowledgeBaseRoot, looksLikeKnowledgeTree, memoryRoot, resourceRoot } f
43
43
  * `<agentRoot>/memory/<库 id>`,由 `migrateLegacyMemory` 负责搬。它留在这里会让
44
44
  * 知识树迁移把 `04_memory` 当成「一段知识」搬进新树 —— 那是搬错方向。
45
45
  */
46
- const LAYOUT_DIRS = ['01_source_truth', '02_wiki', '03_compiled', '05_diagnosis'] as const
46
+ const LAYOUT_DIRS = ['01_source_raw', '02_wiki', '03_compiled', '05_diagnosis'] as const
47
47
 
48
48
  export interface MigrationOutcome {
49
49
  /** `skipped` 无事可做 / `moved` 搬成功 / `failed` 搬失败。 */
@@ -0,0 +1,126 @@
1
+ /**
2
+ * 把平台 `llm` 服务的 chunk 流收成一段文本(0044 实机验证第三批)。
3
+ *
4
+ * ## 为什么单独一个文件
5
+ *
6
+ * 它可以在没有 DSH 上下文的情况下被测试:喂一串假 chunk,看结论对不对。
7
+ * 这段逻辑躲在 `index.ts` 的接缝里时,唯一能验证它的方法是「真跑一次编译再看告警」——
8
+ * 而正是因为没人那样做,下面这个坑活了很久。
9
+ *
10
+ * ## 那个坑:模型失败**不是抛异常**,是一条 finish chunk
11
+ *
12
+ * `LlmRuntime.stream()` 的契约写得很清楚:adapter 选择、派发、迭代的失败都会变成
13
+ * **终结性的 `finish` chunk**(`reason.kind === 'error' | 'aborted'`,里面带
14
+ * `failure.code` / `failure.message`)。只有中间件、嵌套调用这类问题才抛。
15
+ *
16
+ * 第一版接缝只挑 `text-delta` 累加文本,于是:
17
+ *
18
+ * - 模型调用失败(比如默认模型那一行选错了 provider)→ 一个文本都没收到 → 收成空串;
19
+ * - 空串交给 JSON 解析 → 「模型输出不是 JSON 对象」;
20
+ * - **真正的失败原因(code / message)被丢掉了**,人看到的是「模型答非所问」,
21
+ * 而事实是「模型根本没被调到」。
22
+ *
23
+ * 这两种故障的处理方式完全不同:一个是改提示词,一个是去改设置。所以这里把
24
+ * finish chunk 读出来,并在没有任何文本时**主动抛**——抛出去的 message 会原样出现在
25
+ * `rebuild` 的告警里(`extractWithModel` 接到异常后写的是「模型抽取不可用:…」)。
26
+ */
27
+
28
+ /** 平台 chunk 的最小形状。只声明这里用得到的字段,避免把平台类型引进来。 */
29
+ interface StreamChunkLike {
30
+ type?: string
31
+ text?: string
32
+ reason?: { kind?: string; failure?: { code?: string; message?: string } }
33
+ }
34
+
35
+ /**
36
+ * 组一次模型请求。
37
+ *
38
+ * ## 为什么单独一个函数
39
+ *
40
+ * 实机验证时这里的形状错过两次,而**两次的错误信息都完全指不出这一点**:
41
+ *
42
+ * 1. `messages: [{ role: 'user', content: prompt }]`(content 是字符串)→ 平台在适配器里
43
+ * 对 content 调 `.some()`,整次调用以 `content.some is not a function` 失败。
44
+ * 那句话里既没有 `messages`,也没有 `content` 的类型要求。
45
+ * 2. **没有带 `sessionId`** → OpenCode 系的路由直接回
46
+ * `400 MissingSessionID: Request is missing x-opencode-session`。
47
+ * 这个字段在 `GenerateOptions` 里是可选的,所以类型不报错;而它在
48
+ * 「路由与缓存亲和」这件事上是必需的(`dsh-opencode-session` 插件只在
49
+ * `options.sessionId` 存在时才打那个头,没有就原样放过去)。
50
+ *
51
+ * 平台的 `RequestMessage.content` 是**内容块数组**(`[{ type: 'text', text }]`),
52
+ * 不是字符串。把这些形状固定到一个纯函数里,就有了唯一的去处,也能被测试钉住。
53
+ *
54
+ * ## `sessionId` 给什么值
55
+ *
56
+ * 知识编译不是一段对话,但它确实需要「同一批调用被路由到同一个后端」这个性质:
57
+ * 反复编译同一个知识库时,提示词里有大段的 wiki 类型定义与已有卡片清单,**亲和性直接
58
+ * 影响命中缓存的概率**。所以给一个**按知识库稳定**的值,而不是随机的 UUID。
59
+ *
60
+ * 用 `knowledge:<库 id>` 这个形状而不是裸库 id:它明确说出这不是一个会话,
61
+ * 排查的人一眼能看出这些调用是谁发的(同一个头的值还会出现在上游的日志里)。
62
+ */
63
+ export function buildModelRequest(input: {
64
+ provider: string
65
+ model: string
66
+ system?: string
67
+ prompt: string
68
+ reasoningEffort?: string
69
+ /** 路由与缓存亲和的会话身份。知识编译用 `knowledge:<库 id>`。 */
70
+ sessionId: string
71
+ }): {
72
+ provider: string
73
+ model: string
74
+ reasoningEffort?: string
75
+ system?: string
76
+ sessionId: string
77
+ messages: Array<{ role: 'user'; content: Array<{ type: 'text'; text: string }> }>
78
+ } {
79
+ const { provider, model, system, prompt, reasoningEffort, sessionId } = input
80
+ return {
81
+ provider,
82
+ model,
83
+ ...(reasoningEffort ? { reasoningEffort } : {}),
84
+ ...(system ? { system } : {}),
85
+ sessionId,
86
+ messages: [{ role: 'user', content: [{ type: 'text', text: prompt }] }],
87
+ }
88
+ }
89
+
90
+ /**
91
+ * 收完一条模型流的文本。
92
+ *
93
+ * @param stream 平台 `llm.stream()` 返回的 chunk 流
94
+ * @returns 模型输出的全文(不做 trim —— 调用方可能要看空白,也方便断言)
95
+ * @throws 流以 error / aborted 结束时,抛出带 code 与 message 的错误;
96
+ * 流正常结束但一个字符都没有时,也抛 —— 「模型没说话」不是一次成功的调用。
97
+ */
98
+ export async function collectModelText(stream: AsyncIterable<StreamChunkLike>): Promise<string> {
99
+ let text = ''
100
+ /** 流怎么结束的。用来区分「正常但空」「失败」「中止」三种情况。 */
101
+ let finish: string | undefined
102
+ let failure: { code?: string; message?: string } | undefined
103
+
104
+ for await (const chunk of stream) {
105
+ if (!chunk || typeof chunk !== 'object') continue
106
+ if (chunk.type === 'text-delta' && typeof chunk.text === 'string') {
107
+ text += chunk.text
108
+ continue
109
+ }
110
+ if (chunk.type === 'finish') {
111
+ finish = chunk.reason?.kind
112
+ if (finish === 'error' || finish === 'aborted') failure = chunk.reason?.failure
113
+ }
114
+ }
115
+
116
+ if (failure) {
117
+ const code = failure.code?.trim() || '未知错误码'
118
+ const message = failure.message?.trim() || '模型服务没有给出说明'
119
+ throw new Error(`模型调用失败(${code}):${message}`)
120
+ }
121
+ if (text.length === 0) {
122
+ // 走到这里说明流正常结束(或者压根没有 finish 块)却没有一个字符。
123
+ throw new Error(`模型没有返回任何文本(结束原因:${finish ?? '没有收到 finish 块'})`)
124
+ }
125
+ return text
126
+ }