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.
- package/README.md +23 -16
- package/lib/client.js +596 -841
- package/lib/contract-refs.d.ts +0 -1
- package/lib/doc-page.js +7 -7
- package/lib/pages.js +928 -1145
- package/lib/types/agent-behavior-rules.d.ts.map +1 -1
- package/lib/types/agent-behavior-rules.js +1 -2
- package/lib/types/agent-behavior-rules.js.map +1 -1
- package/lib/types/axis-contract.js +1 -1
- package/lib/types/axis-contract.js.map +1 -1
- package/lib/types/card-organize.d.ts +2 -1
- package/lib/types/card-organize.d.ts.map +1 -1
- package/lib/types/card-organize.js +5 -3
- package/lib/types/card-organize.js.map +1 -1
- package/lib/types/config-schema.d.ts +1 -1
- package/lib/types/config-schema.js +1 -1
- package/lib/types/convert/host.d.ts +2 -3
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +5 -2
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +2 -0
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +1 -0
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +1 -1
- package/lib/types/diagnosis/types.js +1 -1
- package/lib/types/images.js +1 -1
- package/lib/types/index.d.ts +0 -4
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +30 -41
- package/lib/types/index.js.map +1 -1
- package/lib/types/knowledge-contract.d.ts +6 -0
- package/lib/types/knowledge-contract.d.ts.map +1 -1
- package/lib/types/knowledge-contract.js +6 -2
- package/lib/types/knowledge-contract.js.map +1 -1
- package/lib/types/layout.d.ts +9 -26
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +10 -28
- package/lib/types/layout.js.map +1 -1
- package/lib/types/materialize-snapshot.d.ts +5 -0
- package/lib/types/materialize-snapshot.d.ts.map +1 -1
- package/lib/types/migrate.d.ts +3 -1
- package/lib/types/migrate.d.ts.map +1 -1
- package/lib/types/migrate.js +3 -1
- package/lib/types/migrate.js.map +1 -1
- package/lib/types/organize-contract.d.ts.map +1 -1
- package/lib/types/organize-contract.js +2 -1
- package/lib/types/organize-contract.js.map +1 -1
- package/lib/types/pipeline/engine.d.ts +1 -0
- package/lib/types/pipeline/engine.d.ts.map +1 -1
- package/lib/types/pipeline/engine.js +9 -3
- package/lib/types/pipeline/engine.js.map +1 -1
- package/lib/types/pipeline/types.d.ts +6 -0
- package/lib/types/pipeline/types.d.ts.map +1 -1
- package/lib/types/pipeline/types.js.map +1 -1
- package/lib/types/present.d.ts +1 -1
- package/lib/types/present.js +1 -1
- package/lib/types/preset-ids.d.ts +0 -1
- package/lib/types/preset-ids.d.ts.map +1 -1
- package/lib/types/preset-ids.js +0 -3
- package/lib/types/preset-ids.js.map +1 -1
- package/lib/types/query-analysis.d.ts +0 -5
- package/lib/types/query-analysis.d.ts.map +1 -1
- package/lib/types/query-analysis.js +0 -16
- package/lib/types/query-analysis.js.map +1 -1
- package/lib/types/recall-delivery.d.ts +36 -0
- package/lib/types/recall-delivery.d.ts.map +1 -0
- package/lib/types/recall-delivery.js +16 -0
- package/lib/types/recall-delivery.js.map +1 -0
- package/lib/types/runtime.d.ts +26 -12
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +231 -106
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-volatile.d.ts +0 -7
- package/lib/types/settings-volatile.d.ts.map +1 -1
- package/lib/types/settings-volatile.js +0 -7
- package/lib/types/settings-volatile.js.map +1 -1
- package/lib/types/source-identity.d.ts +3 -0
- package/lib/types/source-identity.d.ts.map +1 -0
- package/lib/types/source-identity.js +5 -0
- package/lib/types/source-identity.js.map +1 -0
- package/lib/types/store/axis-references.d.ts.map +1 -1
- package/lib/types/store/axis-references.js +5 -3
- package/lib/types/store/axis-references.js.map +1 -1
- package/lib/types/store/knowledge-db.d.ts +1 -1
- package/lib/types/store/knowledge-db.d.ts.map +1 -1
- package/lib/types/store/knowledge-db.js +16 -2
- package/lib/types/store/knowledge-db.js.map +1 -1
- package/lib/types/types.d.ts +5 -4
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +6 -3
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-availability.d.ts +11 -0
- package/lib/types/wiki-availability.d.ts.map +1 -0
- package/lib/types/wiki-availability.js +39 -0
- package/lib/types/wiki-availability.js.map +1 -0
- package/lib/types/wiki-finalize.d.ts +4 -2
- package/lib/types/wiki-finalize.d.ts.map +1 -1
- package/lib/types/wiki-finalize.js +9 -27
- package/lib/types/wiki-finalize.js.map +1 -1
- package/lib/types/wiki-links.d.ts +35 -0
- package/lib/types/wiki-links.d.ts.map +1 -0
- package/lib/types/wiki-links.js +129 -0
- package/lib/types/wiki-links.js.map +1 -0
- package/package.json +16 -16
- package/scripts/doctor.mjs +0 -7
- package/scripts/setup-embedding.sh +0 -0
- package/src/agent-behavior-rules.ts +1 -2
- package/src/axis-contract.ts +1 -1
- package/src/card-organize.ts +5 -3
- package/src/client/AttributionEventGuide.tsx +3 -3
- package/src/client/AxisMatrix.tsx +86 -19
- package/src/client/DirectoryPicker.tsx +0 -2
- package/src/client/GuideArchitectureCanvas.tsx +5 -12
- package/src/client/ImportFilesDialog.tsx +1 -0
- package/src/client/InfoHint.tsx +1 -1
- package/src/client/KnowledgeBaseTab.tsx +113 -39
- package/src/client/KnowledgePanel.tsx +16 -9
- package/src/client/OperationLogTab.tsx +1 -1
- package/src/client/RecallPanel.tsx +5 -16
- package/src/client/TabGuide.tsx +3 -7
- package/src/client/WikiFlowPanel.tsx +10 -11
- package/src/client/arch/NodeLogicPanel.tsx +3 -3
- package/src/client/arch/model.ts +5 -113
- package/src/client/architecture.css +1 -1
- package/src/client/architecture.tsx +13 -27
- package/src/client/attribution/AttributionTab.tsx +141 -10
- package/src/client/pages.css +2 -2
- package/src/client/pages.tsx +4 -11
- package/src/client/styles.css +15 -96
- package/src/config-schema.ts +1 -1
- package/src/convert/host.ts +7 -5
- package/src/convert/ingest.ts +3 -0
- package/src/diagnosis/types.ts +1 -1
- package/src/images.ts +1 -1
- package/src/index.ts +27 -49
- package/src/knowledge-contract.ts +7 -2
- package/src/layout.ts +10 -32
- package/src/materialize-snapshot.ts +1 -0
- package/src/migrate.ts +3 -1
- package/src/organize-contract.ts +2 -1
- package/src/pipeline/engine.ts +9 -3
- package/src/pipeline/types.ts +2 -0
- package/src/present.ts +1 -1
- package/src/preset-ids.ts +0 -4
- package/src/query-analysis.ts +0 -20
- package/src/recall-delivery.ts +19 -0
- package/src/runtime.ts +220 -102
- package/src/settings-volatile.ts +0 -7
- package/src/source-identity.ts +4 -0
- package/src/store/axis-references.ts +5 -3
- package/src/store/knowledge-db.ts +14 -2
- package/src/types.ts +6 -4
- package/src/wiki-availability.ts +37 -0
- package/src/wiki-finalize.ts +9 -25
- package/src/wiki-links.ts +120 -0
- package/lib/types/intent/engine.d.ts +0 -122
- package/lib/types/intent/engine.d.ts.map +0 -1
- package/lib/types/intent/engine.js +0 -488
- package/lib/types/intent/engine.js.map +0 -1
- package/lib/types/intent/host.d.ts +0 -68
- package/lib/types/intent/host.d.ts.map +0 -1
- package/lib/types/intent/host.js +0 -249
- package/lib/types/intent/host.js.map +0 -1
- package/lib/types/intent/scope.d.ts +0 -25
- package/lib/types/intent/scope.d.ts.map +0 -1
- package/lib/types/intent/scope.js +0 -116
- package/lib/types/intent/scope.js.map +0 -1
- package/lib/types/intent/store.d.ts +0 -33
- package/lib/types/intent/store.d.ts.map +0 -1
- package/lib/types/intent/store.js +0 -239
- package/lib/types/intent/store.js.map +0 -1
- package/lib/types/intent/tools.d.ts +0 -62
- package/lib/types/intent/tools.d.ts.map +0 -1
- package/lib/types/intent/tools.js +0 -130
- package/lib/types/intent/tools.js.map +0 -1
- package/lib/types/intent/types.d.ts +0 -170
- package/lib/types/intent/types.d.ts.map +0 -1
- package/lib/types/intent/types.js +0 -56
- package/lib/types/intent/types.js.map +0 -1
- package/src/client/IntentLogic.tsx +0 -192
- package/src/client/IntentTab.tsx +0 -343
- package/src/intent/README.md +0 -42
- package/src/intent/engine.ts +0 -515
- package/src/intent/host.ts +0 -315
- package/src/intent/scope.ts +0 -125
- package/src/intent/store.ts +0 -218
- package/src/intent/tools.ts +0 -154
- package/src/intent/types.ts +0 -229
package/src/runtime.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { compileWikiNavigation, scanWikiLinks, stripWikiNavigation, wikiIndexContent, wikiLinkCandidates, wikiReference, type WikiReference } from './wiki-links.js'
|
|
2
|
+
import { wikiProjection, wikiGroupKey, procedureText, type ProcedureContent, type WikiKind } from './knowledge-contract.js'
|
|
3
|
+
import { wikiUnavailableReason } from './wiki-availability.js'
|
|
4
|
+
import { newSourceId } from './source-identity.js'
|
|
5
|
+
import { agentContextItem } from './recall-delivery.js'
|
|
2
6
|
import { buildDeriveSystem, DERIVE_CONTRACT_VERSION } from './derive-contract.js'
|
|
3
7
|
import { createHash, randomUUID } from 'node:crypto'
|
|
4
8
|
import { spawn, type ChildProcessWithoutNullStreams } from 'node:child_process'
|
|
@@ -40,7 +44,7 @@ import type { MaterializationGroup, PipelineStatus } from './pipeline/types.js'
|
|
|
40
44
|
import type { MaterializationSnapshot } from './materialize-snapshot.js'
|
|
41
45
|
import { candidatesFromReport, coordinateIdsOf, coordinateIsEmpty, type MaterializedCard } from './wiki-materialize.js'
|
|
42
46
|
import {
|
|
43
|
-
sectionRecords,
|
|
47
|
+
sectionRecords, ORGANIZE_VERSION,
|
|
44
48
|
buildOrganizePrompt, buildOrganizeSystem, checkOrganize, citationsFrom, layoutHash, layoutOf,
|
|
45
49
|
parseOrganize, placementsOf, renderFootnoteDefinitions, renderOrganizeBody,
|
|
46
50
|
type CardCitationRecord, type CardLayoutRecord, type OrganizeInput, type OrganizePlan,
|
|
@@ -1181,7 +1185,7 @@ function parseLineage(value: unknown): LineageContext | undefined {
|
|
|
1181
1185
|
if (!value || typeof value !== 'object' || Array.isArray(value)) return undefined
|
|
1182
1186
|
const record = value as Record<string, unknown>
|
|
1183
1187
|
const lineage: LineageContext = {}
|
|
1184
|
-
for (const key of ['sessionId', 'turnId', '
|
|
1188
|
+
for (const key of ['sessionId', 'turnId', 'taskId', 'subtaskId', 'parentSubtaskId', 'runId', 'traceId', 'domain'] as const) {
|
|
1185
1189
|
if (typeof record[key] === 'string' && record[key].trim()) lineage[key] = record[key].trim().slice(0, 160)
|
|
1186
1190
|
}
|
|
1187
1191
|
return Object.keys(lineage).length > 0 ? lineage : undefined
|
|
@@ -1311,7 +1315,7 @@ export function splitWikiNotes(card: Pick<KnowledgeCard, 'summary' | 'content'>)
|
|
|
1311
1315
|
const sections: Array<{ heading: string; lines: string[] }> = []
|
|
1312
1316
|
let current: { heading: string; lines: string[] } | undefined
|
|
1313
1317
|
let fence: string | undefined
|
|
1314
|
-
for (const line of card.content.trim().split('\n')) {
|
|
1318
|
+
for (const line of stripWikiNavigation(card.content).trim().split('\n')) {
|
|
1315
1319
|
const code = line.match(/^\s*(?:(?:\d+[.)]|[-+*])\s+)?(`{3,}|~{3,})(.*)$/)
|
|
1316
1320
|
if (code) {
|
|
1317
1321
|
if (!fence) fence = code[1]!
|
|
@@ -3179,8 +3183,8 @@ export class KnowledgeRuntime {
|
|
|
3179
3183
|
* 每条的原因仍然在 `sources[].message` 里。
|
|
3180
3184
|
*/
|
|
3181
3185
|
async importSources(input: {
|
|
3182
|
-
sources: Array<{ title?: string; content: string; externalId?: string; metadata?: Record<string, unknown>; allowDuplicateTitle?: boolean }>
|
|
3183
|
-
/**
|
|
3186
|
+
sources: Array<{ sourceId?: string; title?: string; content: string; externalId?: string; metadata?: Record<string, unknown>; allowDuplicateTitle?: boolean }>
|
|
3187
|
+
/** 默认只保存 Source Raw;显式 true 才运行完整处理流程。 */
|
|
3184
3188
|
compile?: boolean
|
|
3185
3189
|
lineage?: LineageContext
|
|
3186
3190
|
}): Promise<Envelope<ImportSourcesResult>> {
|
|
@@ -3201,6 +3205,7 @@ export class KnowledgeRuntime {
|
|
|
3201
3205
|
for (const [index, item] of items.entries()) {
|
|
3202
3206
|
const title = item?.title?.trim() || undefined
|
|
3203
3207
|
const result = await this.addSource({
|
|
3208
|
+
...(item?.sourceId ? { sourceId: item.sourceId } : {}),
|
|
3204
3209
|
...(title ? { title } : {}),
|
|
3205
3210
|
content: item?.content,
|
|
3206
3211
|
...(item?.externalId ? { externalId: item.externalId } : {}),
|
|
@@ -3321,7 +3326,7 @@ export class KnowledgeRuntime {
|
|
|
3321
3326
|
provenance?: SourceProvenance
|
|
3322
3327
|
/**
|
|
3323
3328
|
* 允许与已有的来源同标题(0051)。默认 `false`:重名是错误,拒收并要求改标题。
|
|
3324
|
-
* 传 `true`
|
|
3329
|
+
* 传 `true` 时按各自 Source Raw ID 并存,并回一条 warning。
|
|
3325
3330
|
*/
|
|
3326
3331
|
allowDuplicateTitle?: boolean
|
|
3327
3332
|
}): Promise<Envelope<{ sourceId: string; lineage?: LineageContext }>> {
|
|
@@ -3344,7 +3349,7 @@ export class KnowledgeRuntime {
|
|
|
3344
3349
|
if (db === null) {
|
|
3345
3350
|
return failure({ sourceId: '' }, 'STORE_UNAVAILABLE', this.storeProblem ?? '知识库不可用', true, '检查 Node 版本(正文进库之后需要 22.5+ 的 node:sqlite)与知识库文件的属主标记。')
|
|
3346
3351
|
}
|
|
3347
|
-
const sourceId = input.sourceId?.trim() || hash(input.externalId ?? input.content)
|
|
3352
|
+
const sourceId = input.sourceId?.trim() || (input.externalId?.startsWith('upload:') ? newSourceId() : hash(input.externalId ?? input.content))
|
|
3348
3353
|
if (!SAFE_ID.test(sourceId)) return failure({ sourceId: '' }, 'SOURCE_ID_INVALID', 'source_id 含有非法字符', false)
|
|
3349
3354
|
const title = input.title?.trim() || sourceId
|
|
3350
3355
|
/**
|
|
@@ -4386,7 +4391,8 @@ export class KnowledgeRuntime {
|
|
|
4386
4391
|
|
|
4387
4392
|
const listed = input.knowledge ? { knowledge: input.knowledge, total: input.knowledge.length } : await store.list({
|
|
4388
4393
|
objectId: input.coordinate.objectId,
|
|
4389
|
-
|
|
4394
|
+
actionId: input.coordinate.actionId, sceneId: input.coordinate.sceneId,
|
|
4395
|
+
...(input.existing?.metadata.wiki_kind ? { kind: input.existing.metadata.wiki_kind === 'way' ? 'procedure' as const : 'fact' as const } : {}),
|
|
4390
4396
|
excludeWithdrawn: true,
|
|
4391
4397
|
limit: MAX_ORGANIZE_KNOWLEDGE,
|
|
4392
4398
|
})
|
|
@@ -4438,7 +4444,7 @@ export class KnowledgeRuntime {
|
|
|
4438
4444
|
hash,
|
|
4439
4445
|
layout,
|
|
4440
4446
|
reuse,
|
|
4441
|
-
system: buildOrganizeSystem(input.
|
|
4447
|
+
system: buildOrganizeSystem(input.existing?.metadata.wiki_kind === 'way' || listed.knowledge[0]?.kind === 'procedure' ? 'way' : 'fact'),
|
|
4442
4448
|
prompt: buildOrganizePrompt(organizeInput),
|
|
4443
4449
|
}
|
|
4444
4450
|
}
|
|
@@ -4637,7 +4643,7 @@ export class KnowledgeRuntime {
|
|
|
4637
4643
|
const plan = planRelated(cards, relations, await this.projectedRelated(cards))
|
|
4638
4644
|
const entries = plan.get(cardKey(card))
|
|
4639
4645
|
if (entries === undefined || entries.length === 0) return {}
|
|
4640
|
-
return { related: entries.map(entry => ({ title: entry.title, entityType: entry.entityType, reason: entry.reason })) }
|
|
4646
|
+
return { related: entries.map(entry => ({ title: entry.title, entityType: entry.entityType, entityId: entry.entityId, reason: entry.reason })) }
|
|
4641
4647
|
} catch {
|
|
4642
4648
|
// 相关投影是**增强**:算不出来时不给清单,于是正文里一个 `[[…]]` 都不许出现 —— 而不是把卡片卡住。
|
|
4643
4649
|
return {}
|
|
@@ -4868,7 +4874,9 @@ export class KnowledgeRuntime {
|
|
|
4868
4874
|
const createdAt = typeof existing?.frontmatter.created_at === 'string' && existing.frontmatter.created_at ? existing.frontmatter.created_at : updatedAt
|
|
4869
4875
|
const provenance = [...(input.provenance ?? []), ...(input.lineage ? [{ ...input.lineage, role: 'created' as const, refType: 'knowledge_card', refId: input.entityId }] : [])]
|
|
4870
4876
|
// 旧版本曾把 schema_issues 写入 metadata。重写卡片时主动摘掉它,避免旧的待补结果继续出现。
|
|
4871
|
-
const { schema_issues: _staleIssues, ...metadata } = input.metadata ?? {}
|
|
4877
|
+
const { schema_issues: _staleIssues, ...metadata } = input.metadata ?? previous?.metadata ?? {}
|
|
4878
|
+
if (status === 'deprecated' && (input.origin === 'review' || input.origin === 'manual' || (input.origin === undefined && previous?.status !== 'deprecated'))) metadata.wiki_deprecation_origin = 'manual'
|
|
4879
|
+
if (status !== 'deprecated') delete metadata.wiki_deprecation_origin
|
|
4872
4880
|
void _staleIssues
|
|
4873
4881
|
// 备注与 review **默认继承库里已有的**:不传不等于清空(见入参注释)。
|
|
4874
4882
|
const annotations = input.annotations ?? previousAnnotations ?? []
|
|
@@ -4949,7 +4957,89 @@ export class KnowledgeRuntime {
|
|
|
4949
4957
|
return success({ entityId: input.entityId, ...(input.lineage ? { lineage: input.lineage } : {}) })
|
|
4950
4958
|
}
|
|
4951
4959
|
|
|
4952
|
-
/**
|
|
4960
|
+
/** Shared by direct reads, index publication and recall; always reads live contributions. */
|
|
4961
|
+
private async wikiUnavailability(cards: readonly KnowledgeCard[], suppliedPlacements?: Map<string, CardKnowledgeRow[]>): Promise<Map<string, string>> {
|
|
4962
|
+
const truth = await this.storeHandle()
|
|
4963
|
+
const knowledge = await this.knowledgeStore()
|
|
4964
|
+
if (!truth || !knowledge) return new Map(cards.map(card => [cardKey(card), '知识真源不可用,无法核验 Wiki。']))
|
|
4965
|
+
const placements = suppliedPlacements ?? truth.placementsOf(cards.map(card => card.entityId))
|
|
4966
|
+
const ids = [...new Set(cards.flatMap(card => [...(placements.get(card.entityId) ?? []).map(row => row.knowledgeId), ...Object.keys(card.metadata.knowledge_versions as object ?? {})]))]
|
|
4967
|
+
const rows = new Map<string, Knowledge>()
|
|
4968
|
+
for (let offset = 0; offset < ids.length; offset += 500) {
|
|
4969
|
+
const page = await knowledge.list({ ids: ids.slice(offset, offset + 500), includeMerged: true, limit: 500 })
|
|
4970
|
+
for (const row of page.knowledge) rows.set(row.id, row)
|
|
4971
|
+
}
|
|
4972
|
+
const sources = new Map((truth.raw('SELECT entity_id,head_version FROM source').all() as Array<{ entity_id: string; head_version: number }>).map(row => [row.entity_id, row.head_version]))
|
|
4973
|
+
const cases = new Map<string, number | undefined>()
|
|
4974
|
+
const result = new Map<string, string>()
|
|
4975
|
+
for (const card of cards) {
|
|
4976
|
+
const reason = wikiUnavailableReason(card, placements.get(card.entityId) ?? [], { knowledge: rows, sources, caseVersion: id => {
|
|
4977
|
+
if (!cases.has(id)) cases.set(id, truth.event(id)?.version)
|
|
4978
|
+
return cases.get(id)
|
|
4979
|
+
} })
|
|
4980
|
+
if (reason) result.set(cardKey(card), reason)
|
|
4981
|
+
}
|
|
4982
|
+
return result
|
|
4983
|
+
}
|
|
4984
|
+
|
|
4985
|
+
/** Read a stable Wiki link or browse an Object's coordinates without flattening its files. */
|
|
4986
|
+
async readWiki(input: { target?: string; objectId?: string; object?: string; actionId?: string; sceneId?: string; wikiKind?: WikiKind; includeContent?: boolean; limit?: number; offset?: number } = {}): Promise<Envelope<Record<string, unknown>>> {
|
|
4987
|
+
const empty = { wikis: [], total: 0, offset: input.offset ?? 0, hasMore: false, outgoing: [], backlinks: [] }
|
|
4988
|
+
const limit = input.limit ?? 20, offset = input.offset ?? 0
|
|
4989
|
+
if (!Number.isInteger(limit) || limit < 1 || limit > 100 || !Number.isInteger(offset) || offset < 0) return failure(empty, 'WIKI_PAGE_INVALID', 'limit 必须为 1–100 的整数,offset 必须为非负整数。', false)
|
|
4990
|
+
if (input.wikiKind !== undefined && input.wikiKind !== 'fact' && input.wikiKind !== 'way') return failure(empty, 'WIKI_KIND_INVALID', 'wiki_kind 只能为 fact 或 way。', false)
|
|
4991
|
+
if (!input.target?.trim() && !input.objectId?.trim() && !input.object?.trim()) return failure(empty, 'WIKI_SCOPE_REQUIRED', '请提供双链 target、object_id 或明确的 Object 名称。', false)
|
|
4992
|
+
const cards = await this.loadCards()
|
|
4993
|
+
let objectId = input.objectId
|
|
4994
|
+
if (input.object !== undefined) {
|
|
4995
|
+
const name = input.object.trim().toLocaleLowerCase()
|
|
4996
|
+
const objects = (await this.listAxisEntities('object') ?? []).filter(object => object.status !== 'deprecated' && object.status !== 'merged'
|
|
4997
|
+
&& [object.id, object.name, ...object.aliases].some(value => value.toLocaleLowerCase() === name))
|
|
4998
|
+
if (objects.length !== 1) return failure({ ...empty, candidates: objects.map(object => ({ id: object.id, name: object.name })) }, objects.length ? 'OBJECT_AMBIGUOUS' : 'OBJECT_NOT_FOUND', 'Object 名称未唯一解析,请使用明确的 object_id。', false)
|
|
4999
|
+
if (objectId !== undefined && objectId !== objects[0]!.id) return failure(empty, 'OBJECT_SCOPE_CONFLICT', 'object 与 object_id 指向不同对象。', false)
|
|
5000
|
+
objectId = objects[0]!.id
|
|
5001
|
+
}
|
|
5002
|
+
const inScope = (card: KnowledgeCard): boolean => (objectId === undefined || card.coordinate?.objectId === objectId)
|
|
5003
|
+
&& (input.actionId === undefined || card.coordinate?.actionId === input.actionId)
|
|
5004
|
+
&& (input.sceneId === undefined || card.coordinate?.sceneId === input.sceneId)
|
|
5005
|
+
&& (input.wikiKind === undefined || card.metadata.wiki_kind === input.wikiKind)
|
|
5006
|
+
let selected = cards.filter(inScope)
|
|
5007
|
+
if (input.target?.trim()) {
|
|
5008
|
+
selected = wikiLinkCandidates(selected, input.target)
|
|
5009
|
+
if (selected.length !== 1) return failure({ ...empty, candidates: selected.map(card => ({ ...wikiReference(card), coordinate: card.coordinate, wikiKind: card.metadata.wiki_kind })) }, selected.length ? 'WIKI_LINK_AMBIGUOUS' : 'WIKI_NOT_FOUND', '双链未唯一指向 Wiki,请从候选中使用稳定 target。', false)
|
|
5010
|
+
if (selected[0]!.status === 'deprecated') return failure(empty, 'WIKI_UNAVAILABLE', '目标 Wiki 已停用,请按 Object 坐标查找当前文件。', false)
|
|
5011
|
+
}
|
|
5012
|
+
selected = selected.filter(card => card.status !== 'deprecated').sort((a, b) => wikiReference(a).target.localeCompare(wikiReference(b).target))
|
|
5013
|
+
const page = selected.slice(offset, offset + limit)
|
|
5014
|
+
const includeContent = input.includeContent ?? Boolean(input.target)
|
|
5015
|
+
const truth = await this.storeHandle()
|
|
5016
|
+
const placements = truth?.placementsOf(page.map(card => card.entityId)) ?? new Map<string, CardKnowledgeRow[]>()
|
|
5017
|
+
const unavailableByKey = await this.wikiUnavailability(page, placements)
|
|
5018
|
+
const wikis: Array<Record<string, unknown>> = []
|
|
5019
|
+
for (const card of page) {
|
|
5020
|
+
const unavailable = unavailableByKey.get(cardKey(card))
|
|
5021
|
+
wikis.push({ ...wikiReference(card), coordinate: card.coordinate, wikiKind: card.metadata.wiki_kind, status: card.status, available: !unavailable,
|
|
5022
|
+
...(unavailable ? { reason: unavailable } : {}), ...(card.coordinate ? { coordinateLabels: await this.coordinateNames(card.coordinate) } : {}),
|
|
5023
|
+
...(includeContent && !unavailable ? { wiki: card } : {}),
|
|
5024
|
+
})
|
|
5025
|
+
}
|
|
5026
|
+
const first = input.target ? page[0] : undefined
|
|
5027
|
+
const live = cards.filter(card => card.status !== 'deprecated')
|
|
5028
|
+
const outgoing = first ? [...new Map(scanWikiLinks(first.content).flatMap(link => {
|
|
5029
|
+
const targets = wikiLinkCandidates(live, link.target)
|
|
5030
|
+
return targets.length === 1 && targets[0]!.entityId !== first.entityId ? [[targets[0]!.entityId, wikiReference(targets[0]!)] as const] : []
|
|
5031
|
+
})).values()] : []
|
|
5032
|
+
const backlinks = first ? live.filter(card => card.entityId !== first.entityId && scanWikiLinks(card.content).some(link => {
|
|
5033
|
+
const targets = wikiLinkCandidates(live, link.target)
|
|
5034
|
+
return targets.length === 1 && targets[0]!.entityId === first.entityId
|
|
5035
|
+
})).map(wikiReference) : []
|
|
5036
|
+
const data = { wikis, total: selected.length, offset, hasMore: offset + page.length < selected.length,
|
|
5037
|
+
nextOffset: offset + page.length < selected.length ? offset + page.length : null, outgoing, backlinks }
|
|
5038
|
+
await this.audit('wiki.read', { target: input.target, objectId, actionId: input.actionId, sceneId: input.sceneId, wikiKind: input.wikiKind, includeContent, returned: page.length })
|
|
5039
|
+
return success(data)
|
|
5040
|
+
}
|
|
5041
|
+
|
|
5042
|
+
/** Resolve one card by its stable ID, optionally constrained to a type. */
|
|
4953
5043
|
async readCard(entityId: string, entityType?: SchemaType): Promise<KnowledgeCard | undefined> {
|
|
4954
5044
|
const db = await this.storeHandle()
|
|
4955
5045
|
if (db === null) return undefined
|
|
@@ -5040,7 +5130,7 @@ export class KnowledgeRuntime {
|
|
|
5040
5130
|
if (card.status === 'problem' && status !== 'unreviewed') return failure({ entityId, status }, 'STATUS_TRANSITION_INVALID', 'problem 不能直接恢复为其他状态,请先改为 unreviewed', false)
|
|
5041
5131
|
const eventLineage = withRun(lineage, 'knowledge-status')
|
|
5042
5132
|
const provenance = [...(card.provenance ?? []), ...(eventLineage ? [{ ...eventLineage, role: 'executed' as const, refType: 'knowledge_status', refId: entityId }] : [])]
|
|
5043
|
-
const written = await this.writeCard({ ...card, status, sourceRefs: card.sourceRefs, provenance, metadata: { ...card.metadata, ...(problemNote?.trim() ? { problem_note: problemNote.trim() } : {}) } })
|
|
5133
|
+
const written = await this.writeCard({ ...card, status, sourceRefs: card.sourceRefs, provenance, origin: 'review', metadata: { ...card.metadata, ...(problemNote?.trim() ? { problem_note: problemNote.trim() } : {}) } })
|
|
5044
5134
|
if (written.ok === false) return failure({ entityId, status }, written.error.code, written.error.message, written.error.retryable, written.error.suggestion)
|
|
5045
5135
|
return success({ entityId, status, ...(eventLineage ? { lineage: eventLineage } : {}) })
|
|
5046
5136
|
}
|
|
@@ -5087,7 +5177,7 @@ export class KnowledgeRuntime {
|
|
|
5087
5177
|
* `metadata.related_cards`;这里继续剥离旧版本正文里的标记,保证存量卡片迁移期间
|
|
5088
5178
|
* 也不会把导航送进召回。
|
|
5089
5179
|
*/
|
|
5090
|
-
const recallBody = stripEventReferenceSection(stripRelatedSection(card.content))
|
|
5180
|
+
const recallBody = wikiIndexContent(stripEventReferenceSection(stripRelatedSection(card.content)))
|
|
5091
5181
|
for (const [index, chunk] of splitWikiNotes({ ...card, content: recallBody }).entries()) {
|
|
5092
5182
|
const baseId = `${card.entityType}:${card.entityId}`
|
|
5093
5183
|
const stableSection = layoutOf(card.metadata)?.sections?.find(section => section.title === chunk.heading)
|
|
@@ -5649,7 +5739,7 @@ export class KnowledgeRuntime {
|
|
|
5649
5739
|
existing: card,
|
|
5650
5740
|
...(input.imagePlan.byCard.get(key) === undefined ? {} : { images: input.imagePlan.byCard.get(key) }),
|
|
5651
5741
|
...(relatedPlan.get(key) === undefined ? {} : {
|
|
5652
|
-
related: (relatedPlan.get(key) ?? []).map(entry => ({ title: entry.title, entityType: entry.entityType, reason: entry.reason })),
|
|
5742
|
+
related: (relatedPlan.get(key) ?? []).map(entry => ({ title: entry.title, entityType: entry.entityType, entityId: entry.entityId, reason: entry.reason })),
|
|
5653
5743
|
}),
|
|
5654
5744
|
call: input.call,
|
|
5655
5745
|
})
|
|
@@ -6211,12 +6301,11 @@ export class KnowledgeRuntime {
|
|
|
6211
6301
|
// ------------------------------------------- Finalize · 索引 / 断链 / 相关(0049)
|
|
6212
6302
|
|
|
6213
6303
|
/**
|
|
6214
|
-
*
|
|
6215
|
-
*
|
|
6304
|
+
* 编译收尾:生成类型化 Wiki 的稳定双链与导航,更新 `metadata.related_cards`,
|
|
6305
|
+
* 清理旧相关块,并把断链统计交给编译报告与索引页。
|
|
6216
6306
|
*
|
|
6217
|
-
*
|
|
6218
|
-
*
|
|
6219
|
-
* `<!-- compiled:related -->` 只在这里做一次兼容清理。
|
|
6307
|
+
* 导航只放链接,不复制目标正文;面板使用结构化元数据,检索排除生成的导航块。
|
|
6308
|
+
* 旧卡片上的 `<!-- compiled:related -->` 在这里兼容清理。
|
|
6220
6309
|
*
|
|
6221
6310
|
* 三段都是确定性的,所以这个函数可以重复跑:正文和结构化相关数据都没变化时不写盘。
|
|
6222
6311
|
*/
|
|
@@ -6230,11 +6319,24 @@ export class KnowledgeRuntime {
|
|
|
6230
6319
|
* 它是**算出来的**,不是谁声明的:这张卡里的知识引用了哪个轴实体,而那个轴自己
|
|
6231
6320
|
* 有一张卡,就有了链接。模型抽出来的关系(`relations`)是第二层。
|
|
6232
6321
|
*/
|
|
6233
|
-
const
|
|
6234
|
-
const
|
|
6235
|
-
|
|
6236
|
-
const
|
|
6237
|
-
|
|
6322
|
+
const axes = (await this.listAxisEntities() ?? []).filter(axis => axis.status !== 'deprecated' && axis.status !== 'merged')
|
|
6323
|
+
const objects = axes.filter(axis => axis.kind === 'object')
|
|
6324
|
+
const names = new Map(axes.map(axis => [axis.id, axis.name]))
|
|
6325
|
+
const labeled = cards.map(card => card.metadata.wiki_kind && card.coordinate ? { ...card, metadata: { ...card.metadata,
|
|
6326
|
+
wiki_coordinate_labels: { object: names.get(card.coordinate.objectId) ?? card.coordinate.objectId, action: names.get(card.coordinate.actionId) ?? card.coordinate.actionId, scene: names.get(card.coordinate.sceneId) ?? card.coordinate.sceneId },
|
|
6327
|
+
} } : card)
|
|
6328
|
+
const live = labeled.filter(card => card.status !== 'deprecated')
|
|
6329
|
+
const nextCards = labeled.map(card => {
|
|
6330
|
+
const base = { ...card, content: stripEventReferenceSection(stripRelatedSection(card.content)) }
|
|
6331
|
+
if (!card.metadata.wiki_kind || card.status === 'deprecated') return base
|
|
6332
|
+
const navigation = compileWikiNavigation(base, live, objects)
|
|
6333
|
+
return { ...base, content: navigation.content, metadata: { ...base.metadata,
|
|
6334
|
+
wiki_links: navigation.outgoing, wiki_inline_targets: navigation.inlineTargets, wiki_object_refs: navigation.objectIds,
|
|
6335
|
+
} }
|
|
6336
|
+
})
|
|
6337
|
+
const projected = await this.projectedRelated(nextCards)
|
|
6338
|
+
const related = planRelated(live, relations, projected)
|
|
6339
|
+
const deadLinks = deadLinksOf(nextCards.filter(card => card.status !== 'deprecated'))
|
|
6238
6340
|
let rewritten = 0
|
|
6239
6341
|
for (const [index, card] of cards.entries()) {
|
|
6240
6342
|
const key = cardKey(card)
|
|
@@ -6242,7 +6344,7 @@ export class KnowledgeRuntime {
|
|
|
6242
6344
|
const entries = related.get(key) ?? []
|
|
6243
6345
|
const previousEntries = relatedEntriesOf(card.metadata)
|
|
6244
6346
|
const contentChanged = nextCard.content !== card.content
|
|
6245
|
-
const relatedChanged = JSON.stringify(previousEntries) !== JSON.stringify(entries)
|
|
6347
|
+
const relatedChanged = JSON.stringify(previousEntries) !== JSON.stringify(entries) || JSON.stringify(card.metadata.wiki_coordinate_labels) !== JSON.stringify(nextCard.metadata.wiki_coordinate_labels) || JSON.stringify(card.metadata.wiki_links) !== JSON.stringify(nextCard.metadata.wiki_links) || JSON.stringify(card.metadata.wiki_inline_targets) !== JSON.stringify(nextCard.metadata.wiki_inline_targets) || JSON.stringify(card.metadata.wiki_object_refs) !== JSON.stringify(nextCard.metadata.wiki_object_refs)
|
|
6246
6348
|
if (!contentChanged && !relatedChanged) continue
|
|
6247
6349
|
/**
|
|
6248
6350
|
* 没有来源的卡片写不进去(`writeCard` 要求 `source_refs` 非空),而这是**唯一**
|
|
@@ -6263,7 +6365,7 @@ export class KnowledgeRuntime {
|
|
|
6263
6365
|
status: card.status,
|
|
6264
6366
|
...(card.owner ? { owner: card.owner } : {}),
|
|
6265
6367
|
...(card.provenance ? { provenance: card.provenance } : {}),
|
|
6266
|
-
metadata: { ...
|
|
6368
|
+
metadata: { ...nextCard.metadata, [RELATED_METADATA_KEY]: entries },
|
|
6267
6369
|
origin: 'compile',
|
|
6268
6370
|
})
|
|
6269
6371
|
if (written.ok === false) {
|
|
@@ -6387,13 +6489,9 @@ export class KnowledgeRuntime {
|
|
|
6387
6489
|
}
|
|
6388
6490
|
}
|
|
6389
6491
|
|
|
6390
|
-
/**
|
|
6391
|
-
private contextCost(item: ContextItem
|
|
6392
|
-
|
|
6393
|
-
if (card === null) return 0
|
|
6394
|
-
const shallow = card.title.length + (card.summary?.length ?? 0)
|
|
6395
|
-
const knowledge = (item.knowledge ?? []).reduce((sum, row) => sum + row.text.length + row.state.length + 8, 0)
|
|
6396
|
-
return knowledge + shallow + card.content.length
|
|
6492
|
+
/** 与 Agent 实际收到的上下文条目使用同一序列化形状,包含完整 Wiki、依据和引用。 */
|
|
6493
|
+
private contextCost(item: ContextItem): number {
|
|
6494
|
+
return JSON.stringify(agentContextItem(item)).length
|
|
6397
6495
|
}
|
|
6398
6496
|
|
|
6399
6497
|
/** 溢出目录:**系统临时目录**,不随知识库走(0050 的取舍,见 issue)。 */
|
|
@@ -6421,17 +6519,14 @@ export class KnowledgeRuntime {
|
|
|
6421
6519
|
const { items, maxChars, recallId } = input
|
|
6422
6520
|
const hitCards = items.filter(item => item.reason === 'hit').length
|
|
6423
6521
|
const expandedCards = items.length - hitCards
|
|
6424
|
-
const totalChars = items.reduce((sum, item) => sum + this.contextCost(item
|
|
6522
|
+
const totalChars = items.reduce((sum, item) => sum + this.contextCost(item), 0)
|
|
6425
6523
|
if (!Number.isFinite(maxChars) || maxChars <= 0) {
|
|
6426
6524
|
return { items, report: { maxChars: 0, usedChars: totalChars, omittedCards: 0, omittedChars: 0, hitCards, expandedCards } }
|
|
6427
6525
|
}
|
|
6428
6526
|
const keptItems: ContextItem[] = []
|
|
6429
6527
|
let used = 0
|
|
6430
6528
|
for (const item of items) {
|
|
6431
|
-
const
|
|
6432
|
-
const shallow = this.contextCost(item, false)
|
|
6433
|
-
// 命中条目按整条算(它的正文就是证据);扩展条目可以先按「浅」那一档进。
|
|
6434
|
-
const cost = item.reason === 'hit' ? full : shallow
|
|
6529
|
+
const cost = this.contextCost(item)
|
|
6435
6530
|
if (used + cost > maxChars) continue
|
|
6436
6531
|
used += cost
|
|
6437
6532
|
keptItems.push(item)
|
|
@@ -6442,7 +6537,7 @@ export class KnowledgeRuntime {
|
|
|
6442
6537
|
maxChars,
|
|
6443
6538
|
usedChars: used,
|
|
6444
6539
|
omittedCards: omitted.length,
|
|
6445
|
-
omittedChars: omitted.reduce((sum, item) => sum + this.contextCost(item
|
|
6540
|
+
omittedChars: omitted.reduce((sum, item) => sum + this.contextCost(item), 0),
|
|
6446
6541
|
hitCards: keptItems.filter(item => item.reason === 'hit').length,
|
|
6447
6542
|
expandedCards: keptItems.filter(item => item.reason === 'expansion').length,
|
|
6448
6543
|
}
|
|
@@ -6554,13 +6649,17 @@ export class KnowledgeRuntime {
|
|
|
6554
6649
|
* 而不是报错:这一层是**增强**,它坏了不该让编译失败。
|
|
6555
6650
|
*/
|
|
6556
6651
|
private async projectedRelated(cards: KnowledgeCard[]): Promise<Array<{ fromKey: string; toKey: string }>> {
|
|
6652
|
+
const links = cards.flatMap(card => scanWikiLinks(card.content).flatMap(link => {
|
|
6653
|
+
const candidates = wikiLinkCandidates(cards.filter(target => target.status !== 'deprecated'), link.target)
|
|
6654
|
+
return candidates.length === 1 && candidates[0]!.entityId !== card.entityId ? [{ fromKey: cardKey(card), toKey: cardKey(candidates[0]!) }] : []
|
|
6655
|
+
}))
|
|
6557
6656
|
const knowledge = await this.knowledgeStore()
|
|
6558
|
-
if (knowledge === null) return
|
|
6657
|
+
if (knowledge === null) return links
|
|
6559
6658
|
const db = await this.storeHandle()
|
|
6560
|
-
if (db === null) return
|
|
6659
|
+
if (db === null) return links
|
|
6561
6660
|
let placements: Map<string, CardKnowledgeRow[]>
|
|
6562
|
-
try { placements = db.placements() } catch { return
|
|
6563
|
-
if (placements.size === 0) return
|
|
6661
|
+
try { placements = db.placements() } catch { return links }
|
|
6662
|
+
if (placements.size === 0) return links
|
|
6564
6663
|
/**
|
|
6565
6664
|
* 单轴卡:坐标里恰好只有一个分量非空。
|
|
6566
6665
|
*
|
|
@@ -6585,11 +6684,11 @@ export class KnowledgeRuntime {
|
|
|
6585
6684
|
bucket.push(key)
|
|
6586
6685
|
byAxis.set(axis, bucket)
|
|
6587
6686
|
}
|
|
6588
|
-
if (byAxis.size === 0) return
|
|
6687
|
+
if (byAxis.size === 0) return links
|
|
6589
6688
|
const ids = [...new Set([...placements.values()].flat().map(row => row.knowledgeId))]
|
|
6590
|
-
if (ids.length === 0) return
|
|
6689
|
+
if (ids.length === 0) return links
|
|
6591
6690
|
let axes: Map<string, { objectId: string; actionId: string; sceneId: string }>
|
|
6592
|
-
try { axes = await knowledge.axesOf(ids) } catch { return
|
|
6691
|
+
try { axes = await knowledge.axesOf(ids) } catch { return links }
|
|
6593
6692
|
const pairs = new Map<string, { fromKey: string; toKey: string }>()
|
|
6594
6693
|
for (const [cardId, rows] of placements) {
|
|
6595
6694
|
const fromKeys = keyById.get(cardId) ?? []
|
|
@@ -6609,7 +6708,7 @@ export class KnowledgeRuntime {
|
|
|
6609
6708
|
}
|
|
6610
6709
|
}
|
|
6611
6710
|
}
|
|
6612
|
-
return [...pairs.values()]
|
|
6711
|
+
return [...pairs.values(), ...links]
|
|
6613
6712
|
}
|
|
6614
6713
|
|
|
6615
6714
|
/**
|
|
@@ -8127,7 +8226,7 @@ export class KnowledgeRuntime {
|
|
|
8127
8226
|
: card.metadata.wiki_kind
|
|
8128
8227
|
? events.filter(event => {
|
|
8129
8228
|
const c = event.coordinateIds
|
|
8130
|
-
return c
|
|
8229
|
+
return coordinateKeyOf(c) === coordinateKeyOf(card.coordinate!)
|
|
8131
8230
|
}).map(event => event.id).sort()
|
|
8132
8231
|
: (eventsByCoordinate.get(coordinateKeyOf(card.coordinate)) ?? []).sort()
|
|
8133
8232
|
const oldRaw = card.metadata.event_refs
|
|
@@ -8372,9 +8471,15 @@ export class KnowledgeRuntime {
|
|
|
8372
8471
|
derive: (sourceIds, context, force, output, checkpoint) => this.deriveProcedures(sourceIds, context, force, output, checkpoint),
|
|
8373
8472
|
groups: async context => {
|
|
8374
8473
|
const snapshot = await this.materializationSnapshot(store, context)
|
|
8375
|
-
return { groups: snapshot.groups, blocked: snapshot.blocked, blockedAmbiguous: snapshot.blockedAmbiguous, blockedAwaitingNormalize: snapshot.blockedAwaitingNormalize, snapshot }
|
|
8474
|
+
return { groups: snapshot.groups, blocked: snapshot.blocked, blockedAmbiguous: snapshot.blockedAmbiguous, blockedAwaitingNormalize: snapshot.blockedAwaitingNormalize, blockedItems: snapshot.blockedItems, snapshot }
|
|
8376
8475
|
},
|
|
8377
8476
|
materialize: (group, context, output, snapshot) => this.materializePipelineGroup(store, group, context, output, snapshot as MaterializationSnapshot),
|
|
8477
|
+
link: async () => {
|
|
8478
|
+
const warnings: string[] = []
|
|
8479
|
+
const finalized = await this.runFinalizeStage(await this.loadCards(), Object.values(await this.readExtractedRelations()).flat(), warnings)
|
|
8480
|
+
if (warnings.length) throw new Error(warnings.join(';'))
|
|
8481
|
+
return `已更新 Wiki 双链与导航:${finalized.rewritten} 份`
|
|
8482
|
+
},
|
|
8378
8483
|
build: async (taskId, checkpoint) => {
|
|
8379
8484
|
let result: Envelope<RebuildReport>
|
|
8380
8485
|
try { result = await this.compileOnce(taskId, 'all', undefined, { extract: false, checkpoint }) }
|
|
@@ -8568,7 +8673,7 @@ export class KnowledgeRuntime {
|
|
|
8568
8673
|
const cards = await this.loadCards()
|
|
8569
8674
|
const relations = Object.values(await this.readExtractedRelations()).flat()
|
|
8570
8675
|
for (const [key, entries] of planRelated(cards, relations, await this.projectedRelated(cards))) {
|
|
8571
|
-
if (entries.length > 0) plan.set(key, entries.map(entry => ({ title: entry.title, entityType: entry.entityType, reason: entry.reason })))
|
|
8676
|
+
if (entries.length > 0) plan.set(key, entries.map(entry => ({ title: entry.title, entityType: entry.entityType, entityId: entry.entityId, reason: entry.reason })))
|
|
8572
8677
|
}
|
|
8573
8678
|
} catch { /* 增强项:算不出来就不给清单 */ }
|
|
8574
8679
|
return plan
|
|
@@ -8591,17 +8696,18 @@ export class KnowledgeRuntime {
|
|
|
8591
8696
|
const evidence = [...item.sources, ...item.timeline.flatMap(step => step.sources)]
|
|
8592
8697
|
const sourceVersions = new Map(evidence.map(e => [e.documentId, db.source(e.documentId)?.version]))
|
|
8593
8698
|
const fingerprint = contentHashOf(JSON.stringify({ contract: DERIVE_CONTRACT_VERSION, item: { ...item, updatedAt: undefined }, sourceVersions: [...sourceVersions], model: { provider: context.model.provider, model: context.model.model, reasoningEffort: context.model.reasoningEffort } }))
|
|
8594
|
-
const receipt = db.raw('SELECT fingerprint, knowledge_ids FROM pipeline_derivations WHERE case_id=?').get(item.id) as { fingerprint: string; knowledge_ids: string } | undefined
|
|
8595
|
-
|
|
8699
|
+
const receipt = db.raw('SELECT fingerprint, response, knowledge_ids FROM pipeline_derivations WHERE case_id=?').get(item.id) as { fingerprint: string; response: string; knowledge_ids: string } | undefined
|
|
8700
|
+
// Replay validated cached output through ingest too: normalization may have withdrawn its support.
|
|
8701
|
+
const cachedResponse = !force && receipt?.fingerprint === fingerprint ? receipt.response : undefined
|
|
8596
8702
|
const auditId = randomUUID()
|
|
8597
8703
|
const request = { system: buildDeriveSystem(), prompt: JSON.stringify({ case: { ...item, version: caseVersion }, sourceEvidence: evidence, sourceVersions: [...sourceVersions] }) }
|
|
8598
|
-
let response = '{"procedures":[],"skipped_cases":[]}'
|
|
8704
|
+
let response = cachedResponse ?? '{"procedures":[],"skipped_cases":[]}'
|
|
8599
8705
|
db.raw('INSERT INTO pipeline_derivation_runs VALUES (?,?,?,?,?,?,?,?,?)').run(auditId, item.id, caseVersion, fingerprint, JSON.stringify(request), '', 'running', null, new Date().toISOString())
|
|
8600
8706
|
try {
|
|
8601
|
-
if (item.timeline.some(step => step.type === 'check' || step.type === 'action')) {
|
|
8707
|
+
if (cachedResponse === undefined && item.timeline.some(step => step.type === 'check' || step.type === 'action')) {
|
|
8602
8708
|
if (!context.model.call) throw new Error(`提炼模型不可用:${context.model.why ?? '请选择模型'}`)
|
|
8603
8709
|
response = await context.model.call({ ...request, sessionId: `knowledge:${this.baseId || 'default'}:derive`, onDelta: output })
|
|
8604
|
-
} else output(`Case ${item.id} 没有已执行过程,不反推 Procedure。\n`)
|
|
8710
|
+
} else if (cachedResponse === undefined) output(`Case ${item.id} 没有已执行过程,不反推 Procedure。\n`)
|
|
8605
8711
|
const parsed = JSON.parse(response.trim().replace(/^```(?:json)?\s*/, '').replace(/\s*```$/, '')) as { procedures?: Array<Record<string, unknown>> }
|
|
8606
8712
|
if (!Array.isArray(parsed.procedures)) throw new Error('Case 提炼返回缺少 procedures 数组')
|
|
8607
8713
|
if (this.closed || db.event(item.id)?.version !== caseVersion || [...sourceVersions].some(([id, version]) => db.source(id)?.version !== version)) throw new Error('Case 或来源在提炼期间变化,请重试')
|
|
@@ -8636,6 +8742,7 @@ export class KnowledgeRuntime {
|
|
|
8636
8742
|
db.raw("UPDATE pipeline_derivation_runs SET response=?,state='succeeded' WHERE id=?").run(response, auditId)
|
|
8637
8743
|
output(`Case ${item.id}:保存 ${ids.length} 条候选 Procedure。\n`)
|
|
8638
8744
|
done++
|
|
8745
|
+
if (cachedResponse !== undefined) reused++
|
|
8639
8746
|
} catch (cause) {
|
|
8640
8747
|
db.raw("UPDATE pipeline_derivation_runs SET response=?,state='failed',error=? WHERE id=?").run(response, cause instanceof Error ? cause.message : String(cause), auditId)
|
|
8641
8748
|
throw cause
|
|
@@ -8651,7 +8758,7 @@ export class KnowledgeRuntime {
|
|
|
8651
8758
|
* 两处必须用同一个表达式,否则「什么算变化」就有两套说法。
|
|
8652
8759
|
*/
|
|
8653
8760
|
private pipelineGroupFingerprint(knowledge: Knowledge[], context: PipelineContext): string {
|
|
8654
|
-
return contentHashOf(JSON.stringify({ knowledge: knowledge.map(row => ({ id: row.id, kind: row.kind ?? 'fact', data: row.data, text: row.text, status: row.status, sources: row.sources.filter(source => !source.withdrawn) })).sort((a, b) => a.id.localeCompare(b.id)), profile: context.profile, organize: context.organize, model: context.organize ? { provider: context.model.provider, model: context.model.model, reasoningEffort: context.model.reasoningEffort } : undefined }))
|
|
8761
|
+
return contentHashOf(JSON.stringify({ contract: 'coordinate-wiki-v2', organizeVersion: context.organize ? ORGANIZE_VERSION : undefined, knowledge: knowledge.map(row => ({ id: row.id, kind: row.kind ?? 'fact', data: row.data, text: row.text, status: row.status, sources: row.sources.filter(source => !source.withdrawn) })).sort((a, b) => a.id.localeCompare(b.id)), profile: context.profile, organize: context.organize, model: context.organize ? { provider: context.model.provider, model: context.model.model, reasoningEffort: context.model.reasoningEffort } : undefined }))
|
|
8655
8762
|
}
|
|
8656
8763
|
|
|
8657
8764
|
/**
|
|
@@ -8688,12 +8795,12 @@ export class KnowledgeRuntime {
|
|
|
8688
8795
|
].filter(Boolean).map(text => `- ${text}[^${row.id}]`).join('\n')
|
|
8689
8796
|
}
|
|
8690
8797
|
|
|
8691
|
-
private async freshGroupFingerprint(coordinate: CardCoordinate, context: PipelineContext, db: KnowledgeDb): Promise<string> {
|
|
8798
|
+
private async freshGroupFingerprint(coordinate: CardCoordinate, wikiKind: WikiKind, context: PipelineContext, db: KnowledgeDb): Promise<string> {
|
|
8692
8799
|
const store = await this.knowledgeStore()
|
|
8693
8800
|
if (!store) return ''
|
|
8694
8801
|
const raw: Knowledge[] = []
|
|
8695
8802
|
for (let offset = 0;; offset += 500) {
|
|
8696
|
-
const page = await store.list({ objectId: coordinate.objectId, kind:
|
|
8803
|
+
const page = await store.list({ objectId: coordinate.objectId, kind: wikiKind === 'way' ? 'procedure' : 'fact', actionId: coordinate.actionId, sceneId: coordinate.sceneId, excludeWithdrawn: true, limit: 500, offset })
|
|
8697
8804
|
raw.push(...page.knowledge)
|
|
8698
8805
|
if (offset + page.knowledge.length >= page.total) break
|
|
8699
8806
|
}
|
|
@@ -8705,7 +8812,7 @@ export class KnowledgeRuntime {
|
|
|
8705
8812
|
if (source) bodies.set(id, { version: source.version, content: source.record.content })
|
|
8706
8813
|
}
|
|
8707
8814
|
const rows = raw
|
|
8708
|
-
.filter(row => row.status !== 'deprecated' && (row.kind !== 'procedure' || (row.data?.procedure?.completeness === 'complete' && row.data.procedure.method.length > 0 && row.status !== 'disputed' && row.data.procedure.caseRefs.every(ref => db.event(ref.id)?.version === ref.version) && !row.sources.some(source => source.toVersion !== undefined && db.source(source.documentId)?.version !== source.toVersion))))
|
|
8815
|
+
.filter(row => row.status !== 'deprecated' && (row.kind !== 'procedure' || (row.data?.procedure?.completeness === 'complete' && row.data.procedure.method.length > 0 && row.status !== 'disputed' && row.data.procedure.caseRefs.every(ref => db.event(ref.id)?.version === ref.version) && !row.sources.some(source => !source.withdrawn && source.toVersion !== undefined && db.source(source.documentId)?.version !== source.toVersion))))
|
|
8709
8816
|
.map(row => assessKnowledgeEvidence(row, db, bodies))
|
|
8710
8817
|
.map(row => ({ ...row, sources: row.sources.filter(source => !source.withdrawn && source.evidenceStatus !== 'missing') }))
|
|
8711
8818
|
.filter(row => row.sources.length > 0)
|
|
@@ -8726,12 +8833,13 @@ export class KnowledgeRuntime {
|
|
|
8726
8833
|
*/
|
|
8727
8834
|
const plans = options.plans !== false
|
|
8728
8835
|
const unresolved = store.unresolvedKnowledge()
|
|
8729
|
-
const grouped = new Map<string, { coordinate: CardCoordinate; knowledge: Knowledge[] }>()
|
|
8836
|
+
const grouped = new Map<string, { coordinate: CardCoordinate; wikiKind: WikiKind; knowledge: Knowledge[] }>()
|
|
8730
8837
|
let blockedAmbiguous = 0
|
|
8731
8838
|
let blockedAwaitingNormalize = 0
|
|
8732
|
-
const
|
|
8733
|
-
|
|
8734
|
-
|
|
8839
|
+
const blockedItems: MaterializationSnapshot['blockedItems'] = []
|
|
8840
|
+
const groupFor = (coordinate: CardCoordinate, wikiKind: WikiKind) => {
|
|
8841
|
+
const key = wikiGroupKey(wikiKind, coordinate)
|
|
8842
|
+
if (!grouped.has(key)) grouped.set(key, { coordinate, wikiKind, knowledge: [] })
|
|
8735
8843
|
return grouped.get(key)!
|
|
8736
8844
|
}
|
|
8737
8845
|
const caseStore = await this.storeHandle()
|
|
@@ -8739,24 +8847,29 @@ export class KnowledgeRuntime {
|
|
|
8739
8847
|
if (row.kind === 'case') continue
|
|
8740
8848
|
const coordinate = wikiProjection(row.kind ?? 'fact', row)
|
|
8741
8849
|
const method = row.data?.procedure
|
|
8742
|
-
if (!coordinate || (row.kind === 'procedure' && (!method || method.completeness !== 'complete' || !method.method.length || row.status === 'disputed' || method.caseRefs.some(ref => caseStore?.event(ref.id)?.version !== ref.version) || row.sources.some(source => source.toVersion !== undefined && caseStore?.source(source.documentId)?.version !== source.toVersion)))) {
|
|
8743
|
-
|
|
8850
|
+
if (!coordinate || (row.kind === 'procedure' && (!method || method.completeness !== 'complete' || !method.method.length || row.status === 'disputed' || method.caseRefs.some(ref => caseStore?.event(ref.id)?.version !== ref.version) || row.sources.some(source => source.toVersion !== undefined && caseStore?.source(source.documentId)?.version !== source.toVersion)))) {
|
|
8851
|
+
blockedAmbiguous += 1
|
|
8852
|
+
blockedItems.push({ knowledgeId: row.id, sourceIds: [...new Set(row.sources.map(source => source.documentId).filter(Boolean))], ...(coordinate ? { groupKey: wikiGroupKey(row.kind === 'procedure' ? 'way' : 'fact', coordinate) } : {}) })
|
|
8853
|
+
continue
|
|
8854
|
+
}
|
|
8855
|
+
groupFor(coordinate, row.kind === 'procedure' ? 'way' : 'fact').knowledge.push(row)
|
|
8744
8856
|
}
|
|
8745
8857
|
const db = await this.storeHandle()
|
|
8746
8858
|
const events = new Map<string, EventEntity[]>()
|
|
8747
8859
|
for (const event of db?.events() ?? []) {
|
|
8748
8860
|
const coordinate = event.coordinateIds
|
|
8749
8861
|
if (!coordinate.objectId) continue
|
|
8750
|
-
for (const
|
|
8751
|
-
|
|
8752
|
-
const key = coordinateKeyOf(projection)
|
|
8862
|
+
for (const kind of ['fact', 'way'] as const) {
|
|
8863
|
+
const key = wikiGroupKey(kind, coordinate)
|
|
8753
8864
|
const bucket = events.get(key) ?? []
|
|
8754
8865
|
bucket.push(event)
|
|
8755
8866
|
events.set(key, bucket)
|
|
8756
8867
|
}
|
|
8757
8868
|
}
|
|
8758
8869
|
const cards = await this.loadCards()
|
|
8759
|
-
const
|
|
8870
|
+
const groupKeyOf = (card: KnowledgeCard): string => card.coordinate && (card.metadata.wiki_kind === 'fact' || card.metadata.wiki_kind === 'way')
|
|
8871
|
+
? wikiGroupKey(card.metadata.wiki_kind, card.coordinate) : card.coordinate ? coordinateKeyOf(card.coordinate) : ''
|
|
8872
|
+
const byCoordinate = new Map(cards.filter(card => card.coordinate).map(card => [groupKeyOf(card), card]))
|
|
8760
8873
|
const groups: MaterializationGroup[] = []
|
|
8761
8874
|
const knowledge = new Map<string, Knowledge[]>()
|
|
8762
8875
|
for (const [key, value] of grouped) {
|
|
@@ -8770,11 +8883,13 @@ export class KnowledgeRuntime {
|
|
|
8770
8883
|
const needsNormalization = this.needsNormalizationForGroup(store, value.knowledge, unresolved, db)
|
|
8771
8884
|
if (needsNormalization) blockedAwaitingNormalize += value.knowledge.length
|
|
8772
8885
|
knowledge.set(key, value.knowledge)
|
|
8773
|
-
groups.push({ key, wikiKind: value.
|
|
8886
|
+
groups.push({ key, wikiKind: value.wikiKind, title: existing?.title ?? `${names.object}${names.action ? ` · ${names.action}` : ''}${names.scene ? ` · ${names.scene}` : ''} · ${value.wikiKind === 'way' ? 'Wiki Way' : 'Wiki Fact'}`, coordinate: value.coordinate, knowledgeCount: value.knowledge.length, eventCount: events.get(key)?.length ?? 0, sourceIds: contributingSources, ...(existing ? { cardId: existing.entityId } : {}), state: needsNormalization ? 'blocked' : current ? 'current' : existing ? 'changed' : 'new', ...(needsNormalization ? { reason: '分组中的来源已变化、新抽取待归一或坐标有歧义,请先完成归一' } : {}), fingerprint })
|
|
8774
8887
|
}
|
|
8775
8888
|
for (const card of cards) {
|
|
8776
|
-
|
|
8777
|
-
|
|
8889
|
+
const key = groupKeyOf(card)
|
|
8890
|
+
const legacyReceipt = card.coordinate ? store.materialization(coordinateKeyOf(card.coordinate)) : undefined
|
|
8891
|
+
if (!card.coordinate || grouped.has(key) || (!store.materialization(key) && !legacyReceipt && card.metadata.pipeline_materialization !== true) || card.status === 'deprecated') continue
|
|
8892
|
+
groups.push({ key, wikiKind: card.metadata.wiki_kind === 'way' ? 'way' : 'fact', title: card.title, coordinate: card.coordinate, knowledgeCount: 0, eventCount: 0, sourceIds: card.sourceRefs, cardId: card.entityId, state: card.sourceRefs.some(id => unresolved.sources.has(id)) ? 'blocked' : 'withdrawn', ...(card.sourceRefs.some(id => unresolved.sources.has(id)) ? { reason: '来源中的坐标仍有歧义,请先完成归一' } : {}), fingerprint: 'withdrawn' })
|
|
8778
8893
|
}
|
|
8779
8894
|
const sources = plans ? (await this.loadSources()).map(source => ({ sourceId: source.sourceId, content: source.content })) : []
|
|
8780
8895
|
const imagePlan = plans
|
|
@@ -8805,7 +8920,7 @@ export class KnowledgeRuntime {
|
|
|
8805
8920
|
return {
|
|
8806
8921
|
groups: groups.sort((a, b) => a.title.localeCompare(b.title)),
|
|
8807
8922
|
blocked: blockedAmbiguous + blockedAwaitingNormalize,
|
|
8808
|
-
blockedAmbiguous, blockedAwaitingNormalize,
|
|
8923
|
+
blockedAmbiguous, blockedAwaitingNormalize, blockedItems,
|
|
8809
8924
|
knowledge, events, sources, images: imagePlan.byCard, related: relatedByKey,
|
|
8810
8925
|
}
|
|
8811
8926
|
}
|
|
@@ -8836,7 +8951,7 @@ export class KnowledgeRuntime {
|
|
|
8836
8951
|
const statusBeforeDeprecate = (await this.readCard(existing.entityId))?.status
|
|
8837
8952
|
if (statusBeforeDeprecate === undefined) throw new Error('卡片在处理期间已被删除,本次物化未提交,请刷新后重试')
|
|
8838
8953
|
if (statusBeforeDeprecate !== existing.status) throw new Error(`卡片在处理期间被人工改动(状态 ${existing.status} → ${statusBeforeDeprecate}),本次物化未提交,请重试`)
|
|
8839
|
-
const written = await this.writeCard({ ...existing, status: 'deprecated', origin: 'compile' })
|
|
8954
|
+
const written = await this.writeCard({ ...existing, metadata: { ...existing.metadata, wiki_deprecation_origin: existing.status === 'deprecated' && existing.metadata.wiki_deprecation_origin !== 'withdrawal' ? 'manual' : 'withdrawal' }, status: 'deprecated', origin: 'compile' })
|
|
8840
8955
|
if (!written.ok) throw new Error(written.error.message)
|
|
8841
8956
|
store.materialized(group.key, group.fingerprint, existing.entityId)
|
|
8842
8957
|
// 停用也是一次物化:卡片从可召回里退出,这一步发生过,就该留一条轨迹。
|
|
@@ -8855,7 +8970,7 @@ export class KnowledgeRuntime {
|
|
|
8855
8970
|
let content = knowledge.map(row => `## ${row.data?.procedure?.question ?? row.text.split('\n')[0]!.slice(0, 60)}\n\n${this.organizeKnowledgeText(row)}[^${row.id}]${boundaries.get(row.id) ? `\n\n### 适用条件与边界\n\n${boundaries.get(row.id)}` : ''}`).join('\n\n')
|
|
8856
8971
|
let summary = knowledge[0]?.text ?? ''
|
|
8857
8972
|
let placements: CardKnowledgeRow[] = knowledge.map((row, index) => ({ knowledgeId: row.id, section: row.data?.procedure?.question ?? row.text.split('\n')[0]!.slice(0, 60), ordinal: index + 1, origin: 'compiler' }))
|
|
8858
|
-
let metadata: Record<string, unknown> = { ...(existing?.metadata ?? {}), pipeline_materialization: true, wiki_kind: group.wikiKind ?? (
|
|
8973
|
+
let metadata: Record<string, unknown> = { ...(existing?.metadata ?? {}), pipeline_materialization: true, wiki_kind: group.wikiKind ?? 'fact', wiki_coordinate_version: 2, knowledge_versions: Object.fromEntries(knowledge.map(row => [row.id, row.version])) }
|
|
8859
8974
|
const entityId = existing?.entityId ?? `coord-${hash(group.key)}`
|
|
8860
8975
|
const images = snapshot.images.get(group.key) ?? []
|
|
8861
8976
|
const related = existing && snapshot.related.has(group.key) ? { related: snapshot.related.get(group.key)! } : {}
|
|
@@ -8880,7 +8995,7 @@ export class KnowledgeRuntime {
|
|
|
8880
8995
|
* 「只有知识行变了」;这条按坐标重算整组指纹,能把那种变化也挡住。
|
|
8881
8996
|
*/
|
|
8882
8997
|
if (this.needsNormalizationForGroup(store, knowledge, store.unresolvedKnowledge(), db)) throw new Error('卡片组织期间事实发生变化,结果未提交,请重试')
|
|
8883
|
-
if (await this.freshGroupFingerprint(group.coordinate, context, db) !== group.fingerprint) throw new Error('卡片分组在处理期间事实发生了变化,结果未提交,请重试')
|
|
8998
|
+
if (await this.freshGroupFingerprint(group.coordinate, group.wikiKind ?? 'fact', context, db) !== group.fingerprint) throw new Error('卡片分组在处理期间事实发生了变化,结果未提交,请重试')
|
|
8884
8999
|
if (this.closed) throw new Error('知识库已关闭,本次物化未提交')
|
|
8885
9000
|
/**
|
|
8886
9001
|
* 状态必须**在写之前**重读,并且和读到的旧值比对。
|
|
@@ -8896,7 +9011,8 @@ export class KnowledgeRuntime {
|
|
|
8896
9011
|
if (statusAtWrite !== existing?.status) {
|
|
8897
9012
|
throw new Error(`卡片在处理期间被人工改动(状态 ${existing?.status ?? '未知'} → ${statusAtWrite ?? '未知'}),本次物化未提交,请重试`)
|
|
8898
9013
|
}
|
|
8899
|
-
const
|
|
9014
|
+
const nextStatus = statusAtWrite === 'deprecated' && existing?.metadata.wiki_deprecation_origin === 'withdrawal' ? 'unreviewed' : statusAtWrite ?? 'unreviewed'
|
|
9015
|
+
const written = await this.writeCard({ entityType, entityId, title, content, summary, sourceRefs: group.sourceIds, status: nextStatus, coordinate: group.coordinate, placements, metadata, origin: 'compile' })
|
|
8900
9016
|
if (!written.ok) throw new Error(written.error.message)
|
|
8901
9017
|
db.setCardEvents(entityId, eventIds)
|
|
8902
9018
|
store.materialized(group.key, group.fingerprint, entityId)
|
|
@@ -8915,7 +9031,7 @@ export class KnowledgeRuntime {
|
|
|
8915
9031
|
card: {
|
|
8916
9032
|
id: entityId,
|
|
8917
9033
|
title,
|
|
8918
|
-
status:
|
|
9034
|
+
status: nextStatus,
|
|
8919
9035
|
characters: content.length,
|
|
8920
9036
|
events: eventIds.length,
|
|
8921
9037
|
images: images.length,
|
|
@@ -8939,7 +9055,9 @@ export class KnowledgeRuntime {
|
|
|
8939
9055
|
const db = await this.derivedHandle()
|
|
8940
9056
|
const manifest = db?.artifact('build-manifest.json')
|
|
8941
9057
|
const hashes = manifest ? (JSON.parse(manifest) as { cardHashes?: Record<string, string> }).cardHashes ?? {} : {}
|
|
8942
|
-
const
|
|
9058
|
+
const allCards = await this.loadCards()
|
|
9059
|
+
const unavailable = await this.wikiUnavailability(allCards)
|
|
9060
|
+
const cards = allCards.filter(card => !card.metadata.wiki_kind || !unavailable.has(cardKey(card)))
|
|
8943
9061
|
const indexPending = cards.some(card => hashes[`${card.entityType}:${card.entityId}`] !== this.cardHashOf(card)) || Object.keys(hashes).length !== cards.length
|
|
8944
9062
|
return { sources: rows, groups, blocked, pendingNew: groups.filter(group => group.state === 'new').length, pendingUpdated: groups.filter(group => group.state === 'changed').length, pendingWithdrawn: groups.filter(group => group.state === 'withdrawn').length, indexPending }
|
|
8945
9063
|
}
|
|
@@ -9165,6 +9283,8 @@ export class KnowledgeRuntime {
|
|
|
9165
9283
|
}
|
|
9166
9284
|
this.publication.artifacts.set(COMPILE_SNAPSHOT, JSON.stringify(snapshot))
|
|
9167
9285
|
await this.checkpointCompileTask(runId)
|
|
9286
|
+
const changedWiki = await this.wikiUnavailability(snapshot.cards, new Map(snapshot.placements))
|
|
9287
|
+
if (snapshot.cards.some(card => card.metadata.wiki_kind && changedWiki.has(cardKey(card)))) throw new Error('WIKI_CHANGED_DURING_BUILD: Wiki 的支撑知识在构建期间变化,请重新组织并构建索引')
|
|
9168
9288
|
if (JSON.stringify({ config: this.config, fingerprint: this.embeddingFingerprint() }) !== configAtStart) throw new Error('COMPILE_CONFIG_CHANGED: 编译期间设置已变化,请重新编译')
|
|
9169
9289
|
await options.checkpoint?.()
|
|
9170
9290
|
db.publish(this.publication)
|
|
@@ -9173,6 +9293,8 @@ export class KnowledgeRuntime {
|
|
|
9173
9293
|
return result
|
|
9174
9294
|
} finally {
|
|
9175
9295
|
this.publication = undefined
|
|
9296
|
+
// Compilation filters unavailable projections; the browsing cache still represents all saved Wiki.
|
|
9297
|
+
this.cards = await this.loadCards()
|
|
9176
9298
|
}
|
|
9177
9299
|
}
|
|
9178
9300
|
|
|
@@ -9436,6 +9558,9 @@ export class KnowledgeRuntime {
|
|
|
9436
9558
|
await this.checkpointCompileTask(runId)
|
|
9437
9559
|
// 相关 metadata 或旧正文清理刚落盘:stage 2 必须看到最新卡片。
|
|
9438
9560
|
if (finalize.rewritten > 0) this.cards = await this.loadCards()
|
|
9561
|
+
const unavailableWiki = await this.wikiUnavailability(this.cards)
|
|
9562
|
+
for (const card of this.cards) if (card.metadata.wiki_kind && unavailableWiki.has(cardKey(card))) warnings.push(`Wiki「${card.title}」未进入索引:${unavailableWiki.get(cardKey(card))}`)
|
|
9563
|
+
this.cards = this.cards.filter(card => !card.metadata.wiki_kind || !unavailableWiki.has(cardKey(card)))
|
|
9439
9564
|
/**
|
|
9440
9565
|
* 索引页也是这次编译的产物,所以它变了也算「语料变了」。
|
|
9441
9566
|
*
|
|
@@ -9848,12 +9973,11 @@ export class KnowledgeRuntime {
|
|
|
9848
9973
|
}
|
|
9849
9974
|
|
|
9850
9975
|
/**
|
|
9851
|
-
* Coordinate
|
|
9976
|
+
* Coordinate 路:解析 Query 中出现的 Object / Action / Scene,再把命中的卡片拆成
|
|
9852
9977
|
* Section 候选。它和 FTS / Vector 使用同一个 `unitId` 粒度,后面才能统一 RRF 与重排。
|
|
9853
9978
|
*
|
|
9854
9979
|
* 这里不让模型自由生成实体名:注册表负责 canonical / alias 命中,只有真实存在的
|
|
9855
|
-
* 坐标 id
|
|
9856
|
-
* 后续接入模型消歧时不需要改变召回合同。
|
|
9980
|
+
* 坐标 id 才能进入候选。未解析的原文继续交给关键词、向量路线与统一重排。
|
|
9857
9981
|
*/
|
|
9858
9982
|
private async coordinateRoute(
|
|
9859
9983
|
query: string,
|
|
@@ -10443,18 +10567,8 @@ export class KnowledgeRuntime {
|
|
|
10443
10567
|
for (const key of liveKeys) {
|
|
10444
10568
|
if (snapshotCardByKey.get(key)?.sourceRefs.some(ref => !liveSourceIds.has(ref))) liveKeys.delete(key)
|
|
10445
10569
|
}
|
|
10446
|
-
|
|
10447
|
-
|
|
10448
|
-
const unsafeProcedures = new Set(snapshot.knowledge.filter(row => {
|
|
10449
|
-
if (row.kind !== 'procedure') return false
|
|
10450
|
-
const current = truthAtRead?.raw('SELECT status,head_version FROM knowledge WHERE id=?').get(row.id) as { status: string; head_version: number } | undefined
|
|
10451
|
-
return !current || current.status === 'deprecated' || current.status === 'disputed' || current.head_version !== row.version
|
|
10452
|
-
|| !row.data?.procedure || row.data.procedure.completeness !== 'complete'
|
|
10453
|
-
|| row.data.procedure.caseRefs.some(ref => truthAtRead?.event(ref.id)?.version !== ref.version)
|
|
10454
|
-
|| row.sources.some(source => source.toVersion !== undefined && truthAtRead?.source(source.documentId)?.version !== source.toVersion)
|
|
10455
|
-
}).map(row => row.id))
|
|
10456
|
-
const unsafeCardIds = new Set(snapshot.placements.filter(([, placements]) => placements.some(p => unsafeProcedures.has(p.knowledgeId))).map(([id]) => id))
|
|
10457
|
-
for (const key of liveKeys) if (unsafeCardIds.has(snapshotCardByKey.get(key)?.entityId ?? '')) liveKeys.delete(key)
|
|
10570
|
+
const unavailableWiki = await this.wikiUnavailability(snapshot.cards, new Map(snapshot.placements))
|
|
10571
|
+
for (const key of unavailableWiki.keys()) liveKeys.delete(key)
|
|
10458
10572
|
let publishedGraph = visibleGraph(snapshot.graph, truthAtRead ? liveGraphKeys(truthAtRead) : new Set())
|
|
10459
10573
|
const currentByKey = new Map(liveCards.map(card => [cardKey(card), card]))
|
|
10460
10574
|
let cards: KnowledgeCard[] = snapshot.cards.filter(card => liveKeys.has(cardKey(card))).map(card => {
|
|
@@ -10575,9 +10689,6 @@ export class KnowledgeRuntime {
|
|
|
10575
10689
|
} else {
|
|
10576
10690
|
const rerankQuery = [
|
|
10577
10691
|
query,
|
|
10578
|
-
...(queryAnalysis.intent.id !== 'unknown' && queryAnalysis.intent.confidence >= 0.7
|
|
10579
|
-
? [`意图:${queryAnalysis.intent.id}`]
|
|
10580
|
-
: []),
|
|
10581
10692
|
...(['object', 'action', 'scene'] as const).flatMap(kind => {
|
|
10582
10693
|
const values = queryAnalysis.axes[kind].map(item => item.name).slice(0, 3)
|
|
10583
10694
|
return values.length > 0 ? [`${kind}:${values.join('、')}`] : []
|
|
@@ -10720,8 +10831,7 @@ export class KnowledgeRuntime {
|
|
|
10720
10831
|
/**
|
|
10721
10832
|
* 上下文预算(0050):装箱 + 溢出。
|
|
10722
10833
|
*
|
|
10723
|
-
* 让位顺序写死在 `packContext`
|
|
10724
|
-
* 再给扩展卡片的标题 + 摘要 + 关系,最后才给扩展卡片的正文。
|
|
10834
|
+
* 让位顺序写死在 `packContext` 里:优先交付命中的完整 Wiki,再交付扩展 Wiki。
|
|
10725
10835
|
*/
|
|
10726
10836
|
/**
|
|
10727
10837
|
* 每次调用可以覆盖预算(0050):指路行里就写着「读不到就调大预算重新召回」,
|
|
@@ -10729,6 +10839,14 @@ export class KnowledgeRuntime {
|
|
|
10729
10839
|
*/
|
|
10730
10840
|
const maxChars = options.contextMaxChars ?? this.config.contextMaxChars
|
|
10731
10841
|
const packed = await this.packContext({ items, maxChars, recallId })
|
|
10842
|
+
const deliveredKeys = new Set(packed.items.flatMap(item => item.wiki ? [cardKey(item.wiki)] : []))
|
|
10843
|
+
// Diagnostic HTTP requests may inspect unbudgeted candidate text. Normal delivery cannot leak it.
|
|
10844
|
+
const deliveredHits = options.detail ? hits : hits.map(hit => {
|
|
10845
|
+
if (deliveredKeys.has(wikiKey(hit))) return hit
|
|
10846
|
+
const { evidence: _evidence, knowledge: _knowledge, ...reference } = hit
|
|
10847
|
+
return { ...reference, content: '' }
|
|
10848
|
+
})
|
|
10849
|
+
const deliveredWiki = options.detail ? wiki : wiki.filter(card => deliveredKeys.has(cardKey(card)))
|
|
10732
10850
|
/** 哪些扩展卡片真的进了上下文(被预算挡住的也如实报出来)。 */
|
|
10733
10851
|
expansion.included = packed.items
|
|
10734
10852
|
.filter(item => item.reason === 'expansion' && item.wiki !== null)
|
|
@@ -10789,7 +10907,7 @@ export class KnowledgeRuntime {
|
|
|
10789
10907
|
}
|
|
10790
10908
|
|
|
10791
10909
|
const data: RecallResult = {
|
|
10792
|
-
hits, context: packed.items, wiki, graph, backend, routes: ranRoutes, recallMode, rerank, recallTopK, candidatePool, fusedCount, contextTopN,
|
|
10910
|
+
hits: deliveredHits, context: packed.items, wiki: deliveredWiki, graph, backend, routes: ranRoutes, recallMode, rerank, recallTopK, candidatePool, fusedCount, contextTopN,
|
|
10793
10911
|
queryAnalysis,
|
|
10794
10912
|
insufficientEvidence, diagnostics, recallId, timings,
|
|
10795
10913
|
expansion, budget: packed.report,
|