dsh-data-domain-knowledge 0.1.0-alpha.13 → 0.1.0-alpha.15
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 +89 -56
- package/cordis.patch.yml +36 -129
- package/eval/benchmark-extraction-format.mjs +20 -0
- package/lib/client.js +3262 -4142
- package/lib/doc-page.js +1 -1
- package/lib/pages.js +1147 -364
- package/lib/types/axis-registry.d.ts +8 -1
- package/lib/types/axis-registry.d.ts.map +1 -1
- package/lib/types/axis-registry.js +62 -7
- package/lib/types/axis-registry.js.map +1 -1
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +3 -0
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +9 -0
- package/lib/types/convert/ingest.d.ts.map +1 -1
- package/lib/types/convert/ingest.js +68 -9
- package/lib/types/convert/ingest.js.map +1 -1
- package/lib/types/diagnosis/cards.d.ts +7 -3
- package/lib/types/diagnosis/cards.d.ts.map +1 -1
- package/lib/types/diagnosis/cards.js +13 -8
- package/lib/types/diagnosis/cards.js.map +1 -1
- package/lib/types/diagnosis/run.d.ts +4 -13
- package/lib/types/diagnosis/run.d.ts.map +1 -1
- package/lib/types/diagnosis/run.js +26 -16
- package/lib/types/diagnosis/run.js.map +1 -1
- package/lib/types/diagnosis/schema.js +2 -2
- package/lib/types/diagnosis/schema.js.map +1 -1
- package/lib/types/diagnosis/seed-source.d.ts +1 -1
- package/lib/types/diagnosis/seed-source.d.ts.map +1 -1
- package/lib/types/diagnosis/seed-source.js +5 -4
- package/lib/types/diagnosis/seed-source.js.map +1 -1
- package/lib/types/diagnosis/seed.d.ts +4 -4
- package/lib/types/diagnosis/seed.d.ts.map +1 -1
- package/lib/types/diagnosis/seed.js +6 -8
- package/lib/types/diagnosis/seed.js.map +1 -1
- package/lib/types/diagnosis/store.d.ts +18 -17
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +94 -66
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +10 -16
- package/lib/types/diagnosis/types.d.ts.map +1 -1
- package/lib/types/diagnosis/types.js.map +1 -1
- package/lib/types/environment/dsh-python.d.ts +77 -0
- package/lib/types/environment/dsh-python.d.ts.map +1 -0
- package/lib/types/environment/dsh-python.js +139 -0
- package/lib/types/environment/dsh-python.js.map +1 -0
- package/lib/types/environment/install.d.ts +9 -1
- package/lib/types/environment/install.d.ts.map +1 -1
- package/lib/types/environment/install.js +149 -24
- package/lib/types/environment/install.js.map +1 -1
- package/lib/types/environment/model-catalog.d.ts +34 -2
- package/lib/types/environment/model-catalog.d.ts.map +1 -1
- package/lib/types/environment/model-catalog.js +40 -2
- package/lib/types/environment/model-catalog.js.map +1 -1
- package/lib/types/environment/service.d.ts +13 -3
- package/lib/types/environment/service.d.ts.map +1 -1
- package/lib/types/environment/service.js +127 -23
- package/lib/types/environment/service.js.map +1 -1
- package/lib/types/environment/types.d.ts +9 -0
- package/lib/types/environment/types.d.ts.map +1 -1
- package/lib/types/events.d.ts +2 -0
- package/lib/types/events.d.ts.map +1 -1
- package/lib/types/events.js +13 -0
- package/lib/types/events.js.map +1 -1
- package/lib/types/extract-contract.d.ts +1 -1
- package/lib/types/extract-contract.d.ts.map +1 -1
- package/lib/types/extract-contract.js +34 -31
- package/lib/types/extract-contract.js.map +1 -1
- package/lib/types/extract-model.d.ts +2 -0
- package/lib/types/extract-model.d.ts.map +1 -1
- package/lib/types/extract-model.js.map +1 -1
- package/lib/types/extract-table-contract.d.ts +3 -0
- package/lib/types/extract-table-contract.d.ts.map +1 -0
- package/lib/types/extract-table-contract.js +30 -0
- package/lib/types/extract-table-contract.js.map +1 -0
- package/lib/types/extract-table.d.ts +8 -0
- package/lib/types/extract-table.d.ts.map +1 -0
- package/lib/types/extract-table.js +151 -0
- package/lib/types/extract-table.js.map +1 -0
- package/lib/types/extraction-runs.d.ts +30 -6
- package/lib/types/extraction-runs.d.ts.map +1 -1
- package/lib/types/extraction-runs.js +69 -8
- package/lib/types/extraction-runs.js.map +1 -1
- package/lib/types/fs-atomic.d.ts +0 -7
- package/lib/types/fs-atomic.d.ts.map +1 -1
- package/lib/types/fs-atomic.js +17 -2
- package/lib/types/fs-atomic.js.map +1 -1
- package/lib/types/images.d.ts +1 -1
- package/lib/types/images.d.ts.map +1 -1
- package/lib/types/images.js +2 -2
- package/lib/types/images.js.map +1 -1
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +232 -50
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/scope.d.ts.map +1 -1
- package/lib/types/intent/scope.js +1 -0
- package/lib/types/intent/scope.js.map +1 -1
- package/lib/types/intent/tools.d.ts +19 -0
- package/lib/types/intent/tools.d.ts.map +1 -1
- package/lib/types/intent/tools.js +37 -1
- package/lib/types/intent/tools.js.map +1 -1
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +5 -16
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/knowledge.d.ts +3 -0
- package/lib/types/knowledge.d.ts.map +1 -1
- package/lib/types/knowledge.js +21 -5
- package/lib/types/knowledge.js.map +1 -1
- package/lib/types/materialize-snapshot.d.ts +49 -0
- package/lib/types/materialize-snapshot.d.ts.map +1 -0
- package/lib/types/materialize-snapshot.js +2 -0
- package/lib/types/materialize-snapshot.js.map +1 -0
- package/lib/types/model-text.d.ts +1 -1
- package/lib/types/model-text.d.ts.map +1 -1
- package/lib/types/model-text.js +5 -4
- package/lib/types/model-text.js.map +1 -1
- package/lib/types/oplog.d.ts.map +1 -1
- package/lib/types/oplog.js +7 -0
- package/lib/types/oplog.js.map +1 -1
- package/lib/types/pipeline/engine.d.ts +60 -0
- package/lib/types/pipeline/engine.d.ts.map +1 -0
- package/lib/types/pipeline/engine.js +338 -0
- package/lib/types/pipeline/engine.js.map +1 -0
- package/lib/types/pipeline/store.d.ts +73 -0
- package/lib/types/pipeline/store.d.ts.map +1 -0
- package/lib/types/pipeline/store.js +143 -0
- package/lib/types/pipeline/store.js.map +1 -0
- package/lib/types/pipeline/types.d.ts +101 -0
- package/lib/types/pipeline/types.d.ts.map +1 -0
- package/lib/types/pipeline/types.js +5 -0
- package/lib/types/pipeline/types.js.map +1 -0
- package/lib/types/present.d.ts +2 -0
- package/lib/types/present.d.ts.map +1 -1
- package/lib/types/present.js +9 -4
- package/lib/types/present.js.map +1 -1
- package/lib/types/retention.d.ts +39 -0
- package/lib/types/retention.d.ts.map +1 -0
- package/lib/types/retention.js +42 -0
- package/lib/types/retention.js.map +1 -0
- package/lib/types/runtime.d.ts +115 -5
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +862 -132
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/source-evidence.d.ts +9 -1
- package/lib/types/source-evidence.d.ts.map +1 -1
- package/lib/types/source-evidence.js +13 -5
- package/lib/types/source-evidence.js.map +1 -1
- package/lib/types/store/axis-references.js +3 -3
- package/lib/types/store/axis-references.js.map +1 -1
- package/lib/types/store/derived.d.ts +1 -1
- package/lib/types/store/derived.d.ts.map +1 -1
- package/lib/types/store/derived.js +41 -7
- package/lib/types/store/derived.js.map +1 -1
- package/lib/types/store/knowledge-db.d.ts +18 -0
- package/lib/types/store/knowledge-db.d.ts.map +1 -1
- package/lib/types/store/knowledge-db.js +111 -6
- package/lib/types/store/knowledge-db.js.map +1 -1
- package/lib/types/store/migrate.js +1 -1
- package/lib/types/store/migrate.js.map +1 -1
- package/lib/types/trajectory-types.d.ts +172 -0
- package/lib/types/trajectory-types.d.ts.map +1 -0
- package/lib/types/trajectory-types.js +36 -0
- package/lib/types/trajectory-types.js.map +1 -0
- package/lib/types/types.d.ts +3 -4
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js.map +1 -1
- package/lib/types/wiki-finalize.d.ts +16 -0
- package/lib/types/wiki-finalize.d.ts.map +1 -1
- package/lib/types/wiki-finalize.js +26 -0
- package/lib/types/wiki-finalize.js.map +1 -1
- package/lib/types/wiki-materialize.d.ts.map +1 -1
- package/lib/types/wiki-materialize.js +3 -15
- package/lib/types/wiki-materialize.js.map +1 -1
- package/lib/types/wiki-trial-types.d.ts +3 -0
- package/lib/types/wiki-trial-types.d.ts.map +1 -1
- package/lib/types/wiki-trial.d.ts +2 -0
- package/lib/types/wiki-trial.d.ts.map +1 -1
- package/lib/types/wiki-trial.js +31 -7
- package/lib/types/wiki-trial.js.map +1 -1
- package/package.json +6 -3
- package/preset/tita-data-agent/agent.cordis.yml +36 -129
- package/src/axis-registry.ts +57 -9
- package/src/client/AttributionEventGuide.tsx +10 -9
- package/src/client/CompileDialogs.tsx +10 -2
- package/src/client/EnvironmentSetup.tsx +76 -16
- package/src/client/ExpansionGraph.tsx +13 -7
- package/src/client/ExtractionTasksPage.tsx +543 -156
- package/src/client/ImportFilesDialog.tsx +2 -13
- package/src/client/IntentTab.tsx +3 -1
- package/src/client/KnowledgeBaseTab.tsx +139 -122
- package/src/client/KnowledgeLayerSection.tsx +19 -2
- package/src/client/KnowledgePanel.tsx +17 -3
- package/src/client/ListPager.tsx +15 -5
- package/src/client/MaterializeTrajectoryView.tsx +124 -0
- package/src/client/NodeSettings.tsx +90 -25
- package/src/client/NormalizeTrajectoryView.tsx +136 -0
- package/src/client/PipelineNotice.tsx +26 -0
- package/src/client/RecallPanel.tsx +55 -25
- package/src/client/TabGuide.tsx +4 -4
- package/src/client/TrialReportView.tsx +61 -16
- package/src/client/TypeDefinitionDrawer.tsx +33 -9
- package/src/client/WikiCardPreview.tsx +43 -10
- package/src/client/WikiFlowPanel.tsx +4 -4
- package/src/client/architecture.tsx +1 -1
- package/src/client/attribution/AttributionTab.tsx +37 -36
- package/src/client/attribution/CaseView.tsx +3 -1
- package/src/client/attribution/EventView.tsx +14 -4
- package/src/client/attribution/RunView.tsx +2 -2
- package/src/client/attribution/ViewSwitch.tsx +20 -5
- package/src/client/attribution/graph-options.ts +38 -31
- package/src/client/attribution/model.ts +16 -9
- package/src/client/graph/palette.ts +165 -0
- package/src/client/pages.css +224 -28
- package/src/client/pages.tsx +33 -11
- package/src/client/styles.css +61 -6
- package/src/client/wiki-trial.ts +30 -1
- package/src/convert/host.ts +3 -0
- package/src/convert/ingest.ts +73 -9
- package/src/diagnosis/cards.ts +13 -8
- package/src/diagnosis/run.ts +27 -17
- package/src/diagnosis/schema.ts +2 -2
- package/src/diagnosis/seed-source.ts +5 -4
- package/src/diagnosis/seed.ts +6 -8
- package/src/diagnosis/store.ts +99 -69
- package/src/diagnosis/types.ts +10 -16
- package/src/environment/dsh-python.ts +156 -0
- package/src/environment/install.ts +134 -19
- package/src/environment/model-catalog.ts +45 -3
- package/src/environment/service.ts +111 -23
- package/src/environment/types.ts +6 -0
- package/src/events.ts +12 -0
- package/src/extract-contract.ts +6 -3
- package/src/extract-model.ts +2 -0
- package/src/extract-table-contract.ts +29 -0
- package/src/extract-table.ts +126 -0
- package/src/extraction-runs.ts +70 -11
- package/src/fs-atomic.ts +19 -2
- package/src/images.ts +2 -2
- package/src/index.ts +207 -72
- package/src/intent/scope.ts +1 -0
- package/src/intent/tools.ts +38 -1
- package/src/knowledge-bases.ts +4 -12
- package/src/knowledge.ts +24 -4
- package/src/materialize-snapshot.ts +46 -0
- package/src/model-text.ts +5 -3
- package/src/oplog.ts +7 -0
- package/src/pipeline/engine.ts +312 -0
- package/src/pipeline/store.ts +147 -0
- package/src/pipeline/types.ts +92 -0
- package/src/present.ts +10 -4
- package/src/retention.ts +76 -0
- package/src/runtime.ts +853 -131
- package/src/source-evidence.ts +12 -5
- package/src/store/axis-references.ts +3 -3
- package/src/store/derived.ts +40 -7
- package/src/store/knowledge-db.ts +111 -5
- package/src/store/migrate.ts +1 -1
- package/src/trajectory-types.ts +142 -0
- package/src/types.ts +3 -4
- package/src/wiki-finalize.ts +28 -0
- package/src/wiki-materialize.ts +3 -14
- package/src/wiki-trial-types.ts +3 -0
- package/src/wiki-trial.ts +26 -7
- package/src/client/convert/ConvertPanel.tsx +0 -583
- package/src/client/convert/styles.css +0 -138
- package/src/client/convert/styles.ts +0 -49
package/src/index.ts
CHANGED
|
@@ -32,6 +32,7 @@ import { DOC_PAGE_CHARS, DOC_PAGE_CHARS_MAX, documentContentPage } from './docum
|
|
|
32
32
|
import type { ModelCall } from './extract-model.js'
|
|
33
33
|
import { runWikiTrial, trialSummary } from './wiki-trial.js'
|
|
34
34
|
import { extractionRunId } from './extraction-runs.js'
|
|
35
|
+
import type { PipelineStage } from './pipeline/types.js'
|
|
35
36
|
import {
|
|
36
37
|
agentLayout, defaultAgentRoot, ensureAgentLayout, intentRoot, knowledgeBaseRoot,
|
|
37
38
|
memoryRoot, presetSourceDir, resourceRegistry, resourceRoot, type AgentLayout,
|
|
@@ -474,7 +475,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
474
475
|
}
|
|
475
476
|
const provider = configured.mode === 'fixed' ? configured.provider : defaultProvider
|
|
476
477
|
const model = configured.mode === 'fixed' ? configured.model : defaultModel
|
|
477
|
-
const effectiveReasoningEffort = configured.mode === 'default' ? reasoningEffort : undefined
|
|
478
|
+
const effectiveReasoningEffort = configured.reasoningEffort || (configured.mode === 'default' ? reasoningEffort : undefined)
|
|
478
479
|
if (!provider || !model) {
|
|
479
480
|
return { why: `编译模型没有选好(provider=${provider ?? '空'} / model=${model ?? '空'})` }
|
|
480
481
|
}
|
|
@@ -482,7 +483,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
482
483
|
provider,
|
|
483
484
|
model,
|
|
484
485
|
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
485
|
-
call: async ({ system, prompt, sessionId, onUsage }) => {
|
|
486
|
+
call: async ({ system, prompt, sessionId, onUsage, onDelta }) => {
|
|
486
487
|
/**
|
|
487
488
|
* 先走 DSH 的公开解析接口,再开始发 prompt。这样固定模型过期、默认模型失效时,
|
|
488
489
|
* 编译会在模型调用边界明确失败,而不是落成一次看似成功的规则抽取。
|
|
@@ -491,7 +492,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
491
492
|
try {
|
|
492
493
|
await llm.resolveCallConfig({
|
|
493
494
|
provider, model,
|
|
494
|
-
...(
|
|
495
|
+
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
495
496
|
})
|
|
496
497
|
} catch (cause) {
|
|
497
498
|
const detail = cause instanceof Error ? cause.message : String(cause)
|
|
@@ -513,13 +514,13 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
513
514
|
// 而不是留空 —— 留空会在 OpenCode 路由上 400。
|
|
514
515
|
sessionId: sessionId ?? 'knowledge:default',
|
|
515
516
|
})
|
|
516
|
-
return collectModelText(llm.stream(request) as AsyncIterable<never>, onUsage)
|
|
517
|
+
return collectModelText(llm.stream(request) as AsyncIterable<never>, onUsage, onDelta)
|
|
517
518
|
},
|
|
518
519
|
}
|
|
519
520
|
}
|
|
520
521
|
let environment: KnowledgeEnvironment
|
|
521
522
|
const runtimeOptions = (): KnowledgeRuntimeOptions => ({
|
|
522
|
-
environmentGuard:
|
|
523
|
+
environmentGuard: kind => environment.requireReady(kind),
|
|
523
524
|
// 0029:家当目录。配置里三个路径为空时,运行时从这里解析模型与解释器。
|
|
524
525
|
resourceRoot: resourceRoot(config.agentRoot),
|
|
525
526
|
// 0032:记忆段。默认库的记忆落在 `<agentRoot>/memory/default`,与知识树平级。
|
|
@@ -556,7 +557,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
556
557
|
// 0044:非默认库的 runtime 也要拿到模型接缝。只传 `config` 不够 —— 默认库走上面
|
|
557
558
|
// 那个 `new KnowledgeRuntime(... runtimeOptions())`,别的库走注册表这条支路,
|
|
558
559
|
// 两边必须都带上,否则「新建一个库 → 导入文档」静默变成规则抽取。
|
|
559
|
-
runtimeOptions: () => ({ resourceRoot: resourceRoot(config.agentRoot), modelSeam: modelSeamFor, environmentGuard:
|
|
560
|
+
runtimeOptions: () => ({ resourceRoot: resourceRoot(config.agentRoot), modelSeam: modelSeamFor, environmentGuard: kind => environment.requireReady(kind) }),
|
|
560
561
|
})
|
|
561
562
|
compileModelForBase = id => bases.compileModelOf(id)
|
|
562
563
|
runtime.baseId = bases.defaultId
|
|
@@ -566,12 +567,35 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
566
567
|
snapshot: () => ({ config: runtime.config, paths: runtime.resourcePaths() }),
|
|
567
568
|
probe: kind => runtime.environmentProbe(kind),
|
|
568
569
|
reload: () => { runtime.reloadResources(); bases.reloadResources() },
|
|
570
|
+
activateSelection: async () => {
|
|
571
|
+
const keys = ['embeddingProvider', 'embeddingModel', 'rerankProvider', 'rerankModel'] as const
|
|
572
|
+
const previous = Object.fromEntries(keys.map(key => [key, config[key]]))
|
|
573
|
+
const managed = { embeddingProvider: 'wemm-python', embeddingModel: '', rerankProvider: 'bert-python', rerankModel: '' }
|
|
574
|
+
const write = async (values: Record<string, unknown>): Promise<void> => {
|
|
575
|
+
const group = pluginConfig.knowledge as unknown as Record<string, unknown> | undefined
|
|
576
|
+
if (!group) throw new Error('模型设置不可写')
|
|
577
|
+
for (const key of keys) {
|
|
578
|
+
const reference = group[key] as { write?: (value: unknown) => unknown } | undefined
|
|
579
|
+
if (reference && typeof reference.write === 'function') await reference.write(values[key])
|
|
580
|
+
else group[key] = values[key]
|
|
581
|
+
}
|
|
582
|
+
config = normalizeKnowledge(pluginConfig.knowledge)
|
|
583
|
+
runtime.updateConfig(config); bases.updateRuntimeConfig(config)
|
|
584
|
+
}
|
|
585
|
+
try { await write(managed) } catch (cause) { await write(previous); throw cause }
|
|
586
|
+
return () => {
|
|
587
|
+
const current = normalizeKnowledge(pluginConfig.knowledge)
|
|
588
|
+
// A later manual setting change belongs to the user and must survive rollback.
|
|
589
|
+
const restored = Object.fromEntries(keys.map(key => [key, current[key] === managed[key] ? previous[key] : current[key]]))
|
|
590
|
+
return write(restored)
|
|
591
|
+
}
|
|
592
|
+
},
|
|
569
593
|
})
|
|
570
|
-
//
|
|
571
|
-
void environment.check(false).catch(() => undefined)
|
|
594
|
+
// Reuse a matching persisted probe after restart; only stale or missing reports need a resource check.
|
|
595
|
+
if (environment.view().report.state !== 'ready') void environment.check(false).catch(() => undefined)
|
|
572
596
|
|
|
573
|
-
type CompileModelCatalogItem = { provider: string; id: string; name: string; description?: string; available: boolean; current?: boolean }
|
|
574
|
-
const compileModelCatalog = async (kbId: string): Promise<{
|
|
597
|
+
type CompileModelCatalogItem = { provider: string; id: string; name: string; description?: string; available: boolean; current?: boolean; reasoning?: { efforts: Array<{ id: string; name: string; description?: string }>; defaultEffort?: string } }
|
|
598
|
+
const compileModelCatalog = async (kbId: string, queryModelRoute: { provider?: string; model?: string } = {}): Promise<{
|
|
575
599
|
selection: CompileModelSelection
|
|
576
600
|
default: { provider?: string; model?: string; reasoningEffort?: string; available: boolean }
|
|
577
601
|
models: CompileModelCatalogItem[]
|
|
@@ -582,8 +606,10 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
582
606
|
const defaultRoute = service?.currentSelection?.() ?? {}
|
|
583
607
|
const llm = ctx.get('llm') as {
|
|
584
608
|
listModels?: (provider: string) => Promise<Array<{ id: string; name?: string; description?: string }>>
|
|
609
|
+
listProviders?: () => Array<{ id: string; name: string }>
|
|
610
|
+
resolveModelInfo?: (provider: string, model: string) => Promise<{ reasoning?: CompileModelCatalogItem['reasoning'] }>
|
|
585
611
|
} | undefined
|
|
586
|
-
const providers = [...new Set([defaultRoute.provider, compileModelForBase(kbId).provider].filter((value): value is string => Boolean(value)))]
|
|
612
|
+
const providers = [...new Set([...(llm?.listProviders?.() ?? []).map(provider => provider.id), defaultRoute.provider, compileModelForBase(kbId).provider].filter((value): value is string => Boolean(value)))]
|
|
587
613
|
const models: CompileModelCatalogItem[] = []
|
|
588
614
|
const failures: string[] = []
|
|
589
615
|
if (!llm?.listModels) {
|
|
@@ -610,6 +636,17 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
610
636
|
}
|
|
611
637
|
|
|
612
638
|
const selection = compileModelForBase(kbId)
|
|
639
|
+
const effortProvider = selection.mode === 'fixed' ? selection.provider : defaultRoute.provider
|
|
640
|
+
const effortModel = selection.mode === 'fixed' ? selection.model : defaultRoute.model
|
|
641
|
+
const requestedProvider = queryModelRoute.provider || effortProvider
|
|
642
|
+
const requestedModel = queryModelRoute.model || effortModel
|
|
643
|
+
if (requestedProvider && requestedModel && llm?.resolveModelInfo) {
|
|
644
|
+
const item = models.find(model => model.provider === requestedProvider && model.id === requestedModel)
|
|
645
|
+
if (item) {
|
|
646
|
+
try { item.reasoning = (await llm.resolveModelInfo(requestedProvider, requestedModel)).reasoning }
|
|
647
|
+
catch (cause) { failures.push(`思考强度目录:${cause instanceof Error ? cause.message : String(cause)}`) }
|
|
648
|
+
}
|
|
649
|
+
}
|
|
613
650
|
const selected = selection.mode === 'fixed' && selection.provider && selection.model
|
|
614
651
|
? models.find(item => item.provider === selection.provider && item.id === selection.model)
|
|
615
652
|
: undefined
|
|
@@ -777,23 +814,56 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
777
814
|
applyIntent(ctx, { intentRoot: () => intentRoot(config.agentRoot) })
|
|
778
815
|
|
|
779
816
|
/**
|
|
780
|
-
* 需要本轮用户审批的知识写工具。`
|
|
817
|
+
* 需要本轮用户审批的知识写工具。`image_publish` 只写入本地素材目录,按用户要求免审批。
|
|
781
818
|
*
|
|
782
819
|
* 0044 换过一批名字:`write_knowledge_card`(只接受已确认提议)退役了,因为正文现在
|
|
783
820
|
* 只读、卡片由编译直接写入;`list_card_proposals` 也退役了,因为待办队列不存在了。
|
|
784
821
|
* 新增的两个是**入库之后**的动作:加备注、review 抬分。
|
|
785
822
|
*/
|
|
786
|
-
|
|
823
|
+
ctx.tools.register(tool('run_knowledge_pipeline', 'Run one independent knowledge stage. Import only saves sources by default. Stages: extract Knowledge/Event, normalize existing results, materialize selected complete card groups, build retrieval indexes, or all. Returns a durable background task; inspect it with get_knowledge_pipeline.', {
|
|
824
|
+
stage: { type: 'string', required: true, enum: ['extract', 'normalize', 'materialize', 'build', 'all'] },
|
|
825
|
+
kb: { type: 'string' }, source_ids: { type: 'array', description: 'Optional explicit source IDs; omit for all sources. An empty selection is rejected.' },
|
|
826
|
+
group_keys: { type: 'array', description: 'Optional full card group keys returned by get_knowledge_pipeline.' }, force: { type: 'boolean', description: 'Explicitly rerun the selected stage despite an unchanged checkpoint.' },
|
|
827
|
+
}, async args => withRuntime(args.kb, async target => {
|
|
828
|
+
const pipeline = await target.pipeline()
|
|
829
|
+
const { task } = pipeline.start(args.stage as PipelineStage, { sourceIds: args.source_ids, groupKeys: args.group_keys, force: args.force === true })
|
|
830
|
+
return success({ task, url: `/knowledge/pages/tasks?kb=${encodeURIComponent(target.baseId)}&task=${encodeURIComponent(task.id)}` })
|
|
831
|
+
})))
|
|
832
|
+
ctx.tools.register(tool('get_knowledge_pipeline', 'Read pending stages, materializable card groups, persistent task history, or incremental public model output. Use the returned next cursor as after on the next call.', {
|
|
833
|
+
kb: { type: 'string' }, task_id: { type: 'string' }, after: { type: 'integer' },
|
|
834
|
+
}, async args => withRuntime(args.kb, async (target): Promise<Envelope<Record<string, unknown>>> => {
|
|
835
|
+
const pipeline = await target.pipeline()
|
|
836
|
+
return args.task_id
|
|
837
|
+
? success({ task: pipeline.store.task(args.task_id) ?? null, ...pipeline.store.events(args.task_id, Math.max(0, args.after ?? 0)) })
|
|
838
|
+
: success({ status: await target.pipelineStatus(), ...pipeline.store.tasks() })
|
|
839
|
+
})))
|
|
840
|
+
const writeTools = new Set(['add_source', 'import_sources', 'propose_wiki_cards', 'annotate_wiki_card', 'review_wiki_card', 'set_knowledge_status', 'rebuild_knowledge', 'run_knowledge_pipeline', 'record_feedback'])
|
|
787
841
|
ctx.on('tools/pre-execute', (exec, next): Promise<PreToolDecision> => {
|
|
788
842
|
if (writeTools.has(exec.name)) return Promise.resolve({ kind: 'ask', reason: '该操作会写入 Tita Knowledge Store,需要本轮用户审批。' })
|
|
789
843
|
return next()
|
|
790
844
|
})
|
|
791
|
-
ctx.tools.register(tool('recall_knowledge', 'Recall compiled
|
|
845
|
+
ctx.tools.register(tool('recall_knowledge', 'Recall compiled Knowledge through keyword and vector candidate routes, rerank WikiCard sections, then return cards with matched sections, provenance, and Event reference IDs. Use get_knowledge_events to read referenced Events.', { query: { type: 'string', required: true }, limit: { type: 'integer' }, top_k: { type: 'integer' }, top_n: { type: 'integer' }, rerank: { type: 'boolean' }, context_max_chars: { type: 'integer', description: '这次召回的上下文字符上限;省略则用设置里的值(默认 20000,0 = 不限)。超出部分落在系统临时目录,响应里的 budget.overflow_path 就是它的绝对路径 —— 需要更多信息时直接读那个文件。' }, include_problems: { type: 'boolean' }, mode: { type: 'string', enum: ['fts', 'vector', 'hybrid'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, parent_subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.recall(args.query, args.top_k ?? args.limit, args.include_problems ?? false, args.mode as RecallMode | undefined, lineageOf(args, exec), { contextTopN: args.top_n, rerank: args.rerank, ...(typeof args.context_max_chars === 'number' ? { contextMaxChars: args.context_max_chars } : {}) })), {
|
|
792
846
|
call: recallCallView,
|
|
793
847
|
// 查询词从参数来,命中从结果来 —— 两者拼成一张能看懂的账本。
|
|
794
848
|
meta: (args, value) => recallMetaOf({ ...dataOf(value), query: args?.query }),
|
|
795
849
|
result: resultViewOf(recallResultMarkdown),
|
|
796
850
|
}))
|
|
851
|
+
ctx.tools.register(tool('get_knowledge_events', 'Read Event records referenced by recalled WikiCards. Pass event IDs from card metadata.event_refs; the result includes full timelines, evidence, outcomes, and missing fields.', {
|
|
852
|
+
event_ids: { type: 'array', required: true, description: 'Event IDs from WikiCard metadata.event_refs; maximum 50 IDs.' },
|
|
853
|
+
kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' },
|
|
854
|
+
run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' },
|
|
855
|
+
}, async args => withRuntime(args.kb, async target => {
|
|
856
|
+
const ids = [...new Set((Array.isArray(args.event_ids) ? args.event_ids : [])
|
|
857
|
+
.filter((id): id is string => typeof id === 'string' && id.trim() !== '').map(id => id.trim()))]
|
|
858
|
+
if (ids.length === 0) return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_REQUIRED', '请传入 WikiCard 的 event_refs 中的 Event ID。', false)
|
|
859
|
+
if (ids.length > 50) return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_LIMIT', '一次最多读取 50 个 Event ID;请分批读取该 WikiCard 的 event_refs。', false)
|
|
860
|
+
const byId = new Map((await target.loadEvents()).map(event => [event.id, event]))
|
|
861
|
+
const events = ids.flatMap(id => {
|
|
862
|
+
const event = byId.get(id)
|
|
863
|
+
return event ? [event] : []
|
|
864
|
+
})
|
|
865
|
+
return success({ events, missing_ids: ids.filter(id => !byId.has(id)) })
|
|
866
|
+
})))
|
|
797
867
|
ctx.tools.register(tool('check_knowledge_embeddings', 'Inspect the local embedding provider and binary/int8 vector index without loading the model.', { kb: { type: 'string' }, session_key: { type: 'string' } }, async args => withRuntime(args.kb, target => target.embeddingHealth())))
|
|
798
868
|
ctx.tools.register(tool('query_knowledge_graph', 'Query entities, neighbors, paths, lists and relation types in the compiled knowledge graph.', { action: { type: 'string', enum: ['entity', 'neighbors', 'path', 'list', 'types'], required: true }, entity_id: { type: 'string' }, id: { type: 'string' }, entity_type: { type: 'string' }, from_id: { type: 'string' }, to_id: { type: 'string' }, type: { type: 'string' }, status: { type: 'string' }, include_problem: { type: 'boolean' }, limit: { type: 'integer' }, depth: { type: 'integer' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, parent_subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.graphQuery(args.action, args.entity_id ?? args.id, args.entity_type ?? args.type, args.depth ?? 1, { fromId: args.from_id, toId: args.to_id, status: args.status, includeProblem: args.include_problem ?? false, limit: args.limit ?? 50 }, lineageOf(args, exec)))))
|
|
799
869
|
ctx.tools.register(tool('query_source_truth', 'Read SourceRaw (原始可追溯文档) with access audit. Each call returns up to `limit` matched sources and `max_chars` total characters. If `hasMoreInCurrentPage` is true, continue with `offset += returnedChars`. When it is false and `hasMoreSources` is true, continue with `source_offset = nextSourceOffset` and reset `offset` to 0. `truncated` stays true until both the current source page and all matching source pages are complete. Do not answer from partial content.', { query: { type: 'string', required: true }, limit: { type: 'integer' }, max_chars: { type: 'integer' }, offset: { type: 'integer' }, source_offset: { type: 'integer', description: '来源分页游标;当前页字符已读完后才使用 nextSourceOffset,并将 offset 重置为 0' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.sourceTruth(args.query, args.limit ?? 20, args.max_chars ?? 40000, lineageOf(args, exec), args.offset ?? 0, args.source_offset ?? 0))))
|
|
@@ -824,30 +894,28 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
824
894
|
})),
|
|
825
895
|
})))
|
|
826
896
|
|
|
827
|
-
|
|
828
|
-
'
|
|
829
|
-
|
|
830
|
-
{
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
}),
|
|
850
|
-
))
|
|
897
|
+
const imagePublishParameters = {
|
|
898
|
+
file_path: { type: 'string', description: '宿主可读取的本地图片文件路径;与 content_base64 二选一' },
|
|
899
|
+
content_base64: { type: 'string', description: '图片的标准 Base64,或 data:image/...;base64,...;与 file_path 二选一' },
|
|
900
|
+
filename: { type: 'string', description: '可选文件名;默认使用 file_path 的文件名,Base64 输入默认 image-<hash>.<ext>' },
|
|
901
|
+
mime_type: { type: 'string', description: '可选 MIME 类型;必须与图片二进制内容一致' },
|
|
902
|
+
kb: { type: 'string', description: '知识库 id;省略时写入默认库' },
|
|
903
|
+
}
|
|
904
|
+
const publishImage = async (args: Args) => withRuntime(args.kb, async target => {
|
|
905
|
+
try {
|
|
906
|
+
return success(await publishKnowledgeImage(target.rootDir, target.baseId, {
|
|
907
|
+
filePath: args.file_path,
|
|
908
|
+
contentBase64: args.content_base64,
|
|
909
|
+
filename: args.filename,
|
|
910
|
+
mimeType: args.mime_type,
|
|
911
|
+
}))
|
|
912
|
+
} catch (cause) {
|
|
913
|
+
if (cause instanceof ImagePublishError) return failure({}, cause.code, cause.message, false, cause.suggestion)
|
|
914
|
+
return failure({}, 'IMAGE_PUBLISH_FAILED', `图片写入失败:${cause instanceof Error ? cause.message : String(cause)}`, true)
|
|
915
|
+
}
|
|
916
|
+
})
|
|
917
|
+
const imagePublishDescription = 'Publish one local image into a knowledge base assets bucket without user approval. Pass exactly one of `file_path` or `content_base64` (standard Base64 or a data URI); optional `filename` overrides the stored name, and optional `mime_type` must match the file bytes. Supports PNG, JPEG, GIF, WebP, AVIF, BMP and SVG up to 20 MiB. Returns the local asset URL and Markdown image reference.'
|
|
918
|
+
ctx.tools.register(tool('image_publish', imagePublishDescription, imagePublishParameters, publishImage))
|
|
851
919
|
|
|
852
920
|
ctx.tools.register(tool('add_source', 'Add or update a SourceRaw document (原始可追溯文档). DSH requests one user approval before execution.', { title: { type: 'string' }, content: { type: 'string', required: true }, external_id: { type: 'string' }, metadata: { type: 'json' }, source_id: { type: 'string' }, allow_duplicate_title: { type: 'boolean', description: '默认 false:标题与已有来源重复时拒收(SOURCE_TITLE_DUPLICATED),要求改标题。传 true 才允许并存。' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.addSource({ title: args.title, content: args.content, externalId: args.external_id, metadata: args.metadata, sourceId: args.source_id, ...(args.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}), lineage: lineageOf(args, exec) })), {
|
|
853
921
|
call: addSourceCallView,
|
|
@@ -868,7 +936,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
868
936
|
* `compile` 默认开:批量导入的意图几乎总是「进来就能用」。关掉它的场景是「先全部
|
|
869
937
|
* 落盘、稍后统一编译」——那时卡片还没生成,工具返回里会明说 `compile: null`。
|
|
870
938
|
*/
|
|
871
|
-
ctx.tools.register(tool('import_sources', 'Import many SourceRaw documents
|
|
939
|
+
ctx.tools.register(tool('import_sources', 'Import many SourceRaw documents in one call. By default only stores sources. Explicit compile:true runs extract, normalize, materialize, and index publication for the imported documents. Per-source import failures are reported without aborting the batch. DSH requests one user approval before execution.', { sources: { type: 'array', required: true, description: `来源数组,每项 { title?, content, external_id?, metadata?, allow_duplicate_title? };一次最多 ${MAX_IMPORT_SOURCES} 份,超了请分批。标题与已有来源重复的那一份会被拒(SOURCE_TITLE_DUPLICATED),逐条报出来而不是整批失败` }, compile: { type: 'boolean', description: '默认 false:只保存来源;显式 true 才运行完整抽取、归一与物化' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.importSources({ sources: (Array.isArray(args.sources) ? args.sources : []).map(item => ({ ...(item?.title ? { title: item.title } : {}), content: item?.content, ...(item?.external_id ? { externalId: item.external_id } : {}), ...(item?.metadata ? { metadata: item.metadata } : {}), ...(item?.allow_duplicate_title === true ? { allowDuplicateTitle: true } : {}) })), compile: args.compile === true, lineage: lineageOf(args, exec) })), { call: importCallView, meta: (_args, value) => importMetaOf(dataOf(value)), result: resultViewOf(importResultMarkdown) }))
|
|
872
940
|
/**
|
|
873
941
|
* `propose_wiki_cards` —— 把一批卡片**直接写进 `02_wiki`**(工具名不变,语义在 0044 改了)。
|
|
874
942
|
*
|
|
@@ -924,7 +992,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
924
992
|
lineage: lineageOf(args, exec),
|
|
925
993
|
}))))
|
|
926
994
|
ctx.tools.register(tool('set_knowledge_status', 'Change a Wiki card status. This status belongs to the Card lifecycle; it does not change Knowledge truth status. problem requires problem_note and recovery must pass through unreviewed. DSH requests one user approval before execution.', { entity_id: { type: 'string', required: true }, status: { type: 'string', enum: ['unreviewed', 'draft', 'reviewed', 'active', 'deprecated', 'problem'], required: true }, problem_note: { type: 'string' }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.setStatus(args.entity_id, args.status as CardStatus, args.problem_note, lineageOf(args, exec)))))
|
|
927
|
-
ctx.tools.register(tool('rebuild_knowledge', 'Rebuild schema/wiki/compiled knowledge indexes. DSH requests one user approval before execution.', { only: { type: 'string', enum: ['all', 'schema', 'wiki', 'embeddings'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.rebuild(args.only ?? 'all', lineageOf(args, exec))), {
|
|
995
|
+
ctx.tools.register(tool('rebuild_knowledge', 'Rebuild schema/wiki/compiled knowledge indexes from saved cards without document extraction. Use run_knowledge_pipeline for extract, normalize, materialize, or the full chain. DSH requests one user approval before execution.', { only: { type: 'string', enum: ['all', 'schema', 'wiki', 'embeddings'] }, kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' }, run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' } }, async (args, exec) => withRuntime(args.kb, target => target.rebuild(args.only ?? 'all', lineageOf(args, exec), { extract: false })), {
|
|
928
996
|
call: rebuildCallView,
|
|
929
997
|
meta: (args, value) => rebuildMetaOf(args?.only ?? 'all', dataOf(value)),
|
|
930
998
|
result: resultViewOf(rebuildResultMarkdown),
|
|
@@ -1004,7 +1072,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1004
1072
|
const url = query(req)
|
|
1005
1073
|
const id = kbIdOf(url.searchParams.get('kb'))
|
|
1006
1074
|
if (!bases.get(id)) return sendEnvelope(res, failure({}, 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true, '先 GET /knowledge/bases 拿到可用 id。'))
|
|
1007
|
-
return send(res, 200, success(await compileModelCatalog(id)))
|
|
1075
|
+
return send(res, 200, success(await compileModelCatalog(id, { provider: url.searchParams.get('provider') ?? undefined, model: url.searchParams.get('model') ?? undefined })))
|
|
1008
1076
|
} },
|
|
1009
1077
|
{ kind: 'exact' as const, path: '/knowledge/stats', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1010
1078
|
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
@@ -1093,6 +1161,47 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1093
1161
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1094
1162
|
return send(res, 200, success({ progress: resolved.runtime.compileProgressView(url.searchParams.get('runId') ?? undefined) }))
|
|
1095
1163
|
} },
|
|
1164
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1165
|
+
try {
|
|
1166
|
+
const body = req.method === 'POST' ? await readBody(req, 100_000) : {}
|
|
1167
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'))
|
|
1168
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1169
|
+
if (req.method === 'GET') return send(res, 200, success(await resolved.runtime.pipelineStatus()))
|
|
1170
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '知识处理只接受 GET 或 POST'))
|
|
1171
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1172
|
+
const started = pipeline.start(body.stage as PipelineStage, { sourceIds: body.sourceIds, groupKeys: body.groupKeys, force: body.force === true })
|
|
1173
|
+
return send(res, 202, success({ task: started.task }))
|
|
1174
|
+
} catch (cause) { return send(res, 400, envelopeError('PIPELINE_INVALID', cause instanceof Error ? cause.message : String(cause))) }
|
|
1175
|
+
} },
|
|
1176
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline-tasks', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1177
|
+
try {
|
|
1178
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务历史只接受 GET'))
|
|
1179
|
+
const url = query(req)
|
|
1180
|
+
const resolved = resolveBase(url.searchParams.get('kb'))
|
|
1181
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1182
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1183
|
+
const limit = Math.min(100, Math.max(1, Number(url.searchParams.get('limit')) || 20))
|
|
1184
|
+
const offset = Math.max(0, Number(url.searchParams.get('offset')) || 0)
|
|
1185
|
+
return send(res, 200, success({ ...pipeline.store.tasks(limit, offset), extractions: await resolved.runtime.listExtractionRuns({ limit, offset }) }))
|
|
1186
|
+
} catch (cause) { return send(res, 400, envelopeError('PIPELINE_HISTORY_FAILED', cause instanceof Error ? cause.message : String(cause))) }
|
|
1187
|
+
} },
|
|
1188
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline-task', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1189
|
+
try {
|
|
1190
|
+
const url = query(req)
|
|
1191
|
+
const body = req.method === 'POST' ? await readBody(req, 20_000) : {}
|
|
1192
|
+
const resolved = resolveBase(body.kb ?? url.searchParams.get('kb'))
|
|
1193
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1194
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1195
|
+
const id = typeof body.id === 'string' ? body.id : url.searchParams.get('id') ?? ''
|
|
1196
|
+
const task = pipeline.store.task(id)
|
|
1197
|
+
if (!task) return send(res, 404, envelopeError('PIPELINE_NOT_FOUND', '任务不存在'))
|
|
1198
|
+
if (req.method === 'GET') return send(res, 200, success({ task, ...pipeline.store.events(id, Math.max(0, Number(url.searchParams.get('after')) || 0)) }))
|
|
1199
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务只接受 GET 或 POST'))
|
|
1200
|
+
if (body.action === 'retry') return send(res, 202, success({ task: pipeline.start(task.stage, { ...task.selection, force: false }).task }))
|
|
1201
|
+
if (!['pause', 'resume', 'cancel'].includes(body.action)) throw new Error('不支持的任务操作')
|
|
1202
|
+
return send(res, 200, success({ task: pipeline.control(id, body.action) }))
|
|
1203
|
+
} catch (cause) { return send(res, 409, envelopeError('PIPELINE_TASK_REJECTED', cause instanceof Error ? cause.message : String(cause))) }
|
|
1204
|
+
} },
|
|
1096
1205
|
{ kind: 'exact' as const, path: '/knowledge/extraction-tasks', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1097
1206
|
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '抽取任务只接受 GET'))
|
|
1098
1207
|
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
@@ -1156,7 +1265,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1156
1265
|
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'))
|
|
1157
1266
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1158
1267
|
return send(res, 202, success(resolved.runtime.startCompile(body.only ?? 'all', lineageOf(body), {
|
|
1159
|
-
|
|
1268
|
+
extract: body.extract === true,
|
|
1160
1269
|
...(body.forceExtract === true ? { forceExtract: true } : {}),
|
|
1161
1270
|
})))
|
|
1162
1271
|
} catch (cause) {
|
|
@@ -1183,7 +1292,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1183
1292
|
...(item?.external_id ? { externalId: String(item.external_id) } : {}),
|
|
1184
1293
|
...(item?.metadata && typeof item.metadata === 'object' ? { metadata: item.metadata as Record<string, unknown> } : {}),
|
|
1185
1294
|
})),
|
|
1186
|
-
|
|
1295
|
+
compile: body.compile === true,
|
|
1187
1296
|
lineage: lineageOf(body),
|
|
1188
1297
|
}))
|
|
1189
1298
|
} catch (cause) {
|
|
@@ -1276,7 +1385,8 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1276
1385
|
* 任何一条轨迹 —— 「这个实体是哪次抽出来的」就答不上了。
|
|
1277
1386
|
*/
|
|
1278
1387
|
const runId = extractionRunId()
|
|
1279
|
-
const seam = modelSeamFor()
|
|
1388
|
+
const seam = modelSeamFor(resolved.runtime.baseId)
|
|
1389
|
+
const draft = await (await resolved.runtime.axisRegistry())?.draft()
|
|
1280
1390
|
const axesBlock = await resolved.runtime.axisPromptBlockView()
|
|
1281
1391
|
const note = resolved.runtime.extractionNote()
|
|
1282
1392
|
const report = await runWikiTrial({
|
|
@@ -1286,7 +1396,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1286
1396
|
...(typeof body.subject === 'string' && body.subject.trim() ? { subject: body.subject.trim() } : {}),
|
|
1287
1397
|
/** 与编译相同:先查注册表,再把有候选的坐标合并成一次模型补判。 */
|
|
1288
1398
|
resolveAxisSet: axes => resolved.runtime.resolveTrialAxisSet({
|
|
1289
|
-
axes,
|
|
1399
|
+
registry: draft?.registry, axes,
|
|
1290
1400
|
axesBlock,
|
|
1291
1401
|
runId,
|
|
1292
1402
|
call: seam.call,
|
|
@@ -1309,7 +1419,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1309
1419
|
},
|
|
1310
1420
|
...('call' in seam ? { call: seam.call } : {}),
|
|
1311
1421
|
...('why' in seam ? { callWhy: seam.why } : {}),
|
|
1312
|
-
})
|
|
1422
|
+
}).finally(() => draft?.close())
|
|
1313
1423
|
const summary = trialSummary(report)
|
|
1314
1424
|
|
|
1315
1425
|
/**
|
|
@@ -1325,24 +1435,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1325
1435
|
lineage: lineageOf(body),
|
|
1326
1436
|
})
|
|
1327
1437
|
|
|
1328
|
-
|
|
1329
|
-
* 把这次抽出来的 Knowledge 收进真源(0063 §2)。
|
|
1330
|
-
*
|
|
1331
|
-
* **只在跑一份已入库的文档时收。** 理由有两条:来源要指得回一份**真实的**
|
|
1332
|
-
* 文档(临时正文的 `documentId` 是一个标题字符串,存下来就是悬空引用);
|
|
1333
|
-
* 而「这条知识是哪份文档说的」正是它可验证的前提。
|
|
1334
|
-
*
|
|
1335
|
-
* **只收引文核对得上的那些。** 引文对不上原文 = 模型改写了,那条判断不是从
|
|
1336
|
-
* 原文推出来的(§5.7 的引文核验)。把它当知识存进真源,等于让一句编出来的话
|
|
1337
|
-
* 看起来像有出处 —— 而报告里已经把它报成例外了,那里才是它该待的地方。
|
|
1338
|
-
*/
|
|
1339
|
-
const ingest = docId
|
|
1340
|
-
? await resolved.runtime.ingestTrialKnowledge({
|
|
1341
|
-
runId, knowledge: report.knowledge,
|
|
1342
|
-
objects: report.objects, actions: report.actions, scenes: report.scenes,
|
|
1343
|
-
})
|
|
1344
|
-
: { saved: 0, merged: 0, skipped: 0 }
|
|
1345
|
-
return send(res, 200, success({ ...report, summary, run, ingest }))
|
|
1438
|
+
return send(res, 200, success({ ...report, summary, run }))
|
|
1346
1439
|
} catch (cause) {
|
|
1347
1440
|
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)))
|
|
1348
1441
|
}
|
|
@@ -1570,7 +1663,10 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1570
1663
|
return send(res, 400, envelopeError('REQUEST_INVALID', `不认识的 action: ${action || '(空)'}`))
|
|
1571
1664
|
} },
|
|
1572
1665
|
/**
|
|
1573
|
-
*
|
|
1666
|
+
* 轨迹(`extraction_run`):按文档或阶段列、按 id 读全文。
|
|
1667
|
+
*
|
|
1668
|
+
* 2026-10-18 起三段共用这张表(`stage` 列:`extract` / `normalize` / `materialize`)。
|
|
1669
|
+
* `stage` 省略 = 三段一起 —— 面板里那份「这份文档的轨迹」就是这么读的。
|
|
1574
1670
|
*
|
|
1575
1671
|
* 列表**不返回报告本体** —— 一条报告几十到几百 KB,翻一页列表不该按全文付费。
|
|
1576
1672
|
* 点开某一条走 `/knowledge/extraction-run?id=…`(单独一条路由,不是路径参数:
|
|
@@ -1582,8 +1678,11 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1582
1678
|
const resolved = resolveBase(url.searchParams.get('kb'))
|
|
1583
1679
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1584
1680
|
const docId = url.searchParams.get('doc_id')
|
|
1681
|
+
const stageParam = url.searchParams.get('stage')
|
|
1682
|
+
const stage = stageParam === 'extract' || stageParam === 'normalize' || stageParam === 'materialize' ? stageParam : undefined
|
|
1585
1683
|
const page = await resolved.runtime.listExtractionRuns({
|
|
1586
1684
|
...(docId === null ? {} : { docId }),
|
|
1685
|
+
...(stage === undefined ? {} : { stage }),
|
|
1587
1686
|
limit: clampInt(url.searchParams.get('limit'), 20, 1, 100),
|
|
1588
1687
|
offset: clampInt(url.searchParams.get('offset'), 0, 0, Number.MAX_SAFE_INTEGER),
|
|
1589
1688
|
})
|
|
@@ -1696,10 +1795,8 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1696
1795
|
return send(res, 200, success({ cases: (await storeOf(resolved.runtime).snapshot()).cases }))
|
|
1697
1796
|
} },
|
|
1698
1797
|
/**
|
|
1699
|
-
*
|
|
1700
|
-
*
|
|
1701
|
-
* 0044:卡片不再只提议 —— 它直接入库并标成 `unreviewed`。响应给 `sourceId` 与
|
|
1702
|
-
* `entityId`,界面据此说清「记录留下了,卡片也建了,等人 review」。
|
|
1798
|
+
* 一次排查结束只落一条 SourceRaw。Case 视图从记录派生;Knowledge / Event 的抽取、
|
|
1799
|
+
* 归一和 WikiCard 物化由知识处理阶段分别执行。
|
|
1703
1800
|
*/
|
|
1704
1801
|
{ kind: 'exact' as const, path: '/diagnosis/run', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1705
1802
|
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 endpoint 只接受 POST'))
|
|
@@ -1712,9 +1809,9 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1712
1809
|
}
|
|
1713
1810
|
const outcome = await storeOf(resolved.runtime).recordRun(record)
|
|
1714
1811
|
if (outcome.problems.length > 0 && outcome.sourceId === undefined) {
|
|
1715
|
-
return send(res, 200, failure({ sourceId: ''
|
|
1812
|
+
return send(res, 200, failure({ sourceId: '' }, 'RUN_WRITE_FAILED', outcome.problems.join(';'), true))
|
|
1716
1813
|
}
|
|
1717
|
-
return send(res, 200, success({ sourceId: outcome.sourceId ?? ''
|
|
1814
|
+
return send(res, 200, success({ sourceId: outcome.sourceId ?? '' }, outcome.problems))
|
|
1718
1815
|
} },
|
|
1719
1816
|
/**
|
|
1720
1817
|
* 入库之后的两个人工动作(0044):加备注、review 抬分。
|
|
@@ -1862,6 +1959,24 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1862
1959
|
if (rest.length === 2) {
|
|
1863
1960
|
if (method === 'PATCH') {
|
|
1864
1961
|
const body = await readBody(req)
|
|
1962
|
+
if (body.compileModel && typeof body.compileModel === 'object') {
|
|
1963
|
+
const selection = body.compileModel as CompileModelSelection
|
|
1964
|
+
const catalog = await compileModelCatalog(baseId)
|
|
1965
|
+
const provider = selection.mode === 'fixed' ? selection.provider : catalog.default.provider
|
|
1966
|
+
const model = selection.mode === 'fixed' ? selection.model : catalog.default.model
|
|
1967
|
+
if (selection.mode !== 'fixed' && selection.mode !== 'default') return send(res, 400, envelopeError('MODEL_INVALID', '请选择跟随默认或固定模型'))
|
|
1968
|
+
if (selection.mode === 'fixed' && (!provider || !model)) return send(res, 400, envelopeError('MODEL_INVALID', '固定模型需要 provider 和 model'))
|
|
1969
|
+
const llm = ctx.get('llm')
|
|
1970
|
+
if (provider && model && llm?.resolveCallConfig) {
|
|
1971
|
+
try {
|
|
1972
|
+
const info = selection.reasoningEffort ? await llm.resolveModelInfo(provider, model) : undefined
|
|
1973
|
+
const effort = info?.reasoning?.efforts.find(item => item.id === selection.reasoningEffort)
|
|
1974
|
+
if (selection.reasoningEffort && !effort) throw new Error('所选模型不支持这个思考强度,请重新选择')
|
|
1975
|
+
await llm.resolveCallConfig({ provider, model, ...(effort ? { reasoningEffort: effort.id } : {}) })
|
|
1976
|
+
}
|
|
1977
|
+
catch (cause) { return send(res, 400, envelopeError('MODEL_INVALID', cause instanceof Error ? cause.message : String(cause))) }
|
|
1978
|
+
} else if (selection.reasoningEffort) return send(res, 400, envelopeError('MODEL_INVALID', '模型不可用,无法校验思考强度'))
|
|
1979
|
+
}
|
|
1865
1980
|
const updated = await bases.update(baseId, {
|
|
1866
1981
|
name: body.name, description: body.description,
|
|
1867
1982
|
extractionNote: typeof body.extractionNote === 'string' ? body.extractionNote : undefined,
|
|
@@ -2083,6 +2198,26 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
2083
2198
|
let documents = await resolved.runtime.listDocuments()
|
|
2084
2199
|
const search = (url.searchParams.get('query') ?? '').trim().toLocaleLowerCase()
|
|
2085
2200
|
if (search) documents = documents.filter(item => `${item.docId}\n${item.title}\n${item.externalId}`.toLocaleLowerCase().includes(search))
|
|
2201
|
+
/**
|
|
2202
|
+
* 文件类型的计数对着**搜索之后、类型筛选之前**的集合算。
|
|
2203
|
+
*
|
|
2204
|
+
* 下拉要能切换类型,所以每个类型的数量必须是「当前搜索下有多少份」,
|
|
2205
|
+
* 而不是「当前这一页里有多少份」—— 后者会让选项随翻页变来变去。
|
|
2206
|
+
*/
|
|
2207
|
+
const extCounts: Record<string, number> = {}
|
|
2208
|
+
for (const item of documents) {
|
|
2209
|
+
const key = item.ext || '—'
|
|
2210
|
+
extCounts[key] = (extCounts[key] ?? 0) + 1
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* 文件类型筛选**在服务端**做(0070)。
|
|
2214
|
+
*
|
|
2215
|
+
* 原来是「客户端取前 500 份、再在这一页里筛」:超过 500 份的库,第 501 份起
|
|
2216
|
+
* 在面板上根本不可达,而表头把这种截断显示成「筛出 500 / 600 个」——
|
|
2217
|
+
* 读起来像用户自己开了筛选。既然这里已经支持 `offset`,筛选就该和分页同层。
|
|
2218
|
+
*/
|
|
2219
|
+
const ext = (url.searchParams.get('ext') ?? '').trim()
|
|
2220
|
+
if (ext) documents = documents.filter(item => item.ext === ext)
|
|
2086
2221
|
const total = documents.length
|
|
2087
2222
|
const offset = clampInt(url.searchParams.get('offset'), 0, 0, Number.MAX_SAFE_INTEGER)
|
|
2088
2223
|
const limit = clampInt(url.searchParams.get('limit'), 50, 1, 500)
|
|
@@ -2093,7 +2228,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
2093
2228
|
* 两件事。放成并列的一张表,前端按 `docId` 查,文档类型不用为审计字段让步。
|
|
2094
2229
|
*/
|
|
2095
2230
|
const extractionRuns = await resolved.runtime.extractionRunCounts()
|
|
2096
|
-
return send(res, 200, success({ documents: documents.slice(offset, offset + limit), total, extractionRuns }))
|
|
2231
|
+
return send(res, 200, success({ documents: documents.slice(offset, offset + limit), total, extCounts, extractionRuns }))
|
|
2097
2232
|
}
|
|
2098
2233
|
const docId = rest[1]!
|
|
2099
2234
|
const resolved = resolveBase(kbParam)
|
package/src/intent/scope.ts
CHANGED
package/src/intent/tools.ts
CHANGED
|
@@ -91,6 +91,40 @@ export function sessionKeyOf(exec?: ToolRunContext): string | undefined {
|
|
|
91
91
|
return typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : undefined
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
+
/**
|
|
95
|
+
* 这个会话是不是**子代理**。
|
|
96
|
+
*
|
|
97
|
+
* 子代理会加入父会话的组合,所以它会继承到 `intent_*` 两个工具。而意图记录描述的是
|
|
98
|
+
* 「用户在跟 Agent 要什么」:子代理做的是被拆出来的一个片段,它记一条只会污染任务树。
|
|
99
|
+
*
|
|
100
|
+
* 这条规矩原来写在 preset 的 delegation 行上(`toolFilter: { deny: [...] }`)。DSH 0.1.8
|
|
101
|
+
* 起 `toolFilter`(与 `persona`)**不再被 `dsh-tool-subagent` 接受** —— 带上它的 preset
|
|
102
|
+
* 直接挂载失败,整个组合都不会生效。平台给的替代是「在 Agent Preset 里配好工具,再用
|
|
103
|
+
* `agentPreset` 选中它」,而那**只对 spawn provider 有效**:fork / codex / claude-code
|
|
104
|
+
* 的子会话按定义继承父会话的组合,没有选择余地(`inheritsParentContext` 直接拒绝)。
|
|
105
|
+
*
|
|
106
|
+
* 所以判据搬进工具本身:看会话头,与「谁把子会话拉起来的」无关。四个 provider 一视同仁,
|
|
107
|
+
* 也不会再出现「新增一个 delegation 行忘了 deny」这种漏法。
|
|
108
|
+
*
|
|
109
|
+
* 判据取两个字段:`origin === 'subagent'` 是产品分类,`delegationDepth > 0` 是递归深度
|
|
110
|
+
* (持久化过,重启后仍然成立)。任一条成立就算子会话。
|
|
111
|
+
*/
|
|
112
|
+
export function isSubagentSession(exec?: ToolRunContext): boolean {
|
|
113
|
+
const header = exec?.agent?.session?.header
|
|
114
|
+
if (!header) return false
|
|
115
|
+
if (header.origin === 'subagent') return true
|
|
116
|
+
return typeof header.delegationDepth === 'number' && header.delegationDepth > 0
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** 子代理调这两个工具时统一回这一条:说清为什么,并给出该走的路。 */
|
|
120
|
+
function subagentRefusal(): unknown {
|
|
121
|
+
return envelopeFailure({ sessionKey: null }, {
|
|
122
|
+
code: 'SUBAGENT_NOT_RECORDED',
|
|
123
|
+
message: '子代理不记录意图:意图描述的是用户与主 Agent 的交互。把要记的内容写进返回结果,由主 Agent 记录。',
|
|
124
|
+
retryable: false,
|
|
125
|
+
})
|
|
126
|
+
}
|
|
127
|
+
|
|
94
128
|
/**
|
|
95
129
|
* 注册两个 `intent_*` 工具,返回一个把两者一起撤掉的 disposer。
|
|
96
130
|
*
|
|
@@ -100,11 +134,14 @@ export function sessionKeyOf(exec?: ToolRunContext): string | undefined {
|
|
|
100
134
|
* 返回 disposer。
|
|
101
135
|
*/
|
|
102
136
|
export function registerIntentTools(ctx: { tools: ToolRegistryLike }, host: IntentToolHost): () => void {
|
|
103
|
-
const disposeRecord: unknown = ctx.tools.register(jsonTool('intent_record', host.routingPrompt(), INTENT_RECORD_PARAMS, (args, exec) =>
|
|
137
|
+
const disposeRecord: unknown = ctx.tools.register(jsonTool('intent_record', host.routingPrompt(), INTENT_RECORD_PARAMS, (args, exec) => (
|
|
138
|
+
isSubagentSession(exec) ? subagentRefusal() : host.record(args, exec)
|
|
139
|
+
)))
|
|
104
140
|
const disposeView: unknown = ctx.tools.register(jsonTool(
|
|
105
141
|
'intent_store_view', 'View recent intent records and the active task ids for the current session.',
|
|
106
142
|
{ include_prompt: { type: 'boolean' } },
|
|
107
143
|
(args: { include_prompt?: boolean }, exec) => {
|
|
144
|
+
if (isSubagentSession(exec)) return subagentRefusal()
|
|
108
145
|
const sessionKey = sessionKeyOf(exec)
|
|
109
146
|
if (!sessionKey) {
|
|
110
147
|
return envelopeFailure({ sessionKey: null }, {
|