dsh-data-domain-knowledge 0.1.0-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +360 -0
- package/cordis.patch.yml +410 -0
- package/eval/README.md +76 -0
- package/eval/evaluate-retrieval.mjs +121 -0
- package/eval/queries.example.jsonl +2 -0
- package/lib/client.js +111415 -0
- package/lib/types/config-schema.d.ts +35 -0
- package/lib/types/config-schema.d.ts.map +1 -0
- package/lib/types/config-schema.js +34 -0
- package/lib/types/config-schema.js.map +1 -0
- package/lib/types/convert/core-loader.d.ts +32 -0
- package/lib/types/convert/core-loader.d.ts.map +1 -0
- package/lib/types/convert/core-loader.js +60 -0
- package/lib/types/convert/core-loader.js.map +1 -0
- package/lib/types/convert/host.d.ts +88 -0
- package/lib/types/convert/host.d.ts.map +1 -0
- package/lib/types/convert/host.js +479 -0
- package/lib/types/convert/host.js.map +1 -0
- package/lib/types/convert/ingest.d.ts +125 -0
- package/lib/types/convert/ingest.d.ts.map +1 -0
- package/lib/types/convert/ingest.js +228 -0
- package/lib/types/convert/ingest.js.map +1 -0
- package/lib/types/convert/settings-schema.d.ts +18 -0
- package/lib/types/convert/settings-schema.d.ts.map +1 -0
- package/lib/types/convert/settings-schema.js +26 -0
- package/lib/types/convert/settings-schema.js.map +1 -0
- package/lib/types/convert/types.d.ts +46 -0
- package/lib/types/convert/types.d.ts.map +1 -0
- package/lib/types/convert/types.js +41 -0
- package/lib/types/convert/types.js.map +1 -0
- package/lib/types/diagnosis/cards.d.ts +71 -0
- package/lib/types/diagnosis/cards.d.ts.map +1 -0
- package/lib/types/diagnosis/cards.js +482 -0
- package/lib/types/diagnosis/cards.js.map +1 -0
- package/lib/types/diagnosis/problem-classes.d.ts +51 -0
- package/lib/types/diagnosis/problem-classes.d.ts.map +1 -0
- package/lib/types/diagnosis/problem-classes.js +66 -0
- package/lib/types/diagnosis/problem-classes.js.map +1 -0
- package/lib/types/diagnosis/schema.d.ts +65 -0
- package/lib/types/diagnosis/schema.d.ts.map +1 -0
- package/lib/types/diagnosis/schema.js +208 -0
- package/lib/types/diagnosis/schema.js.map +1 -0
- package/lib/types/diagnosis/seed-source.d.ts +20 -0
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -0
- package/lib/types/diagnosis/seed-source.js +96 -0
- package/lib/types/diagnosis/seed-source.js.map +1 -0
- package/lib/types/diagnosis/seed.d.ts +50 -0
- package/lib/types/diagnosis/seed.d.ts.map +1 -0
- package/lib/types/diagnosis/seed.js +242 -0
- package/lib/types/diagnosis/seed.js.map +1 -0
- package/lib/types/diagnosis/store.d.ts +81 -0
- package/lib/types/diagnosis/store.d.ts.map +1 -0
- package/lib/types/diagnosis/store.js +409 -0
- package/lib/types/diagnosis/store.js.map +1 -0
- package/lib/types/diagnosis/types.d.ts +124 -0
- package/lib/types/diagnosis/types.d.ts.map +1 -0
- package/lib/types/diagnosis/types.js +13 -0
- package/lib/types/diagnosis/types.js.map +1 -0
- package/lib/types/directory-picker.d.ts +94 -0
- package/lib/types/directory-picker.d.ts.map +1 -0
- package/lib/types/directory-picker.js +110 -0
- package/lib/types/directory-picker.js.map +1 -0
- package/lib/types/extract-model.d.ts +76 -0
- package/lib/types/extract-model.d.ts.map +1 -0
- package/lib/types/extract-model.js +193 -0
- package/lib/types/extract-model.js.map +1 -0
- package/lib/types/extract.d.ts +121 -0
- package/lib/types/extract.d.ts.map +1 -0
- package/lib/types/extract.js +549 -0
- package/lib/types/extract.js.map +1 -0
- package/lib/types/fts.d.ts +96 -0
- package/lib/types/fts.d.ts.map +1 -0
- package/lib/types/fts.js +184 -0
- package/lib/types/fts.js.map +1 -0
- package/lib/types/index.d.ts +39 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +1112 -0
- package/lib/types/index.js.map +1 -0
- package/lib/types/intent/engine.d.ts +108 -0
- package/lib/types/intent/engine.d.ts.map +1 -0
- package/lib/types/intent/engine.js +501 -0
- package/lib/types/intent/engine.js.map +1 -0
- package/lib/types/intent/host.d.ts +88 -0
- package/lib/types/intent/host.d.ts.map +1 -0
- package/lib/types/intent/host.js +389 -0
- package/lib/types/intent/host.js.map +1 -0
- package/lib/types/intent/preset-tools.d.ts +37 -0
- package/lib/types/intent/preset-tools.d.ts.map +1 -0
- package/lib/types/intent/preset-tools.js +45 -0
- package/lib/types/intent/preset-tools.js.map +1 -0
- package/lib/types/intent/settings-schema.d.ts +59 -0
- package/lib/types/intent/settings-schema.d.ts.map +1 -0
- package/lib/types/intent/settings-schema.js +57 -0
- package/lib/types/intent/settings-schema.js.map +1 -0
- package/lib/types/intent/store.d.ts +75 -0
- package/lib/types/intent/store.d.ts.map +1 -0
- package/lib/types/intent/store.js +139 -0
- package/lib/types/intent/store.js.map +1 -0
- package/lib/types/intent/tools.d.ts +63 -0
- package/lib/types/intent/tools.d.ts.map +1 -0
- package/lib/types/intent/tools.js +108 -0
- package/lib/types/intent/tools.js.map +1 -0
- package/lib/types/intent/types.d.ts +189 -0
- package/lib/types/intent/types.d.ts.map +1 -0
- package/lib/types/intent/types.js +62 -0
- package/lib/types/intent/types.js.map +1 -0
- package/lib/types/knowledge-bases.d.ts +266 -0
- package/lib/types/knowledge-bases.d.ts.map +1 -0
- package/lib/types/knowledge-bases.js +1107 -0
- package/lib/types/knowledge-bases.js.map +1 -0
- package/lib/types/layout.d.ts +262 -0
- package/lib/types/layout.d.ts.map +1 -0
- package/lib/types/layout.js +312 -0
- package/lib/types/layout.js.map +1 -0
- package/lib/types/migrate.d.ts +110 -0
- package/lib/types/migrate.d.ts.map +1 -0
- package/lib/types/migrate.js +280 -0
- package/lib/types/migrate.js.map +1 -0
- package/lib/types/recall-rank.d.ts +158 -0
- package/lib/types/recall-rank.d.ts.map +1 -0
- package/lib/types/recall-rank.js +261 -0
- package/lib/types/recall-rank.js.map +1 -0
- package/lib/types/runtime.d.ts +568 -0
- package/lib/types/runtime.d.ts.map +1 -0
- package/lib/types/runtime.js +3045 -0
- package/lib/types/runtime.js.map +1 -0
- package/lib/types/settings-schema.d.ts +117 -0
- package/lib/types/settings-schema.d.ts.map +1 -0
- package/lib/types/settings-schema.js +92 -0
- package/lib/types/settings-schema.js.map +1 -0
- package/lib/types/settings-volatile.d.ts +102 -0
- package/lib/types/settings-volatile.d.ts.map +1 -0
- package/lib/types/settings-volatile.js +114 -0
- package/lib/types/settings-volatile.js.map +1 -0
- package/lib/types/types.d.ts +799 -0
- package/lib/types/types.d.ts.map +1 -0
- package/lib/types/types.js +387 -0
- package/lib/types/types.js.map +1 -0
- package/lib/types/wiki-types.d.ts +68 -0
- package/lib/types/wiki-types.d.ts.map +1 -0
- package/lib/types/wiki-types.js +322 -0
- package/lib/types/wiki-types.js.map +1 -0
- package/package.json +101 -0
- package/preset/tita-data-agent/agent.cordis.yml +376 -0
- package/preset/tita-data-agent/preset.yml +3 -0
- package/python/requirements.txt +8 -0
- package/scripts/doctor.mjs +70 -0
- package/scripts/e2e-pipeline.mjs +355 -0
- package/scripts/install-agent-preset.mjs +92 -0
- package/scripts/setup-embedding.sh +34 -0
- package/scripts/sync-agent-preset.mjs +188 -0
- package/src/client/AgentPresetCard.tsx +247 -0
- package/src/client/DirectoryPicker.tsx +357 -0
- package/src/client/InfoHint.tsx +65 -0
- package/src/client/IntentLogicTab.tsx +185 -0
- package/src/client/IntentTab.tsx +715 -0
- package/src/client/KnowledgeBaseTab.tsx +1454 -0
- package/src/client/KnowledgeGraphView.tsx +306 -0
- package/src/client/KnowledgePanel.tsx +381 -0
- package/src/client/KnowledgeStepRail.tsx +60 -0
- package/src/client/NodeSettings.tsx +392 -0
- package/src/client/PreviewSections.tsx +116 -0
- package/src/client/RecallPanel.tsx +399 -0
- package/src/client/TypeDefinitionDrawer.tsx +215 -0
- package/src/client/WikiFlowCard.tsx +226 -0
- package/src/client/WikiTypeEditor.tsx +332 -0
- package/src/client/agent-preset.ts +121 -0
- package/src/client/arch/AgentArchitectureGraph.tsx +219 -0
- package/src/client/arch/NodeLogicPanel.tsx +133 -0
- package/src/client/arch/ProblemDetailPanel.tsx +88 -0
- package/src/client/arch/model.ts +691 -0
- package/src/client/architecture.css +191 -0
- package/src/client/architecture.tsx +530 -0
- package/src/client/attribution/AttributionGraph.tsx +104 -0
- package/src/client/attribution/AttributionTab.tsx +332 -0
- package/src/client/attribution/CaseView.tsx +102 -0
- package/src/client/attribution/GraphStage.tsx +87 -0
- package/src/client/attribution/RunView.tsx +184 -0
- package/src/client/attribution/StageChrome.tsx +403 -0
- package/src/client/attribution/ViewSwitch.tsx +107 -0
- package/src/client/attribution/graph-options.ts +381 -0
- package/src/client/attribution/model.ts +462 -0
- package/src/client/config-scope.ts +119 -0
- package/src/client/convert/ConvertPanel.tsx +594 -0
- package/src/client/convert/styles.css +138 -0
- package/src/client/convert/styles.ts +49 -0
- package/src/client/format.ts +39 -0
- package/src/client/graph/layout.ts +181 -0
- package/src/client/graph.css +113 -0
- package/src/client/index.ts +98 -0
- package/src/client/problem-domains.ts +114 -0
- package/src/client/styles.css +940 -0
- package/src/client/styles.ts +11 -0
- package/src/client/useFitScale.ts +68 -0
- package/src/config-schema.ts +45 -0
- package/src/convert/core-loader.ts +68 -0
- package/src/convert/host.ts +579 -0
- package/src/convert/ingest.ts +367 -0
- package/src/convert/settings-schema.ts +27 -0
- package/src/convert/types.ts +65 -0
- package/src/diagnosis/cards.ts +498 -0
- package/src/diagnosis/problem-classes.ts +117 -0
- package/src/diagnosis/schema.ts +260 -0
- package/src/diagnosis/seed-source.ts +107 -0
- package/src/diagnosis/seed.ts +291 -0
- package/src/diagnosis/store.ts +432 -0
- package/src/diagnosis/types.ts +152 -0
- package/src/directory-picker.ts +164 -0
- package/src/extract-model.ts +227 -0
- package/src/extract.ts +632 -0
- package/src/fts.ts +203 -0
- package/src/index.ts +1091 -0
- package/src/intent/README.md +63 -0
- package/src/intent/engine.ts +527 -0
- package/src/intent/host.ts +462 -0
- package/src/intent/preset-tools.ts +48 -0
- package/src/intent/settings-schema.ts +63 -0
- package/src/intent/store.ts +143 -0
- package/src/intent/tools.ts +139 -0
- package/src/intent/types.ts +245 -0
- package/src/knowledge-bases.ts +1160 -0
- package/src/layout.ts +350 -0
- package/src/migrate.ts +262 -0
- package/src/python/embedding_worker.py +471 -0
- package/src/recall-rank.ts +299 -0
- package/src/runtime.ts +3069 -0
- package/src/settings-schema.ts +94 -0
- package/src/settings-volatile.ts +122 -0
- package/src/types.ts +1140 -0
- package/src/wiki-types.ts +357 -0
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The knowledge-ingestion seam.
|
|
3
|
+
*
|
|
4
|
+
* Rules this file obeys, and why:
|
|
5
|
+
*
|
|
6
|
+
* 1. **One write entry, in-process.** Since 0018 the convert host lives inside
|
|
7
|
+
* the knowledge plugin, so ingestion calls `KnowledgeRuntime.addSource()`
|
|
8
|
+
* directly instead of `POST /knowledge/sources`. That removes a JSON body
|
|
9
|
+
* cap (the old transport 400'd above ~1.2 MB) and removes port discovery,
|
|
10
|
+
* startup ordering and error-code re-translation from the failure surface.
|
|
11
|
+
* The structural `IngestTarget` interface is deliberately the only knowledge
|
|
12
|
+
* API this module may touch — conversion code must not reach into the store.
|
|
13
|
+
* 2. **The kernel never writes the knowledge store.** It only produces
|
|
14
|
+
* `convert-out/<taskId>/`. This module is the translation step.
|
|
15
|
+
* 3. **`source_type` stays the knowledge side's decision.** We propose
|
|
16
|
+
* candidates; if the store rejects the value we surface its error verbatim
|
|
17
|
+
* instead of silently defaulting.
|
|
18
|
+
* 4. **Images must land inside the knowledge root**, because retrieval only
|
|
19
|
+
* reads local relative paths — and only the *first* image of a unit reaches
|
|
20
|
+
* the multimodal embedder, so we also emit card hints that tell the Agent how
|
|
21
|
+
* to reference them.
|
|
22
|
+
*/
|
|
23
|
+
|
|
24
|
+
import { cp, mkdir, stat } from 'node:fs/promises'
|
|
25
|
+
import { basename, join, resolve, sep } from 'node:path'
|
|
26
|
+
import type { ConvertService, IngestPayload } from 'dsh-convert-core'
|
|
27
|
+
import type { LineageContext } from 'dsh-data-contracts/refs'
|
|
28
|
+
import type { SourceType, SourceTypeDef } from '../types.js'
|
|
29
|
+
import {
|
|
30
|
+
failure,
|
|
31
|
+
KNOWLEDGE_ASSET_DIR,
|
|
32
|
+
KNOWLEDGE_SOURCE_DIR,
|
|
33
|
+
KNOWLEDGE_WIKI_DIR,
|
|
34
|
+
success,
|
|
35
|
+
type Envelope,
|
|
36
|
+
} from './types.js'
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* The only slice of the knowledge store this seam is allowed to use. Declared
|
|
40
|
+
* structurally so this module never imports `runtime.ts`.
|
|
41
|
+
*/
|
|
42
|
+
export interface IngestTarget {
|
|
43
|
+
addSource(input: {
|
|
44
|
+
sourceType: SourceType
|
|
45
|
+
title?: string
|
|
46
|
+
content: string
|
|
47
|
+
externalId?: string
|
|
48
|
+
metadata?: Record<string, unknown>
|
|
49
|
+
sourceId?: string
|
|
50
|
+
lineage?: LineageContext
|
|
51
|
+
}): Promise<{
|
|
52
|
+
ok: boolean
|
|
53
|
+
data: { sourceId: string; path: string }
|
|
54
|
+
error?: { code?: string; message?: string; suggestion?: string } | null
|
|
55
|
+
}>
|
|
56
|
+
/**
|
|
57
|
+
* 该知识库的来源分类注册表(0013,**只读**)。
|
|
58
|
+
*
|
|
59
|
+
* 只用来把「候选分类」告诉调用方,转换模块不参与分类判定、也不改注册表,
|
|
60
|
+
* 所以它仍然是 knowledge 侧的能力,不算越界写。可选:老的测试替身可以不给。
|
|
61
|
+
*/
|
|
62
|
+
sourceTypes?(): SourceTypeDef[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface IngestOptions {
|
|
66
|
+
service: ConvertService
|
|
67
|
+
taskId: string
|
|
68
|
+
/** The live knowledge runtime; the write goes through `addSource` only. */
|
|
69
|
+
runtime: IngestTarget
|
|
70
|
+
/** Absolute knowledge root the runtime currently writes to. */
|
|
71
|
+
knowledgeRoot: string
|
|
72
|
+
/** Business classification. Omitted means "let the store decide/default". */
|
|
73
|
+
sourceType?: string
|
|
74
|
+
title?: string
|
|
75
|
+
metadata?: Record<string, unknown>
|
|
76
|
+
lineage?: Record<string, unknown>
|
|
77
|
+
/** Dry run: build the plan without copying assets or writing the source. */
|
|
78
|
+
planOnly?: boolean
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export interface PlacedAsset {
|
|
82
|
+
rel: string
|
|
83
|
+
/** Absolute path inside the knowledge root. */
|
|
84
|
+
target: string
|
|
85
|
+
bytes: number
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface IngestOutcome {
|
|
89
|
+
taskId: string
|
|
90
|
+
sourceId?: string
|
|
91
|
+
sourcePath?: string
|
|
92
|
+
knowledgeRoot: string
|
|
93
|
+
externalId: string
|
|
94
|
+
contentHash: string
|
|
95
|
+
sourceFormat: string
|
|
96
|
+
parser: string
|
|
97
|
+
sourceType: string
|
|
98
|
+
sourceTypeSupplied: boolean
|
|
99
|
+
sourceTypeCandidates: string[]
|
|
100
|
+
assets: PlacedAsset[]
|
|
101
|
+
/** Path to the source file relative to the knowledge root, e.g. `01_source_truth/x.md`. */
|
|
102
|
+
relativeSourcePath?: string
|
|
103
|
+
cardHints: CardHints
|
|
104
|
+
notes: string[]
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export interface CardHints {
|
|
108
|
+
/** `entity_type` values accepted by the knowledge schema. */
|
|
109
|
+
entityTypes: string[]
|
|
110
|
+
sourceRefs: string[]
|
|
111
|
+
title: string
|
|
112
|
+
summary: string
|
|
113
|
+
/**
|
|
114
|
+
* Card body with image references already re-based to `02_wiki/<entityType>/`,
|
|
115
|
+
* so the first image actually reaches the multimodal embedder.
|
|
116
|
+
*/
|
|
117
|
+
contentWithImages: string
|
|
118
|
+
/** The image references as they must appear inside a card body. */
|
|
119
|
+
imageRefs: string[]
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const ENTITY_TYPES = ['concept', 'metric', 'table', 'event', 'playbook', 'case']
|
|
123
|
+
|
|
124
|
+
/** Refuse any path that would escape the knowledge root. */
|
|
125
|
+
export function safeJoin(root: string, ...parts: string[]): string | undefined {
|
|
126
|
+
const base = resolve(root)
|
|
127
|
+
const target = resolve(base, ...parts)
|
|
128
|
+
if (target !== base && !target.startsWith(base + sep)) return undefined
|
|
129
|
+
return target
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function rewriteImageRefs(markdown: string, mapping: Map<string, string>): string {
|
|
133
|
+
let result = markdown
|
|
134
|
+
for (const [from, to] of mapping) {
|
|
135
|
+
result = result.split(`(${from})`).join(`(${to})`)
|
|
136
|
+
}
|
|
137
|
+
return result
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function cardImageRef(hash: string, rel: string, entityType: string): string {
|
|
141
|
+
// A card lives at 02_wiki/<entityType>/<slug>.md; the asset lives at
|
|
142
|
+
// 01_source_truth/_assets/<hash>/<file>.
|
|
143
|
+
const depth = 2
|
|
144
|
+
const up = Array.from({ length: depth }, () => '..').join('/')
|
|
145
|
+
return `${up}/${KNOWLEDGE_SOURCE_DIR}/${KNOWLEDGE_ASSET_DIR}/${hash}/${basename(rel)}`
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function buildCardHints(input: {
|
|
149
|
+
payload: IngestPayload
|
|
150
|
+
sourceId: string
|
|
151
|
+
assetBucket: string
|
|
152
|
+
assetMapping: Map<string, string>
|
|
153
|
+
entityType?: string
|
|
154
|
+
}): CardHints {
|
|
155
|
+
const entityType = input.entityType && ENTITY_TYPES.includes(input.entityType) ? input.entityType : 'concept'
|
|
156
|
+
const content = rewriteImageRefs(
|
|
157
|
+
input.payload.content,
|
|
158
|
+
new Map([...input.assetMapping].map(([from, to]) => [from, cardImageRef(input.assetBucket, to, entityType)])),
|
|
159
|
+
)
|
|
160
|
+
const imageRefs = input.payload.assets.map(asset => cardImageRef(input.assetBucket, asset.rel, entityType))
|
|
161
|
+
const summary = input.payload.content
|
|
162
|
+
.replace(/!\[[^\]]*\]\([^)]*\)/g, '')
|
|
163
|
+
.replace(/[#>*`_\-[\]()]/g, ' ')
|
|
164
|
+
.replace(/\s+/g, ' ')
|
|
165
|
+
.trim()
|
|
166
|
+
.slice(0, 160)
|
|
167
|
+
return {
|
|
168
|
+
entityTypes: ENTITY_TYPES,
|
|
169
|
+
sourceRefs: [input.sourceId],
|
|
170
|
+
title: input.payload.title,
|
|
171
|
+
summary,
|
|
172
|
+
contentWithImages: content,
|
|
173
|
+
imageRefs,
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Turn a store rejection into an actionable suggestion without inventing one.
|
|
179
|
+
*
|
|
180
|
+
* 0013:分类候选来自**当前知识库的注册表**(含用户自定义分类),不再写死三个内置值。
|
|
181
|
+
*/
|
|
182
|
+
function suggestionFor(code: string, candidates: string[]): string | undefined {
|
|
183
|
+
if (code === 'SOURCE_TYPE_UNKNOWN') {
|
|
184
|
+
return `当前知识库的分类注册表里没有这个分类。可用分类:${candidates.join(' / ') || '(无)'};要新增分类请到「知识库设置 → 来源类型」,并写清怎么判断它是这一类。`
|
|
185
|
+
}
|
|
186
|
+
if (code === 'SOURCE_TYPE_DISABLED') {
|
|
187
|
+
return `该分类已停用。可用分类:${candidates.join(' / ') || '(无)'};要恢复请到「知识库设置 → 来源类型」重新启用。`
|
|
188
|
+
}
|
|
189
|
+
// 这里曾经有一条 `CONTENT_TOO_LARGE` 的建议(「调大 maxSourceBytes」)。0029 撤掉
|
|
190
|
+
// 那个上限之后,`addSource` 再也不会产出这个 code,留着它就成了一条指向不存在开关
|
|
191
|
+
// 的指路牌 —— 环境里已经没有任何东西会触发它。
|
|
192
|
+
if (code === 'SOURCE_TRUTH_DISABLED') {
|
|
193
|
+
return '在知识插件设置里打开「允许写入 SourceTruth」(allowSourceTruth)。'
|
|
194
|
+
}
|
|
195
|
+
return undefined
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function ingestDelivery(options: IngestOptions): Promise<Envelope<IngestOutcome>> {
|
|
199
|
+
const { service, taskId } = options
|
|
200
|
+
let payload: IngestPayload
|
|
201
|
+
try {
|
|
202
|
+
payload = await service.ingestPlan(taskId)
|
|
203
|
+
} catch (cause) {
|
|
204
|
+
const error = cause as Error & { code?: string; suggestion?: string }
|
|
205
|
+
return failure(
|
|
206
|
+
emptyOutcome(taskId, options.knowledgeRoot, options.sourceType),
|
|
207
|
+
error.code ?? 'TASK_NOT_READY',
|
|
208
|
+
error.message,
|
|
209
|
+
false,
|
|
210
|
+
error.suggestion,
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const bucket = payload.contentHash.slice(0, 12)
|
|
215
|
+
const assetRoot = safeJoin(options.knowledgeRoot, KNOWLEDGE_SOURCE_DIR, KNOWLEDGE_ASSET_DIR, bucket)
|
|
216
|
+
if (!assetRoot) {
|
|
217
|
+
return failure(emptyOutcome(taskId, options.knowledgeRoot, options.sourceType), 'PATH_OUTSIDE_KNOWLEDGE_ROOT', '资产目标路径越出知识库根目录。')
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const assetMapping = new Map<string, string>()
|
|
221
|
+
const placed: PlacedAsset[] = []
|
|
222
|
+
const notes: string[] = []
|
|
223
|
+
const warnings: string[] = []
|
|
224
|
+
|
|
225
|
+
if (options.planOnly !== true && payload.assets.length > 0) {
|
|
226
|
+
await mkdir(assetRoot, { recursive: true })
|
|
227
|
+
for (const asset of payload.assets) {
|
|
228
|
+
const target = safeJoin(assetRoot, basename(asset.rel))
|
|
229
|
+
if (!target) {
|
|
230
|
+
warnings.push(`跳过越界资产:${asset.rel}`)
|
|
231
|
+
continue
|
|
232
|
+
}
|
|
233
|
+
try {
|
|
234
|
+
await cp(asset.path, target, { force: true })
|
|
235
|
+
const info = await stat(target)
|
|
236
|
+
placed.push({ rel: asset.rel, target, bytes: info.size })
|
|
237
|
+
assetMapping.set(asset.rel, asset.rel)
|
|
238
|
+
} catch (cause) {
|
|
239
|
+
warnings.push(`资产复制失败(${asset.rel}):${cause instanceof Error ? cause.message : String(cause)}`)
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
if (placed.length !== payload.assets.length) {
|
|
243
|
+
notes.push(`${payload.assets.length - placed.length} 个图片资产未复制,正文中的对应引用会指向不存在的文件。`)
|
|
244
|
+
}
|
|
245
|
+
} else if (options.planOnly === true) {
|
|
246
|
+
for (const asset of payload.assets) assetMapping.set(asset.rel, asset.rel)
|
|
247
|
+
notes.push('planOnly=true:未复制资产、未写入 SourceRaw。')
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const sourceContent = rewriteImageRefs(
|
|
251
|
+
payload.content,
|
|
252
|
+
new Map([...assetMapping].map(([from, to]) => [from, `${KNOWLEDGE_ASSET_DIR}/${bucket}/${basename(to)}`])),
|
|
253
|
+
)
|
|
254
|
+
|
|
255
|
+
const metadata: Record<string, unknown> = {
|
|
256
|
+
...(options.metadata ?? {}),
|
|
257
|
+
source_format: payload.sourceFormat,
|
|
258
|
+
parser: payload.parser,
|
|
259
|
+
content_hash: payload.contentHash,
|
|
260
|
+
convert_task_id: taskId,
|
|
261
|
+
convert_contract_version: 1,
|
|
262
|
+
...(payload.sourceUrl ? { source_url: payload.sourceUrl } : {}),
|
|
263
|
+
...(payload.sourcePath ? { source_path: payload.sourcePath } : {}),
|
|
264
|
+
assets: placed.map(asset => `${KNOWLEDGE_ASSET_DIR}/${bucket}/${basename(asset.rel)}`),
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const sourceType = options.sourceType?.trim() || 'document'
|
|
268
|
+
// 分类候选取自当前知识库的注册表(0013):内置分类 + 用户自定义,只列启用的。
|
|
269
|
+
const sourceTypeCandidates = (options.runtime.sourceTypes?.() ?? [])
|
|
270
|
+
.filter((def: SourceTypeDef) => def.enabled)
|
|
271
|
+
.map((def: SourceTypeDef) => def.id)
|
|
272
|
+
payload.sourceTypeCandidates = sourceTypeCandidates
|
|
273
|
+
const title = options.title?.trim() || payload.title
|
|
274
|
+
const externalId = payload.externalId
|
|
275
|
+
|
|
276
|
+
if (!options.planOnly) {
|
|
277
|
+
const result = await options.runtime.addSource({
|
|
278
|
+
sourceType: sourceType as SourceType,
|
|
279
|
+
title,
|
|
280
|
+
content: sourceContent,
|
|
281
|
+
externalId,
|
|
282
|
+
metadata,
|
|
283
|
+
...(options.lineage ? { lineage: options.lineage as LineageContext } : {}),
|
|
284
|
+
})
|
|
285
|
+
|
|
286
|
+
if (!result.ok) {
|
|
287
|
+
const code = result.error?.code ?? 'KNOWLEDGE_WRITE_FAILED'
|
|
288
|
+
return failure(
|
|
289
|
+
emptyOutcome(taskId, options.knowledgeRoot, options.sourceType),
|
|
290
|
+
code,
|
|
291
|
+
result.error?.message ?? '知识库拒绝了该来源',
|
|
292
|
+
false,
|
|
293
|
+
result.error?.suggestion ?? suggestionFor(code, sourceTypeCandidates),
|
|
294
|
+
)
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const sourceId = result.data.sourceId
|
|
298
|
+
const sourcePath = result.data.path
|
|
299
|
+
const cardHints = buildCardHints({ payload, sourceId, assetBucket: bucket, assetMapping })
|
|
300
|
+
notes.push('SourceRaw 已写入;图片要进入多模态召回,还需要一张引用它们的 wiki 卡片(见 cardHints)。')
|
|
301
|
+
|
|
302
|
+
return success<IngestOutcome>(
|
|
303
|
+
{
|
|
304
|
+
taskId,
|
|
305
|
+
sourceId,
|
|
306
|
+
...(sourcePath ? { sourcePath } : {}),
|
|
307
|
+
knowledgeRoot: options.knowledgeRoot,
|
|
308
|
+
externalId,
|
|
309
|
+
contentHash: payload.contentHash,
|
|
310
|
+
sourceFormat: payload.sourceFormat,
|
|
311
|
+
parser: payload.parser,
|
|
312
|
+
sourceType,
|
|
313
|
+
sourceTypeSupplied: Boolean(options.sourceType?.trim()),
|
|
314
|
+
sourceTypeCandidates: payload.sourceTypeCandidates,
|
|
315
|
+
assets: placed,
|
|
316
|
+
...(sourcePath ? { relativeSourcePath: relativeTo(options.knowledgeRoot, sourcePath) } : {}),
|
|
317
|
+
cardHints,
|
|
318
|
+
notes,
|
|
319
|
+
},
|
|
320
|
+
warnings,
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Dry run: report what would happen, including the card hints.
|
|
325
|
+
const preview = buildCardHints({ payload, sourceId: '<add_source 返回的 sourceId>', assetBucket: bucket, assetMapping })
|
|
326
|
+
return success<IngestOutcome>(
|
|
327
|
+
{
|
|
328
|
+
taskId,
|
|
329
|
+
knowledgeRoot: options.knowledgeRoot,
|
|
330
|
+
externalId,
|
|
331
|
+
contentHash: payload.contentHash,
|
|
332
|
+
sourceFormat: payload.sourceFormat,
|
|
333
|
+
parser: payload.parser,
|
|
334
|
+
sourceType,
|
|
335
|
+
sourceTypeSupplied: Boolean(options.sourceType?.trim()),
|
|
336
|
+
sourceTypeCandidates: payload.sourceTypeCandidates,
|
|
337
|
+
assets: payload.assets.map(asset => ({ rel: asset.rel, target: join(assetRoot, basename(asset.rel)), bytes: asset.bytes })),
|
|
338
|
+
cardHints: preview,
|
|
339
|
+
notes: [...notes, `将写入 ${KNOWLEDGE_SOURCE_DIR},资产放入 ${KNOWLEDGE_ASSET_DIR}/${bucket}/`],
|
|
340
|
+
},
|
|
341
|
+
)
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function relativeTo(root: string, path: string): string {
|
|
345
|
+
const base = resolve(root)
|
|
346
|
+
const target = resolve(path)
|
|
347
|
+
return target.startsWith(base + sep) ? target.slice(base.length + 1) : target
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function emptyOutcome(taskId: string, knowledgeRoot: string, sourceType: string | undefined): IngestOutcome {
|
|
351
|
+
return {
|
|
352
|
+
taskId,
|
|
353
|
+
knowledgeRoot,
|
|
354
|
+
externalId: '',
|
|
355
|
+
contentHash: '',
|
|
356
|
+
sourceFormat: '',
|
|
357
|
+
parser: '',
|
|
358
|
+
sourceType: sourceType?.trim() || 'document',
|
|
359
|
+
sourceTypeSupplied: Boolean(sourceType?.trim()),
|
|
360
|
+
sourceTypeCandidates: [],
|
|
361
|
+
assets: [],
|
|
362
|
+
cardHints: { entityTypes: ENTITY_TYPES, sourceRefs: [], title: '', summary: '', contentWithImages: '', imageRefs: [] },
|
|
363
|
+
notes: [],
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
export { ENTITY_TYPES, KNOWLEDGE_WIKI_DIR }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 转换设置的 schema(纯数据,单独一个模块)。
|
|
3
|
+
*
|
|
4
|
+
* 与 `src/intent/settings-schema.ts` 同一个理由:0.1.7 之后它要被 `Config` 组合、
|
|
5
|
+
* 并被 `tests/settings-schema.test.mjs` 独立解析一遍,而 `host.ts` 会 `import`
|
|
6
|
+
* 工具层 —— 那条依赖在 `node --test` 里拉不起来(`dsh-tools` 的
|
|
7
|
+
* `@deepseek-ai/dsh-sandbox` 是未安装的 peer)。
|
|
8
|
+
*
|
|
9
|
+
* 0029 起这里**没有路径字段**:交付物与工作区都从知识插件的 `agentRoot` 推出来
|
|
10
|
+
* (`<agentRoot>/output/<日期>/`、`<agentRoot>/tmp/`)。一个 Agent 只有一个根路径,
|
|
11
|
+
* 所以这里再放一个 `rootDir` 就等于把「东西存在哪」这个答案劈成两半。
|
|
12
|
+
*
|
|
13
|
+
* @module
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import z from '@deepseek-ai/schemastery'
|
|
17
|
+
import { volatileFields } from '../settings-volatile.js'
|
|
18
|
+
import { DEFAULT_CONVERT_CONFIG, type ConvertPluginConfig } from './types.js'
|
|
19
|
+
|
|
20
|
+
/** 显式标注是为了 TS2742(见 `intent/settings-schema.ts` 上的同一条注释)。 */
|
|
21
|
+
export const CONVERT_SETTINGS_SCHEMA: Schemastery<ConvertPluginConfig> = z.object(volatileFields({
|
|
22
|
+
maxConcurrent: z.number().step(1).min(1).max(8).default(DEFAULT_CONVERT_CONFIG.maxConcurrent),
|
|
23
|
+
maxInputBytes: z.number().step(1).min(1024).default(DEFAULT_CONVERT_CONFIG.maxInputBytes),
|
|
24
|
+
retentionDays: z.number().step(1).max(365).default(DEFAULT_CONVERT_CONFIG.retentionDays),
|
|
25
|
+
toolWaitMs: z.number().step(1).min(1000).max(900000).default(DEFAULT_CONVERT_CONFIG.toolWaitMs),
|
|
26
|
+
ingestEnabled: z.boolean().default(DEFAULT_CONVERT_CONFIG.ingestEnabled),
|
|
27
|
+
}))
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-level types, defaults and the envelope shape for the **convert seam**.
|
|
3
|
+
*
|
|
4
|
+
* These live inside `data-knowledge` since 0018: conversion is a sub-capability
|
|
5
|
+
* of the knowledge plugin, not a separately mounted plugin. The kernel itself
|
|
6
|
+
* (`dsh-convert-core`) stays an independent, optional package.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { homedir } from 'node:os'
|
|
10
|
+
import { join } from 'node:path'
|
|
11
|
+
|
|
12
|
+
export const CONVERT_SETTINGS_NAMESPACE = 'tita-data-agent-convert'
|
|
13
|
+
|
|
14
|
+
/** Knowledge store layout the convert seam cooperates with (issue 0013). */
|
|
15
|
+
export const KNOWLEDGE_SOURCE_DIR = '01_source_truth'
|
|
16
|
+
export const KNOWLEDGE_WIKI_DIR = '02_wiki'
|
|
17
|
+
/** Sub-directory inside `01_source_truth` that holds converted images. */
|
|
18
|
+
export const KNOWLEDGE_ASSET_DIR = '_assets'
|
|
19
|
+
|
|
20
|
+
export interface ConvertPluginConfig {
|
|
21
|
+
maxConcurrent: number
|
|
22
|
+
maxInputBytes: number
|
|
23
|
+
/** Deliveries older than this are pruned on start; 0 disables. */
|
|
24
|
+
retentionDays: number
|
|
25
|
+
/** How long a tool call waits for the conversion before returning `running`. */
|
|
26
|
+
toolWaitMs: number
|
|
27
|
+
/** Allow `convert_ingest` to write into the knowledge store. */
|
|
28
|
+
ingestEnabled: boolean
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @deprecated 0029:转换不再有自己的根目录。交付物落
|
|
33
|
+
* `<agentRoot>/output/<日期>/`、工作区落 `<agentRoot>/tmp/<日期>/`,
|
|
34
|
+
* 由 `layout.ts` 统一算出来。这个函数只留给**独立运行的内核**
|
|
35
|
+
* (`bin/tita-convert.mjs`)与它的单测,插件侧不再读它。
|
|
36
|
+
*/
|
|
37
|
+
export function defaultConvertRoot(): string {
|
|
38
|
+
if (process.env.DSH_CONVERT_ROOT) return process.env.DSH_CONVERT_ROOT
|
|
39
|
+
return join(homedir(), '.tita-data-agent', 'convert')
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export const DEFAULT_CONVERT_CONFIG: ConvertPluginConfig = {
|
|
43
|
+
maxConcurrent: 2,
|
|
44
|
+
maxInputBytes: 256 * 1024 * 1024,
|
|
45
|
+
retentionDays: 14,
|
|
46
|
+
toolWaitMs: 120_000,
|
|
47
|
+
ingestEnabled: true,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface Envelope<T> {
|
|
51
|
+
ok: boolean
|
|
52
|
+
data: T
|
|
53
|
+
warnings?: string[]
|
|
54
|
+
error?: { code: string; message: string; suggestion?: string; retryable?: boolean }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function success<T>(data: T, warnings?: string[]): Envelope<T> {
|
|
58
|
+
return { ok: true, data, ...(warnings && warnings.length > 0 ? { warnings } : {}) }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export function failure<T>(data: T, code: string, message: string, retryable = false, suggestion?: string): Envelope<T> {
|
|
62
|
+
return { ok: false, data, error: { code, message, retryable, ...(suggestion ? { suggestion } : {}) } }
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const WRITE_TOOLS = ['convert_source', 'convert_ingest'] as const
|