dsh-data-domain-knowledge 0.1.1-alpha.2 → 0.1.1-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 (189) hide show
  1. package/README.md +23 -16
  2. package/lib/client.js +596 -841
  3. package/lib/contract-refs.d.ts +0 -1
  4. package/lib/doc-page.js +7 -7
  5. package/lib/pages.js +928 -1145
  6. package/lib/types/agent-behavior-rules.d.ts.map +1 -1
  7. package/lib/types/agent-behavior-rules.js +1 -2
  8. package/lib/types/agent-behavior-rules.js.map +1 -1
  9. package/lib/types/axis-contract.js +1 -1
  10. package/lib/types/axis-contract.js.map +1 -1
  11. package/lib/types/card-organize.d.ts +2 -1
  12. package/lib/types/card-organize.d.ts.map +1 -1
  13. package/lib/types/card-organize.js +5 -3
  14. package/lib/types/card-organize.js.map +1 -1
  15. package/lib/types/config-schema.d.ts +1 -1
  16. package/lib/types/config-schema.js +1 -1
  17. package/lib/types/convert/host.d.ts +2 -3
  18. package/lib/types/convert/host.d.ts.map +1 -1
  19. package/lib/types/convert/host.js +5 -2
  20. package/lib/types/convert/host.js.map +1 -1
  21. package/lib/types/convert/ingest.d.ts +2 -0
  22. package/lib/types/convert/ingest.d.ts.map +1 -1
  23. package/lib/types/convert/ingest.js +1 -0
  24. package/lib/types/convert/ingest.js.map +1 -1
  25. package/lib/types/diagnosis/types.d.ts +1 -1
  26. package/lib/types/diagnosis/types.js +1 -1
  27. package/lib/types/images.js +1 -1
  28. package/lib/types/index.d.ts +0 -4
  29. package/lib/types/index.d.ts.map +1 -1
  30. package/lib/types/index.js +30 -41
  31. package/lib/types/index.js.map +1 -1
  32. package/lib/types/knowledge-contract.d.ts +6 -0
  33. package/lib/types/knowledge-contract.d.ts.map +1 -1
  34. package/lib/types/knowledge-contract.js +6 -2
  35. package/lib/types/knowledge-contract.js.map +1 -1
  36. package/lib/types/layout.d.ts +9 -26
  37. package/lib/types/layout.d.ts.map +1 -1
  38. package/lib/types/layout.js +10 -28
  39. package/lib/types/layout.js.map +1 -1
  40. package/lib/types/materialize-snapshot.d.ts +5 -0
  41. package/lib/types/materialize-snapshot.d.ts.map +1 -1
  42. package/lib/types/migrate.d.ts +3 -1
  43. package/lib/types/migrate.d.ts.map +1 -1
  44. package/lib/types/migrate.js +3 -1
  45. package/lib/types/migrate.js.map +1 -1
  46. package/lib/types/organize-contract.d.ts.map +1 -1
  47. package/lib/types/organize-contract.js +2 -1
  48. package/lib/types/organize-contract.js.map +1 -1
  49. package/lib/types/pipeline/engine.d.ts +1 -0
  50. package/lib/types/pipeline/engine.d.ts.map +1 -1
  51. package/lib/types/pipeline/engine.js +9 -3
  52. package/lib/types/pipeline/engine.js.map +1 -1
  53. package/lib/types/pipeline/types.d.ts +6 -0
  54. package/lib/types/pipeline/types.d.ts.map +1 -1
  55. package/lib/types/pipeline/types.js.map +1 -1
  56. package/lib/types/present.d.ts +1 -1
  57. package/lib/types/present.js +1 -1
  58. package/lib/types/preset-ids.d.ts +0 -1
  59. package/lib/types/preset-ids.d.ts.map +1 -1
  60. package/lib/types/preset-ids.js +0 -3
  61. package/lib/types/preset-ids.js.map +1 -1
  62. package/lib/types/query-analysis.d.ts +0 -5
  63. package/lib/types/query-analysis.d.ts.map +1 -1
  64. package/lib/types/query-analysis.js +0 -16
  65. package/lib/types/query-analysis.js.map +1 -1
  66. package/lib/types/recall-delivery.d.ts +36 -0
  67. package/lib/types/recall-delivery.d.ts.map +1 -0
  68. package/lib/types/recall-delivery.js +16 -0
  69. package/lib/types/recall-delivery.js.map +1 -0
  70. package/lib/types/runtime.d.ts +26 -12
  71. package/lib/types/runtime.d.ts.map +1 -1
  72. package/lib/types/runtime.js +231 -106
  73. package/lib/types/runtime.js.map +1 -1
  74. package/lib/types/settings-volatile.d.ts +0 -7
  75. package/lib/types/settings-volatile.d.ts.map +1 -1
  76. package/lib/types/settings-volatile.js +0 -7
  77. package/lib/types/settings-volatile.js.map +1 -1
  78. package/lib/types/source-identity.d.ts +3 -0
  79. package/lib/types/source-identity.d.ts.map +1 -0
  80. package/lib/types/source-identity.js +5 -0
  81. package/lib/types/source-identity.js.map +1 -0
  82. package/lib/types/store/axis-references.d.ts.map +1 -1
  83. package/lib/types/store/axis-references.js +5 -3
  84. package/lib/types/store/axis-references.js.map +1 -1
  85. package/lib/types/store/knowledge-db.d.ts +1 -1
  86. package/lib/types/store/knowledge-db.d.ts.map +1 -1
  87. package/lib/types/store/knowledge-db.js +16 -2
  88. package/lib/types/store/knowledge-db.js.map +1 -1
  89. package/lib/types/types.d.ts +5 -4
  90. package/lib/types/types.d.ts.map +1 -1
  91. package/lib/types/types.js +6 -3
  92. package/lib/types/types.js.map +1 -1
  93. package/lib/types/wiki-availability.d.ts +11 -0
  94. package/lib/types/wiki-availability.d.ts.map +1 -0
  95. package/lib/types/wiki-availability.js +39 -0
  96. package/lib/types/wiki-availability.js.map +1 -0
  97. package/lib/types/wiki-finalize.d.ts +4 -2
  98. package/lib/types/wiki-finalize.d.ts.map +1 -1
  99. package/lib/types/wiki-finalize.js +9 -27
  100. package/lib/types/wiki-finalize.js.map +1 -1
  101. package/lib/types/wiki-links.d.ts +35 -0
  102. package/lib/types/wiki-links.d.ts.map +1 -0
  103. package/lib/types/wiki-links.js +129 -0
  104. package/lib/types/wiki-links.js.map +1 -0
  105. package/package.json +16 -16
  106. package/scripts/doctor.mjs +0 -7
  107. package/scripts/setup-embedding.sh +0 -0
  108. package/src/agent-behavior-rules.ts +1 -2
  109. package/src/axis-contract.ts +1 -1
  110. package/src/card-organize.ts +5 -3
  111. package/src/client/AttributionEventGuide.tsx +3 -3
  112. package/src/client/AxisMatrix.tsx +86 -19
  113. package/src/client/DirectoryPicker.tsx +0 -2
  114. package/src/client/GuideArchitectureCanvas.tsx +5 -12
  115. package/src/client/ImportFilesDialog.tsx +1 -0
  116. package/src/client/InfoHint.tsx +1 -1
  117. package/src/client/KnowledgeBaseTab.tsx +113 -39
  118. package/src/client/KnowledgePanel.tsx +16 -9
  119. package/src/client/OperationLogTab.tsx +1 -1
  120. package/src/client/RecallPanel.tsx +5 -16
  121. package/src/client/TabGuide.tsx +3 -7
  122. package/src/client/WikiFlowPanel.tsx +10 -11
  123. package/src/client/arch/NodeLogicPanel.tsx +3 -3
  124. package/src/client/arch/model.ts +5 -113
  125. package/src/client/architecture.css +1 -1
  126. package/src/client/architecture.tsx +13 -27
  127. package/src/client/attribution/AttributionTab.tsx +141 -10
  128. package/src/client/pages.css +2 -2
  129. package/src/client/pages.tsx +4 -11
  130. package/src/client/styles.css +15 -96
  131. package/src/config-schema.ts +1 -1
  132. package/src/convert/host.ts +7 -5
  133. package/src/convert/ingest.ts +3 -0
  134. package/src/diagnosis/types.ts +1 -1
  135. package/src/images.ts +1 -1
  136. package/src/index.ts +27 -49
  137. package/src/knowledge-contract.ts +7 -2
  138. package/src/layout.ts +10 -32
  139. package/src/materialize-snapshot.ts +1 -0
  140. package/src/migrate.ts +3 -1
  141. package/src/organize-contract.ts +2 -1
  142. package/src/pipeline/engine.ts +9 -3
  143. package/src/pipeline/types.ts +2 -0
  144. package/src/present.ts +1 -1
  145. package/src/preset-ids.ts +0 -4
  146. package/src/query-analysis.ts +0 -20
  147. package/src/recall-delivery.ts +19 -0
  148. package/src/runtime.ts +220 -102
  149. package/src/settings-volatile.ts +0 -7
  150. package/src/source-identity.ts +4 -0
  151. package/src/store/axis-references.ts +5 -3
  152. package/src/store/knowledge-db.ts +14 -2
  153. package/src/types.ts +6 -4
  154. package/src/wiki-availability.ts +37 -0
  155. package/src/wiki-finalize.ts +9 -25
  156. package/src/wiki-links.ts +120 -0
  157. package/lib/types/intent/engine.d.ts +0 -122
  158. package/lib/types/intent/engine.d.ts.map +0 -1
  159. package/lib/types/intent/engine.js +0 -488
  160. package/lib/types/intent/engine.js.map +0 -1
  161. package/lib/types/intent/host.d.ts +0 -68
  162. package/lib/types/intent/host.d.ts.map +0 -1
  163. package/lib/types/intent/host.js +0 -249
  164. package/lib/types/intent/host.js.map +0 -1
  165. package/lib/types/intent/scope.d.ts +0 -25
  166. package/lib/types/intent/scope.d.ts.map +0 -1
  167. package/lib/types/intent/scope.js +0 -116
  168. package/lib/types/intent/scope.js.map +0 -1
  169. package/lib/types/intent/store.d.ts +0 -33
  170. package/lib/types/intent/store.d.ts.map +0 -1
  171. package/lib/types/intent/store.js +0 -239
  172. package/lib/types/intent/store.js.map +0 -1
  173. package/lib/types/intent/tools.d.ts +0 -62
  174. package/lib/types/intent/tools.d.ts.map +0 -1
  175. package/lib/types/intent/tools.js +0 -130
  176. package/lib/types/intent/tools.js.map +0 -1
  177. package/lib/types/intent/types.d.ts +0 -170
  178. package/lib/types/intent/types.d.ts.map +0 -1
  179. package/lib/types/intent/types.js +0 -56
  180. package/lib/types/intent/types.js.map +0 -1
  181. package/src/client/IntentLogic.tsx +0 -192
  182. package/src/client/IntentTab.tsx +0 -343
  183. package/src/intent/README.md +0 -42
  184. package/src/intent/engine.ts +0 -515
  185. package/src/intent/host.ts +0 -315
  186. package/src/intent/scope.ts +0 -125
  187. package/src/intent/store.ts +0 -218
  188. package/src/intent/tools.ts +0 -154
  189. package/src/intent/types.ts +0 -229
@@ -1,9 +1,10 @@
1
1
  import {
2
- CircleDot, Database, GitBranch, History, Sparkles, Stethoscope, X,
2
+ CircleDot, Database, GitBranch, History, Library, Sparkles, Stethoscope, X,
3
3
  } from '@tita-agent/ui/icons'
4
4
  import { useCallback, useEffect, useMemo, useState, type ReactNode } from 'react'
5
- import { Button } from '@tita-agent/ui/components'
5
+ import { Badge, Button } from '@tita-agent/ui/components'
6
6
  import { Dialog, DialogContent, DialogTitle } from '@tita-agent/ui/overlays'
7
+ import { requestJson } from '../request.ts'
7
8
  import { AttributionGraph } from './AttributionGraph.tsx'
8
9
  import { CaseView } from './CaseView.tsx'
9
10
  import { EventView } from './EventView.tsx'
@@ -35,6 +36,16 @@ interface DiagnosisPayload {
35
36
  canLoadSample?: boolean
36
37
  }
37
38
 
39
+ /** 客户端只消费搜索返回的这几个字段,不直接依赖服务端 KnowledgeCard 模块。 */
40
+ interface EvidenceWikiCard {
41
+ entityId: string
42
+ entityType: string
43
+ title: string
44
+ summary?: string
45
+ content: string
46
+ metadata?: Record<string, unknown>
47
+ }
48
+
38
49
  /** 服务端的三种来源 → 徽标上那句人话。 */
39
50
  function sourceLabelOf(source: string | undefined): string {
40
51
  if (source === 'cards') return '可复用诊断卡片'
@@ -43,7 +54,7 @@ function sourceLabelOf(source: string | undefined): string {
43
54
  return '还没有排查知识'
44
55
  }
45
56
 
46
- type SubView = 'graph' | 'case' | 'event' | 'run'
57
+ type SubView = 'graph' | 'case' | 'event' | 'run' | 'knowledge'
47
58
 
48
59
  function normalizeQuestion(value: string): string {
49
60
  return value.toLocaleLowerCase().replace(/[^\p{L}\p{N}]+/gu, '')
@@ -80,15 +91,19 @@ function symptomMatchOf(question: string | undefined, symptoms: readonly Attribu
80
91
  * - 画布左上 = 操作(四组按动线顺序);右上 = 读法(四个开关同排);
81
92
  * 左下 = 读数(统计 + 来源);底中 = 搜索;右侧 = 图例正文 / 节点详情。
82
93
  */
83
- export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
94
+ export function AttributionTab({ visible, activeQuestion, kbId, onOpenKnowledge, onOpenWiki }: {
84
95
  visible: boolean
85
96
  /** 当前会话最近一条用户消息;仅精确匹配到唯一现象时自动高亮。 */
86
97
  activeQuestion?: string
98
+ /** 与知识库分栏当前选中的库保持一致。 */
99
+ kbId?: string
87
100
  /**
88
101
  * 切到「知识库」那一栏。空态的主操作要用它 —— 排查知识是从来源与卡片编译来的,
89
102
  * 空的时候该把人送去**录知识的那一栏**,而不是让人在这一栏里等。
90
103
  */
91
104
  onOpenKnowledge?: () => void
105
+ /** 打开召回卡片的原 Wiki。 */
106
+ onOpenWiki?: (card: Pick<EvidenceWikiCard, 'entityId' | 'entityType'>) => void
92
107
  }): ReactNode {
93
108
  const [subView, setSubView] = useState<SubView>('graph')
94
109
  /** 默认关系图:空问题态先看完整排查关系。 */
@@ -211,7 +226,8 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
211
226
  setPhase('loading')
212
227
  void (async () => {
213
228
  try {
214
- const response = await fetch('/diagnosis/graph')
229
+ const query = kbId ? `?kb=${encodeURIComponent(kbId)}` : ''
230
+ const response = await fetch(`/diagnosis/graph${query}`)
215
231
  if (!response.ok) throw new Error(`HTTP ${response.status}`)
216
232
  const body = await response.json() as { data?: DiagnosisPayload }
217
233
  if (cancelled) return
@@ -222,7 +238,7 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
222
238
  }
223
239
  })()
224
240
  return () => { cancelled = true }
225
- }, [visible, reloadToken, applyPayload])
241
+ }, [visible, reloadToken, applyPayload, kbId])
226
242
 
227
243
  /**
228
244
  * 载入内置示例:**显式的一次写**,将来源记录与可复用诊断卡写入当前知识库。
@@ -232,7 +248,8 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
232
248
  const loadSample = async (): Promise<void> => {
233
249
  setSampleBusy(true); setSampleError(null)
234
250
  try {
235
- const response = await fetch('/diagnosis/sample', { method: 'POST' })
251
+ const query = kbId ? `?kb=${encodeURIComponent(kbId)}` : ''
252
+ const response = await fetch(`/diagnosis/sample${query}`, { method: 'POST' })
236
253
  const body = await response.json() as { ok?: boolean; data?: DiagnosisPayload; error?: { message?: string } }
237
254
  if (!response.ok || body.ok === false) throw new Error(body.error?.message ?? `HTTP ${response.status}`)
238
255
  applyPayload(body.data ?? {})
@@ -275,6 +292,8 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
275
292
  const symptomCounts = useMemo(() => symptomCountByProblemClass(data.nodes), [data])
276
293
  const symptoms = useMemo(() => data.nodes.filter(node => node.kind === 'symptom'), [data])
277
294
  const matchedSymptom = useMemo(() => symptomMatchOf(activeQuestion, symptoms), [activeQuestion, symptoms])
295
+ const selectedSymptom = selectedId ? symptoms.find(node => node.id === selectedId) : undefined
296
+ const evidenceQuery = activeQuestion?.trim() || selectedSymptom?.title || matchedSymptom?.title || ''
278
297
  useEffect(() => {
279
298
  // 新会话问题或新载入的图谱:只在唯一、明确匹配时预选;其余情况保持全图。
280
299
  setSelectedId(matchedSymptom?.id)
@@ -434,6 +453,10 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
434
453
  hint: '剪枝:按优先分排出下一步该查什么,每个检查点都有主操作。',
435
454
  meta: String(candidateCount),
436
455
  },
456
+ {
457
+ id: 'knowledge', label: '知识', icon: Library,
458
+ hint: '依据:按当前问题召回 Wiki Fact / Wiki Way;可打开原 Wiki 核对。',
459
+ },
437
460
  ]
438
461
 
439
462
  const layoutHint = layout === 'line'
@@ -517,6 +540,7 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
517
540
  />}
518
541
  {...(subView === 'case' ? { layer: <CaseView cases={cases} activeId={activeCase} onActiveChange={setActiveCase} /> } : {})}
519
542
  {...(subView === 'event' ? { layer: <EventView events={events} /> } : {})}
543
+ {...(subView === 'knowledge' ? { layer: <KnowledgeEvidenceView query={evidenceQuery} kbId={kbId} onOpenWiki={onOpenWiki} /> } : {})}
520
544
  {...(subView === 'run' ? {
521
545
  layer: <RunView
522
546
  data={data}
@@ -621,8 +645,10 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
621
645
  if (phase !== 'ready' || (data.nodes.length === 0 && events.length === 0)) {
622
646
  return <div className="flex min-h-0 flex-1 flex-col gap-1.5">
623
647
  {helpError && <AttributionHelpNotice message={helpError} onDismiss={() => setHelpError(null)} />}
624
- <div className="flex min-w-0 items-center justify-end">{helpHint}</div>
625
- <AttributionEmpty
648
+ <div className="flex min-w-0 flex-wrap items-center justify-between gap-1.5">{switcher}{helpHint}</div>
649
+ {subView === 'knowledge'
650
+ ? <div className="min-h-0 flex-1 overflow-y-auto px-2"><KnowledgeEvidenceView query={evidenceQuery} kbId={kbId} onOpenWiki={onOpenWiki} /></div>
651
+ : <AttributionEmpty
626
652
  phase={phase}
627
653
  canLoadSample={canLoadSample}
628
654
  busy={sampleBusy}
@@ -630,7 +656,7 @@ export function AttributionTab({ visible, activeQuestion, onOpenKnowledge }: {
630
656
  onLoadSample={() => void loadSample()}
631
657
  onRetry={() => setReloadToken(token => token + 1)}
632
658
  {...(onOpenKnowledge ? { onOpenKnowledge } : {})}
633
- />
659
+ />}
634
660
  </div>
635
661
  }
636
662
 
@@ -731,6 +757,111 @@ function AttributionHelpNotice({ message, onDismiss }: { message: string; onDism
731
757
  </div>
732
758
  }
733
759
 
760
+ /**
761
+ * 只读查看当前问题从知识库召回的 Wiki Fact / Wiki Way。
762
+ *
763
+ * 这里消费正式的 `/knowledge/search` 结果,不再保存第二份问题定位知识。召回顺序保留,
764
+ * 每条结果都能跳回原 Wiki;正文默认收起,避免把完整 Wiki 挤进窄栏。
765
+ */
766
+ function KnowledgeEvidenceView({ query, kbId, onOpenWiki }: {
767
+ query: string
768
+ kbId?: string
769
+ onOpenWiki?: (card: Pick<EvidenceWikiCard, 'entityId' | 'entityType'>) => void
770
+ }): ReactNode {
771
+ const [items, setItems] = useState<EvidenceWikiCard[]>([])
772
+ const [phase, setPhase] = useState<'idle' | 'loading' | 'ready' | 'error'>('idle')
773
+ const [error, setError] = useState('')
774
+ const [retry, setRetry] = useState(0)
775
+ const [expanded, setExpanded] = useState<ReadonlySet<string>>(new Set())
776
+
777
+ useEffect(() => {
778
+ const trimmed = query.trim()
779
+ if (!trimmed) {
780
+ setItems([])
781
+ setPhase('idle')
782
+ setError('')
783
+ return undefined
784
+ }
785
+ let cancelled = false
786
+ setPhase('loading')
787
+ setItems([])
788
+ setExpanded(new Set())
789
+ setError('')
790
+ const params = new URLSearchParams({ query: trimmed, top_k: '30', top_n: '20' })
791
+ if (kbId) params.set('kb', kbId)
792
+ void requestJson<{ wiki?: EvidenceWikiCard[] }>(`/knowledge/search?${params.toString()}`)
793
+ .then(payload => {
794
+ if (cancelled) return
795
+ setItems((payload.data?.wiki ?? []).filter(card => card.metadata?.wiki_kind === 'fact' || card.metadata?.wiki_kind === 'way'))
796
+ setPhase('ready')
797
+ })
798
+ .catch(cause => {
799
+ if (cancelled) return
800
+ setItems([])
801
+ setError(cause instanceof Error ? cause.message : String(cause))
802
+ setPhase('error')
803
+ })
804
+ return () => { cancelled = true }
805
+ }, [query, kbId, retry])
806
+
807
+ const toggleExpanded = (id: string): void => setExpanded(current => {
808
+ const next = new Set(current)
809
+ if (next.has(id)) next.delete(id)
810
+ else next.add(id)
811
+ return next
812
+ })
813
+
814
+ return <section className="mx-auto flex w-full max-w-3xl flex-col gap-2 py-1" aria-label="知识库召回依据">
815
+ <header className="border-b border-tita-line pb-2">
816
+ <h3 className="text-[13px] font-semibold text-tita-ink">问题定位相关 Wiki</h3>
817
+ <p className="mt-1 text-[10.5px] leading-relaxed text-tita-ink-muted">
818
+ 按当前问题召回已有 Wiki Fact / Wiki Way。这里只读展示,不会创建或修改知识。
819
+ </p>
820
+ {query.trim() && <p className="mt-1 line-clamp-2 break-words text-[10.5px] leading-relaxed text-tita-ink" title={query}>{query}</p>}
821
+ </header>
822
+
823
+ {phase === 'idle' && <p className="py-5 text-center text-[11px] text-tita-ink-muted">
824
+ 当前没有可用于召回的问题。请在会话中提出问题,或先在图谱里选择一个问题现象。
825
+ </p>}
826
+ {phase === 'loading' && <p role="status" className="py-5 text-center text-[11px] text-tita-ink-muted">
827
+ 正在召回 Wiki Fact / Wiki Way…
828
+ </p>}
829
+ {phase === 'error' && <div className="flex flex-col items-start gap-2 py-3">
830
+ <p role="alert" className="text-[10.5px] leading-relaxed text-tita-danger">知识召回失败:{error}</p>
831
+ <Button size="sm" variant="outline" className="h-[26px] min-h-[26px] px-2 text-[10.5px]" onClick={() => setRetry(value => value + 1)}>重试召回</Button>
832
+ </div>}
833
+ {phase === 'ready' && items.length === 0 && <p className="py-5 text-center text-[11px] leading-relaxed text-tita-ink-muted">
834
+ 当前问题没有召回到 Wiki Fact 或 Wiki Way。可以补充问题现象,或到知识库检查相关 Wiki 是否已编译。
835
+ </p>}
836
+ {items.length > 0 && <ol className="divide-y divide-tita-line">
837
+ {items.map(card => {
838
+ const wikiKind = card.metadata?.wiki_kind === 'way' ? 'way' : 'fact'
839
+ const open = expanded.has(card.entityId)
840
+ return <li key={`${card.entityType}:${card.entityId}`} className="py-2.5 first:pt-1">
841
+ <article className="flex min-w-0 flex-col gap-1.5">
842
+ <header className="flex min-w-0 items-start gap-1.5">
843
+ <Badge tone={wikiKind === 'way' ? 'success' : 'neutral'} className="mt-0.5 shrink-0">
844
+ {wikiKind === 'way' ? 'Wiki Way' : 'Wiki Fact'}
845
+ </Badge>
846
+ <h4 className="min-w-0 break-words text-[11.5px] font-semibold leading-snug text-tita-ink">{card.title}</h4>
847
+ </header>
848
+ {card.summary && <p className="break-words text-[10.5px] leading-relaxed text-tita-ink-muted">{card.summary}</p>}
849
+ <div className="flex flex-wrap items-center gap-1">
850
+ <Button size="sm" variant="ghost" className="h-[25px] min-h-[25px] px-1.5 text-[10px]" onClick={() => toggleExpanded(card.entityId)}>
851
+ {open ? '收起 Wiki 正文' : '展开 Wiki 正文'}
852
+ </Button>
853
+ {onOpenWiki && <Button size="sm" variant="outline" className="h-[25px] min-h-[25px] px-1.5 text-[10px]" onClick={() => onOpenWiki(card)}>
854
+ 打开原 Wiki
855
+ </Button>}
856
+ </div>
857
+ {open && <pre className="max-h-[36vh] overflow-auto overscroll-contain whitespace-pre-wrap break-words rounded-md bg-tita-surface-muted p-2 text-[10px] leading-relaxed text-tita-ink">{card.content}</pre>}
858
+ </article>
859
+ </li>
860
+ })}
861
+ </ol>}
862
+ </section>
863
+ }
864
+
734
865
  /** 大类 id → 它下面的子类 id。取消大类时要把这些子类一起撤掉。 */
735
866
  const PROBLEM_CLASS_IDS_TO_SUBCLASSES: Record<string, string[]> = Object.fromEntries(
736
867
  PROBLEM_CLASSES.map(item => [item.id, item.subclasses.map(sub => sub.id)]),
@@ -1,12 +1,12 @@
1
1
  /*
2
- * 通用整页(抽取试跑 / 编译链路 / 意图的逻辑)的页面样式。
2
+ * 通用整页(抽取试跑 / 编译链路)的页面样式。
3
3
  *
4
4
  * 它和 `doc-page.css` 是同一种东西:一份**单独的浏览器文档**,没有 DSH 外壳、没有侧栏
5
5
  * 那 380px 的宽度限制。所以这里的决定围绕两件事:
6
6
  *
7
7
  * 1. **一条常驻的窄条**回答「我在哪一页、看的是哪个库」—— 整页可以滚很久,标题滚走了
8
8
  * 就只剩内容,而这三页都需要知道自己在哪。
9
- * 2. **两条内容宽度**:说明类(编译链路、意图的逻辑)是读排版,约 860px 就够;试跑报告
9
+ * 2. **两条内容宽度**:说明类(编译链路)是读排版,约 860px 就够;试跑报告
10
10
  * 里有一张矩阵,给它 1180px。宽度是**内容**的属性,所以按页给,不按壳给。
11
11
  *
12
12
  * 颜色全部走共享令牌(`@tita-agent/ui/styles.css` 带进来的那一套)。插件自己的
@@ -1,5 +1,5 @@
1
1
  /**
2
- * 通用整页:**抽取试跑 / 抽取轨迹 / 编译和召回链路 / 意图的逻辑 / Agent 方案说明**。
2
+ * 通用整页:**抽取试跑 / 抽取轨迹 / 编译和召回链路 / Agent 方案说明**。
3
3
  *
4
4
  * 这些页面共用一个 bundle、一条宿主路由(`/knowledge/pages/<...>`),因为它们是同一种东西:
5
5
  * 「在面板里放不下、也不该挤进 380px 侧栏」的内容。**它们开在真的浏览器窗口里** ——
@@ -15,9 +15,8 @@
15
15
  * - **抽取轨迹**:同一份理由的另一面 —— 报告几十 KB,2026-10-06 从抽屉里就地展开
16
16
  * 改成开整页(用户点名「点击要在浏览器打开」)。
17
17
  * - **编译和召回链路**:按架构说明 SourceRaw 如何变成 Wiki,以及一次 Query 如何变成 Agent Context。
18
- * - **意图的逻辑**:同上,讲的是词表为什么这么设计。
19
18
  *
20
- * 说明类内容原来住在表单里(编译流程对话框的第二个页签、意图页的第二个页签)。抽成
19
+ * 说明类内容原来住在表单里(编译流程对话框的说明页)。抽成
21
20
  * 整页之后,**每个容器只装一种东西**:浮层装要动手的,整页装要读的。
22
21
  *
23
22
  * ## 地址形状
@@ -26,7 +25,6 @@
26
25
  * /knowledge/pages/trial?kb=<知识库 id> 抽取试跑
27
26
  * /knowledge/pages/run?kb=<库 id>&run=<轨迹 id> 一次抽取的完整报告
28
27
  * /knowledge/pages/help/compile 编译和召回链路
29
- * /knowledge/pages/help/intent 意图的逻辑
30
28
  * /knowledge/pages/help/attribution 智能归因事件
31
29
  * /knowledge/pages/help/overview?node=... TitaAgent 完整架构和逻辑说明
32
30
  * ```
@@ -40,7 +38,6 @@ import '@tita-agent/ui/styles.css'
40
38
  import './styles.css'
41
39
  import './pages.css'
42
40
  import { AttributionEventGuide } from './AttributionEventGuide.tsx'
43
- import { IntentLogic } from './IntentLogic.tsx'
44
41
  import { TabGuidePage } from './TabGuide.tsx'
45
42
  import { WikiFlowPanel } from './WikiFlowPanel.tsx'
46
43
  import { WikiTrialRun, type TrialDocumentOption } from './WikiTrialRun.tsx'
@@ -59,7 +56,7 @@ type PageRoute =
59
56
  | { kind: 'trial'; kb: string }
60
57
  | { kind: 'run'; kb: string; run: string }
61
58
  | { kind: 'tasks'; kb: string }
62
- | { kind: 'help'; topic: 'intent' | 'compile' | 'attribution' | 'overview'; node?: string }
59
+ | { kind: 'help'; topic: 'compile' | 'attribution' | 'overview'; node?: string }
63
60
  | { kind: 'unknown' }
64
61
 
65
62
  /**
@@ -78,7 +75,7 @@ function routeOf(location: Location): PageRoute {
78
75
  if (parts[0] === 'help' && parts[1] === 'overview') {
79
76
  return { kind: 'help', topic: 'overview', node: params.get('node') ?? undefined }
80
77
  }
81
- if (parts[0] === 'help' && (parts[1] === 'intent' || parts[1] === 'compile' || parts[1] === 'attribution')) {
78
+ if (parts[0] === 'help' && (parts[1] === 'compile' || parts[1] === 'attribution')) {
82
79
  return { kind: 'help', topic: parts[1] }
83
80
  }
84
81
  return { kind: 'unknown' }
@@ -256,9 +253,6 @@ function App(): ReactNode {
256
253
  if (route.topic === 'overview') {
257
254
  return <PageShell title="TitaAgent 完整架构" meta="从理解请求到持续改进的三层闭环" column="guide"><TabGuidePage initialNode={route.node} /></PageShell>
258
255
  }
259
- if (route.topic === 'intent') {
260
- return <PageShell title="意图的逻辑" meta="这套词表为什么这样设计" column="help"><IntentLogic /></PageShell>
261
- }
262
256
  if (route.topic === 'compile') {
263
257
  return <PageShell title="编译和召回链路" meta="信息编译为知识,然后才值得被召回使用" column="help"><WikiFlowPanel /></PageShell>
264
258
  }
@@ -272,7 +266,6 @@ function App(): ReactNode {
272
266
  <code>/knowledge/pages/run?kb=&lt;知识库 id&gt;&amp;run=&lt;轨迹 id&gt;</code> · 一次抽取的报告<br />
273
267
  <code>/knowledge/pages/tasks?kb=&lt;知识库 id&gt;</code> · 知识处理与运行记录<br />
274
268
  <code>/knowledge/pages/help/compile</code> · 编译和召回链路<br />
275
- <code>/knowledge/pages/help/intent</code> · 意图的逻辑<br />
276
269
  <code>/knowledge/pages/help/attribution</code> · 智能归因事件<br />
277
270
  <code>/knowledge/pages/help/overview?node=knowledge</code> · 五个 Tab 的方案说明</p>
278
271
  <p>它们都由面板上的入口打开,不需要手写。</p>
@@ -672,7 +672,7 @@ html[data-platform='darwin'] [data-window-drag] [data-conversation-tabs] > [role
672
672
  同一个图标有两种含义时,光标是零成本的那条线索。 */
673
673
  .tita-infoHintAction { cursor: pointer; }
674
674
  .tita-infoHintAction:hover { color: var(--tita-ink); }
675
- /* 说明靠右(2026-10-05):知识库与意图两条工具栏同一条规矩 —— 动作一组靠左,
675
+ /* 说明靠右(2026-10-05):知识库工具栏的排版规矩 —— 动作一组靠左,
676
676
  `?` 自己站在最右端。`margin-left: auto` 在换行时也成立:它落到第二行,仍然右对齐。 */
677
677
  .tita-kbToolbarHint { flex: 0 0 auto; margin-left: auto; }
678
678
  /* 标题里的说明入口(设置小节同理):与标题文字留一格气,
@@ -852,7 +852,7 @@ html[data-platform='darwin'] [data-window-drag] [data-conversation-tabs] > [role
852
852
  /*
853
853
  * 行右侧的次要事实(类型 · 时间 / N 个来源 / N 张)。
854
854
  *
855
- * 0042 补的定义:这个类名被五处用着(卡片行、原文抽屉的编译产物、意图任务、
855
+ * 0042 补的定义:这个类名被五处用着(卡片行、原文抽屉的编译产物、
856
856
  * 类型定义、卡片类型编辑器)却**一直没有样式** —— 于是它只传递了一个名字,
857
857
  * 右对齐与「比主文字轻」全靠父级的 flex 碰运气。一次定义到位。
858
858
  */
@@ -1014,91 +1014,11 @@ html[data-platform='darwin'] [data-window-drag] [data-conversation-tabs] > [role
1014
1014
  .tita-kbInlineImage { margin: 10px 0; text-align: center; }
1015
1015
  .tita-kbInlineImage img { display: block; width: auto; max-width: 100%; max-height: 360px; margin: 0 auto; object-fit: contain; }
1016
1016
  .tita-kbPre { max-height: 320px; margin: 0; border: 1px solid var(--tita-line); border-radius: 9px; background: var(--tita-surface); color: var(--tita-ink); padding: 10px; overflow: auto; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px; line-height: 1.6; white-space: pre-wrap; overflow-wrap: anywhere; }
1017
- /* 文本视图的 `<pre>` 自带 max-height(它同时被意图 / 类型定义两处用),进了正文块之后
1017
+ /* 文本视图的 `<pre>` 自带 max-height(类型定义视图也会用),进了正文块之后
1018
1018
  由外层那一层负责滚 —— 同一件事不画两条滚动条。 */
1019
1019
  .tita-drawerBody .tita-kbPre { max-height: none; border: 0; border-radius: 0; background: transparent; padding: 0; }
1020
1020
 
1021
- /* ------------------------------------------------------ intent (session audit) */
1022
- .tita-intentTop { display: flex; min-width: 0; align-items: flex-start; justify-content: space-between; gap: 12px; border-bottom: 1px solid var(--tita-line); padding-bottom: 12px; }
1023
- .tita-intentTop h2 { margin: 0; color: var(--tita-ink); font-size: 17px; font-weight: 780; letter-spacing: -.025em; line-height: 1.3; }
1024
- .tita-intentTop p { max-width: 320px; margin: 5px 0 0; color: var(--tita-ink-muted); font-size: 11px; line-height: 1.6; }
1025
- .tita-intentControls { display: grid; gap: 8px; border-bottom: 1px solid var(--tita-line); padding-bottom: 12px; }
1026
- .tita-intentPrimaryFilter { display: grid; grid-template-columns: auto minmax(0, 1fr) auto; min-width: 0; align-items: center; gap: 7px; }
1027
- .tita-intentFilterHeading { display: grid; min-width: 42px; gap: 1px; }
1028
- .tita-intentFilterHeading small { color: var(--tita-primary); font-size: 9px; font-weight: 700; white-space: nowrap; }
1029
- .tita-intentFilterCount { color: var(--tita-ink-faint); font-size: 10px; font-variant-numeric: tabular-nums; white-space: nowrap; }
1030
- .tita-intentSecondaryFilters { display: grid; grid-template-columns: auto minmax(0, .7fr) auto minmax(0, 1.3fr) auto; min-width: 0; align-items: center; gap: 7px; }
1031
- .tita-intentControlLabel { color: var(--tita-ink-muted); font-size: 11px; white-space: nowrap; }
1032
- .tita-intentFilterSummary { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; gap: 5px 7px; border-radius: 8px; background: var(--tita-surface-muted); padding: 6px 8px; color: var(--tita-ink-faint); font-size: 10px; line-height: 1.4; }
1033
- .tita-intentFilterSummary > span { color: var(--tita-ink-muted); font-weight: 650; }
1034
- .tita-intentSectionHead { display: flex; min-width: 0; align-items: flex-start; justify-content: space-between; gap: 12px; }
1035
- .tita-intentSectionHead h3 { margin: 0; color: var(--tita-ink); font-size: 13.5px; font-weight: 760; }
1036
- .tita-intentSectionSubhead { margin: 3px 0 0; color: var(--tita-ink-faint); font-size: 10px; line-height: 1.45; }
1037
- .tita-intentSectionHead > time, .tita-intentSectionCount { flex: 0 0 auto; color: var(--tita-ink-faint); font-size: 10px; font-variant-numeric: tabular-nums; }
1038
- .tita-intentEyebrow { display: inline-flex; align-items: center; gap: 5px; color: var(--tita-ink-faint); font-size: 9.5px; font-weight: 750; letter-spacing: .08em; text-transform: uppercase; }
1039
- .tita-intentInterpretation { display: grid; gap: 11px; border-color: var(--tita-line-strong); padding: 14px 15px; }
1040
- .tita-intentUserBlock { min-width: 0; border: 1px solid var(--tita-line); border-radius: 9px; background: var(--tita-surface-muted); padding: 10px 11px; }
1041
- .tita-intentStepLabel { display: block; color: var(--tita-ink-faint); font-size: 10px; font-weight: 700; }
1042
- .tita-intentUserInput { margin: 5px 0 0; color: var(--tita-ink); font-size: 12px; line-height: 1.65; overflow-wrap: anywhere; }
1043
- .tita-intentVerdict { min-width: 0; border: 1px solid var(--tita-primary); border-radius: 9px; background: var(--tita-primary-soft); padding: 11px; }
1044
- .tita-intentVerdictHead { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
1045
- .tita-intentResultTitle { margin: 5px 0 0; color: var(--tita-ink); font-size: 15px; font-weight: 780; letter-spacing: -.02em; line-height: 1.35; overflow-wrap: anywhere; }
1046
- .tita-intentResultText { margin: 4px 0 0; color: var(--tita-ink-muted); font-size: 11px; line-height: 1.6; }
1047
- .tita-intentResultTags { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; gap: 5px; margin-top: 8px; }
1048
- .tita-intentLowConfidence { color: var(--tita-tone-amber); font-size: 10px; font-weight: 650; }
1049
- .tita-intentMetaGrid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
1050
- .tita-intentMetaCell { display: grid; min-width: 0; gap: 3px; border-top: 1px solid var(--tita-line); padding-top: 8px; }
1051
- .tita-intentMetaCell span { color: var(--tita-ink-faint); font-size: 9.5px; }
1052
- .tita-intentMetaCell strong { color: var(--tita-ink-muted); font-size: 10.5px; font-weight: 650; overflow-wrap: anywhere; }
1053
- .tita-intentDiagnostics { border-top: 1px solid var(--tita-line); padding-top: 9px; }
1054
- .tita-intentDiagnostics summary { color: var(--tita-ink-faint); font-size: 10px; cursor: pointer; list-style-position: inside; }
1055
- .tita-intentDiagnostics summary:hover { color: var(--tita-ink-muted); }
1056
- .tita-intentDiagnosticsBody { display: grid; gap: 5px; margin: 8px 0 0 15px; color: var(--tita-ink-faint); font-size: 10px; line-height: 1.5; }
1057
- .tita-intentDiagnosticsBody code { border-radius: 4px; background: var(--tita-surface-muted); padding: 1px 4px; color: var(--tita-tone-violet); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9.5px; }
1058
- .tita-intentEmptyCard { display: grid; justify-items: center; gap: 6px; padding: 25px 15px; text-align: center; }
1059
- .tita-intentEmptyCard > svg { color: var(--tita-ink-faint); }
1060
- .tita-intentEmptyCard strong { color: var(--tita-ink); font-size: 12px; }
1061
- .tita-intentEmptyCard p, .tita-intentEmpty { margin: 0; color: var(--tita-ink-faint); font-size: 11px; line-height: 1.6; }
1062
- .tita-intentHistory { border-top: 1px solid var(--tita-line); padding-top: 13px; }
1063
- .tita-intentHistoryList { display: grid; margin: 10px 0 0; padding: 0; list-style: none; }
1064
- .tita-intentHistoryItem { display: grid; min-width: 0; gap: 4px; border-bottom: 1px solid var(--tita-line); padding: 10px 0 11px; }
1065
- .tita-intentHistoryItem:first-child { padding-top: 0; }
1066
- .tita-intentHistoryItem:last-child { border-bottom: 0; }
1067
- .tita-intentHistoryPrimary { display: flex; min-width: 0; align-items: baseline; justify-content: space-between; gap: 9px; }
1068
- .tita-intentHistoryPrimary strong { min-width: 0; color: var(--tita-ink); font-size: 12px; font-weight: 720; overflow-wrap: anywhere; }
1069
- .tita-intentHistoryPrimary time { display: inline-flex; flex: 0 0 auto; align-items: center; gap: 4px; color: var(--tita-ink-muted); font-size: 10px; font-variant-numeric: tabular-nums; }
1070
- .tita-intentHistoryMeta { display: flex; min-width: 0; flex-wrap: wrap; align-items: center; gap: 4px 9px; color: var(--tita-ink-faint); font-size: 10px; }
1071
- .tita-intentHistoryMeta span + span::before { content: '·'; margin-right: 9px; color: var(--tita-line-strong); }
1072
- .tita-intentHistoryWarning { color: var(--tita-tone-amber); }
1073
- .tita-intentHistoryItem p { margin: 0; color: var(--tita-ink-muted); font-size: 11px; line-height: 1.55; overflow-wrap: anywhere; }
1074
- .tita-intentFooter { display: flex; flex-wrap: wrap; gap: 5px 12px; border-top: 1px solid var(--tita-line); padding-top: 10px; color: var(--tita-ink-faint); font-size: 10px; }
1075
- .tita-intentFooter span + span::before { content: '·'; margin-right: 12px; color: var(--tita-line-strong); }
1076
-
1077
- /* 「意图的逻辑」:说明性内容,读的是排版而不是控件。它现在是一份整页
1078
- (`/knowledge/pages/help/intent`),所以这几条规则同时服务面板与那一页。 */
1079
- .tita-intentDocList { display: grid; margin: 3px 0 0; padding-left: 18px; gap: 5px; color: var(--tita-ink); font-size: 11.5px; line-height: 1.75; }
1080
- .tita-intentDocList b { color: var(--tita-ink); }
1081
- .tita-intentDocList code, .tita-kbHint code { border-radius: 4px; background: var(--tita-surface-muted); padding: 0 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px; }
1082
- .tita-intentDocGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 8px; margin-top: 10px; }
1083
- .tita-intentDocGrid > div { display: grid; gap: 3px; border: 1px solid var(--tita-line); border-radius: 9px; background: var(--tita-surface); padding: 9px 11px; }
1084
- .tita-intentDocGrid strong { color: var(--tita-ink); font-size: 11.5px; }
1085
- .tita-intentDocGrid span { color: var(--tita-ink-faint); font-size: 10.5px; line-height: 1.6; }
1086
- .tita-intentDocTableWrap { overflow-x: auto; margin-top: 8px; }
1087
- .tita-intentDocTable { width: 100%; border-collapse: collapse; font-size: 11px; }
1088
- .tita-intentDocTable th { border-bottom: 1px solid var(--tita-line); color: var(--tita-ink-muted); padding: 6px 8px; font-weight: 700; text-align: left; white-space: nowrap; }
1089
- .tita-intentDocTable td { border-bottom: 1px solid var(--tita-surface-muted); color: var(--tita-ink); padding: 6px 8px; line-height: 1.65; vertical-align: top; }
1090
- .tita-intentDocTable td:first-child code { border-radius: 4px; background: var(--tita-surface-muted); padding: 1px 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px; }
1091
- .tita-intentDocTable td:nth-child(2) { color: var(--tita-tone-green); text-align: center; }
1092
- .tita-intentPaperList { display: grid; margin: 8px 0 0; padding: 0; list-style: none; gap: 10px; }
1093
- .tita-intentPaperList > li { border: 1px solid var(--tita-line); border-radius: 8px; background: var(--tita-surface); padding: 9px 11px; }
1094
- .tita-intentPaperList a { color: var(--tita-primary); font-size: 12px; font-weight: 640; text-decoration: none; }
1095
- .tita-intentPaperList a:hover { text-decoration: underline; }
1096
- .tita-intentPaperMeta { display: block; margin-top: 2px; color: var(--tita-ink-faint); font-size: 10px; }
1097
- .tita-intentPaperList p { margin: 6px 0 0; color: var(--tita-ink-muted); font-size: 11px; line-height: 1.75; }
1098
- .tita-intentPaperNote { color: var(--tita-ink-faint) !important; font-size: 10.5px !important; }
1099
- .tita-intentOasLegend { display: flex; flex-wrap: wrap; align-items: center; gap: 5px 12px; margin-top: 9px; color: var(--tita-ink-faint); font-size: 10px; }
1100
- .tita-intentOasLegend span { display: inline-flex; align-items: center; gap: 4px; }
1101
- .tita-intentOasLegend svg { color: var(--tita-primary); }
1021
+ .tita-kbHint code { border-radius: 4px; background: var(--tita-surface-muted); padding: 0 4px; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10.5px; }
1102
1022
 
1103
1023
  /* Wiki 编译与召回流程:总览图 + 当前选中节点说明。 */
1104
1024
  .tita-wikiFlowSection + .tita-wikiFlowSection { margin-top: 30px; border-top: 1px solid var(--tita-line); padding-top: 24px; }
@@ -1414,6 +1334,7 @@ html[data-platform='darwin'] [data-window-drag] [data-conversation-tabs] > [role
1414
1334
  .tita-trialMatrixGrid tbody th > .tita-trialMatrixAxisPick { margin-bottom: 3px; }
1415
1335
  .tita-trialMatrixObjectHead { text-align: center; }
1416
1336
  .tita-trialMatrixLoose { display: block; width: 100%; max-width: 100%; margin: 0; border: 1px solid transparent; border-radius: 5px; color: var(--tita-ink-faint); padding: 0 3px; font: inherit; font-size: 9px; font-weight: 500; line-height: 1.5; text-align: center; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1337
+ .tita-trialMatrixLooseStack { display: grid; min-width: 0; justify-items: center; gap: 1px; }
1417
1338
  .tita-trialMatrixLooseEmpty { display: block; height: 14px; }
1418
1339
  .tita-trialMatrixLoosePick { border-color: var(--tita-line); background: none; cursor: pointer; }
1419
1340
  .tita-trialMatrixLoosePick:hover { border-color: var(--tita-line-strong); color: var(--tita-ink-muted); }
@@ -1450,6 +1371,16 @@ html[data-platform='darwin'] [data-window-drag] [data-conversation-tabs] > [role
1450
1371
  .tita-trialMatrixCellPick:focus-visible { outline: 2px solid var(--tita-focus-ring); outline-offset: 0; }
1451
1372
  .tita-trialMatrixCellPickOn { border-color: var(--tita-primary); background: var(--tita-primary-soft); color: var(--tita-primary); }
1452
1373
  .tita-trialMatrixCellValue { font-size: 11px; font-weight: 700; font-variant-numeric: tabular-nums; }
1374
+ .tita-trialMatrixCellStack { display: grid; min-width: 0; justify-items: center; gap: 1px; }
1375
+ .tita-trialMatrixCellStack .tita-trialMatrixCellPick,
1376
+ .tita-trialMatrixCellStack .tita-trialMatrixCellStatic { min-height: 20px; }
1377
+ .tita-trialMatrixBreakdown { display: flex; min-width: 0; flex-wrap: wrap; justify-content: center; gap: 2px; }
1378
+ .tita-trialMatrixBreakdownItem { display: inline-flex; align-items: center; justify-content: center; border: 1px solid transparent; border-radius: 4px; color: var(--tita-ink-muted); padding: 0 2px; font-size: 8px; font-weight: 650; line-height: 1.35; font-variant-numeric: tabular-nums; white-space: nowrap; }
1379
+ .tita-trialMatrixBreakdownPick { background: transparent; cursor: pointer; }
1380
+ .tita-trialMatrixBreakdownPick:hover { border-color: var(--tita-line-strong); background: var(--tita-surface-muted); color: var(--tita-ink); }
1381
+ .tita-trialMatrixBreakdownPick:focus-visible { outline: 2px solid var(--tita-focus-ring); outline-offset: 1px; }
1382
+ .tita-trialMatrixBreakdownOn { border-color: var(--tita-primary); background: var(--tita-primary-soft); color: var(--tita-primary); }
1383
+ .tita-trialMatrixBreakdownOn:hover { background: var(--tita-primary-soft); color: var(--tita-primary); }
1453
1384
  .tita-trialMatrixGroups { display: grid; gap: 0; }
1454
1385
  .tita-trialMatrixGroupLine { display: block; min-width: 0; }
1455
1386
  .tita-trialMatrixGroupLine + .tita-trialMatrixGroupLine { border-top: 1px dashed var(--tita-line); }
@@ -2049,8 +1980,6 @@ button.tita-trialMatrixGroup:focus-visible { outline: 2px solid var(--tita-focus
2049
1980
  .tita-recallAnalysisTitle > svg { flex: 0 0 auto; color: var(--tita-tone-violet); }
2050
1981
  .tita-recallAnalysisTitle h4 { margin: 0; color: var(--tita-ink); font-size: 11.5px; font-weight: 720; }
2051
1982
  .tita-recallAnalysisTitle span { display: block; margin-top: 1px; color: var(--tita-ink-faint); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9.5px; }
2052
- .tita-recallAnalysisIntent { display: inline-flex; flex: 0 0 auto; align-items: baseline; gap: 5px; border-radius: 999px; background: var(--tita-tone-violet-tint); color: var(--tita-tone-violet); padding: 3px 8px; font-size: 10.5px; font-weight: 700; }
2053
- .tita-recallAnalysisIntent b { color: var(--tita-ink-faint); font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px; font-weight: 600; }
2054
1983
  .tita-recallAnalysisAxes { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 7px; }
2055
1984
  .tita-recallAnalysisAxis { display: grid; min-width: 0; gap: 5px; border: 1px solid var(--tita-line); border-radius: 8px; background: var(--tita-surface); padding: 7px 8px; }
2056
1985
  .tita-recallAnalysisAxisHead { display: flex; align-items: center; gap: 5px; color: var(--tita-ink-muted); font-size: 10px; }
@@ -2161,16 +2090,6 @@ button.tita-trialMatrixGroup:focus-visible { outline: 2px solid var(--tita-focus
2161
2090
  .tita-hitSnippet { margin: 0; color: var(--tita-ink-muted); font-size: 11px; line-height: 1.6; overflow-wrap: anywhere; }
2162
2091
  .tita-hitFoot { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; color: var(--tita-ink-faint); font-size: 10px; }
2163
2092
 
2164
- /* 意图:从最近记录里归纳出的分布(act / 分类 / 低置信度)。 */
2165
- .tita-distList { display: grid; gap: 7px; margin: 10px 0 0; }
2166
- .tita-distRow { display: grid; grid-template-columns: minmax(56px, 96px) minmax(0, 1fr) 34px; align-items: center; gap: 8px; }
2167
- .tita-distRow > span { overflow: hidden; color: var(--tita-ink-muted); font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
2168
- .tita-distBar { height: 7px; overflow: hidden; border-radius: 999px; background: var(--tita-surface-muted); }
2169
- .tita-distBar > i { display: block; height: 100%; border-radius: 999px; background: var(--tita-tone-violet); }
2170
- .tita-distBarFts > i { background: var(--tita-primary); }
2171
- .tita-distBarGraph > i { background: var(--tita-tone-green); }
2172
- .tita-distCount { color: var(--tita-ink-faint); font-size: 10.5px; text-align: right; }
2173
-
2174
2093
  /* 记忆:真实落盘计数(04_memory)与「示意」卡片分开摆,避免把占位数字读成真实数据。 */
2175
2094
  .tita-memoryReal { display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px; margin-bottom: 14px; }
2176
2095
 
@@ -9,7 +9,7 @@
9
9
  * (`{op:'set', path:['knowledge','recallTopK']}`)。
10
10
  *
11
11
  * `knowledge` / `convert` 沿用原来的配置边界;`behaviorRules` 保存可关闭的系统提示词
12
- * 扩展开关。会话意图审计仍使用固定内置契约,不属于 entry config。
12
+ * 扩展开关。
13
13
  *
14
14
  * 「单独一个模块」的理由和 `settings-schema.ts` 一样,
15
15
  * 是**可测**:`tests/settings-schema.test.mjs` 要能 import 这个 schema 并把它解析
@@ -91,14 +91,13 @@ export interface ConvertHostDeps {
91
91
  * 0.1.7 起设置不再是命名空间,而是随 `apply(ctx, config)` 进来的 entry config,
92
92
  * 所以这里由调用方把那一组传下来。省略时用 `DEFAULT_CONVERT_CONFIG`。
93
93
  *
94
- * 允许给**函数**的理由与意图那组相同(见 `intent/host.ts` 的 `IntentHostDeps.config`):
95
- * 字段是 volatile,解包是一次快照,而 volatile 改动不会重载插件。
94
+ * 给函数可响应设置热更新:字段是 volatile,解包是一次快照,而 volatile 改动不会重载插件。
96
95
  */
97
96
  config?: Partial<ConvertPluginConfig> | (() => Partial<ConvertPluginConfig>)
98
97
  }
99
98
 
100
99
  /**
101
- * `applyConvert` 交给组合根的那一个把手,与 `intent/host.ts` 的 `IntentHost` 同形。
100
+ * `applyConvert` 交给组合根的那一个把手。
102
101
  *
103
102
  * 转换这一组也有 volatile 字段(`maxConcurrent` / `maxInputBytes` / `retentionDays` /
104
103
  * `toolWaitMs` / `ingestEnabled`),其中一个解包漏了就是沉默的错误:`maxInputBytes`
@@ -131,7 +130,7 @@ function lineageOf(args: Args, exec?: ToolRunContext): Record<string, string> |
131
130
  const sessionId = typeof args.session_key === 'string' && args.session_key.trim() ? args.session_key.trim() : exec?.agent?.session.header.id
132
131
  if (sessionId) lineage.sessionId = sessionId
133
132
  const fields: Array<[string, string]> = [
134
- ['turnId', 'turn_id'], ['intentId', 'intent_id'], ['taskId', 'task_id'],
133
+ ['turnId', 'turn_id'], ['taskId', 'task_id'],
135
134
  ['subtaskId', 'subtask_id'], ['parentSubtaskId', 'parent_subtask_id'], ['runId', 'run_id'], ['traceId', 'trace_id'], ['domain', 'domain'],
136
135
  ]
137
136
  for (const [target, source] of fields) if (typeof args[source] === 'string' && args[source].trim()) lineage[target] = args[source].trim().slice(0, 160)
@@ -506,7 +505,8 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
506
505
  convert_task_id: { type: 'string', required: true },
507
506
  kb: { type: 'string', description: '目标知识库 id;省略则写入默认知识库' },
508
507
  title: { type: 'string' },
509
- source_id: { type: 'string', description: '写进这一条已有的 SourceRaw(同一实体的新版本)。省略则按 external_id / 内容算一个新来源 —— 给一份「已经录进库的文档重新转换」时应当传它,否则会多出一条来源。' },
508
+ source_id: { type: 'string', description: '导入前分配的稳定 Source Raw ID;重试沿用,更新已有来源时明确传入其 ID。' },
509
+ allow_duplicate_title: { type: 'boolean', description: '显式允许同名独立来源;来源身份由 source_id 指定。' },
510
510
  plan_only: { type: 'boolean' },
511
511
  session_key: { type: 'string' },
512
512
  task_id: { type: 'string' },
@@ -533,6 +533,7 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
533
533
  knowledgeRoot: target.knowledgeRoot,
534
534
  ...(typeof args.title === 'string' && args.title.trim() ? { title: args.title.trim() } : {}),
535
535
  ...(typeof args.source_id === 'string' && args.source_id.trim() ? { sourceId: args.source_id.trim() } : {}),
536
+ ...(args.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}),
536
537
  planOnly: args.plan_only === true,
537
538
  ...(lineageOf(args, exec) ? { lineage: lineageOf(args, exec)! } : {}),
538
539
  })
@@ -588,6 +589,7 @@ export function applyConvert(ctx: Context, deps: ConvertHostDeps): ConvertHost {
588
589
  knowledgeRoot: target.knowledgeRoot,
589
590
  ...(typeof body.title === 'string' && body.title.trim() ? { title: body.title.trim() } : {}),
590
591
  ...(typeof body.source_id === 'string' && body.source_id.trim() ? { sourceId: body.source_id.trim() } : {}),
592
+ ...(body.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}),
591
593
  planOnly: body.plan_only === true,
592
594
  ...(body.lineage && typeof body.lineage === 'object' ? { lineage: body.lineage as Record<string, string> } : {}),
593
595
  })
@@ -46,6 +46,7 @@ export interface IngestTarget {
46
46
  externalId?: string
47
47
  metadata?: Record<string, unknown>
48
48
  sourceId?: string
49
+ allowDuplicateTitle?: boolean
49
50
  lineage?: LineageContext
50
51
  /** OKF v0.2 溯源信息(0048):写成 front matter 的顶层键,不塞进 metadata。 */
51
52
  provenance?: SourceProvenance
@@ -77,6 +78,7 @@ export interface IngestOptions {
77
78
  * 历史保留、卡片与知识的出处不变、正文与图片引用一起更新。
78
79
  */
79
80
  sourceId?: string
81
+ allowDuplicateTitle?: boolean
80
82
  metadata?: Record<string, unknown>
81
83
  lineage?: Record<string, unknown>
82
84
  /** Dry run: build the plan without copying assets or writing the source. */
@@ -455,6 +457,7 @@ export async function ingestDelivery(options: IngestOptions): Promise<Envelope<I
455
457
  metadata,
456
458
  provenance,
457
459
  ...(options.sourceId?.trim() ? { sourceId: options.sourceId.trim() } : {}),
460
+ ...(options.allowDuplicateTitle === true ? { allowDuplicateTitle: true } : {}),
458
461
  ...(options.lineage ? { lineage: options.lineage as LineageContext } : {}),
459
462
  })
460
463
 
@@ -5,7 +5,7 @@
5
5
  * `/diagnosis/graph`)。视图是消费者,不是所有者 —— 模型放在领域层,客户端和宿主
6
6
  * 各自 import,谁都不能私自改形状。
7
7
  *
8
- * 目录约定:`src/diagnosis/` 与 `src/intent/`、`src/convert/` 平级,都是领域子模块;
8
+ * 目录约定:`src/diagnosis/` 与 `src/convert/` 平级,都是领域子模块;
9
9
  * `src/client/` 只放渲染。判据与 0020 第 3 条一致:**目录镜像领域流水线,不镜像技术层次**。
10
10
  */
11
11
 
package/src/images.ts CHANGED
@@ -185,7 +185,7 @@ export async function publishKnowledgeImage(
185
185
  throw new ImagePublishError('ASSET_NAME_COLLISION', '目标文件名已被不同内容占用,未覆盖现有图片。')
186
186
  }
187
187
  if (!existing?.equals(bytes)) await writeBufferAtomic(join(assetDir, storedFilename), bytes)
188
- // Standalone hosted images have no SourceRaw or card by design. Keep that intent beside the
188
+ // Standalone hosted images have no SourceRaw or card by design. Keep that reason beside the
189
189
  // asset so the orphan sweep does not mistake it for a disposable extraction artifact.
190
190
  await writeTextAtomic(join(assetDir, PUBLISHED_IMAGE_MARKER), 'image_publish\n')
191
191
  const relativePath = `${ASSETS_DIR}/${bucket}/${storedFilename}`