dsh-data-domain-knowledge 0.1.0-alpha.9 → 0.1.1-alpha.2
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 +118 -567
- package/cordis.patch.yml +5 -418
- package/eval/README.md +19 -8
- package/eval/benchmark-extraction-format.mjs +20 -0
- package/eval/evaluate-retrieval.mjs +17 -8
- package/eval/queries.example.jsonl +1 -0
- package/lib/client.js +4181 -3239
- package/lib/contract-envelope.d.ts +91 -0
- package/lib/contract-envelope.js +106 -0
- package/lib/contract-layout.d.ts +130 -0
- package/lib/contract-layout.js +138 -0
- package/lib/contract-refs.d.ts +39 -0
- package/lib/contract-refs.js +9 -0
- package/lib/contract-version.d.ts +311 -0
- package/lib/contract-version.js +270 -0
- package/lib/contract-wire.d.ts +147 -0
- package/lib/contract-wire.js +99 -0
- package/lib/contracts.d.ts +12 -0
- package/lib/contracts.js +12 -0
- package/lib/doc-page.js +2 -2
- package/lib/pages.js +3329 -1296
- package/lib/types/agent-behavior-rules.d.ts +10 -0
- package/lib/types/agent-behavior-rules.d.ts.map +1 -0
- package/lib/types/agent-behavior-rules.js +28 -0
- package/lib/types/agent-behavior-rules.js.map +1 -0
- package/lib/types/axis-registry.d.ts +37 -5
- package/lib/types/axis-registry.d.ts.map +1 -1
- package/lib/types/axis-registry.js +234 -26
- package/lib/types/axis-registry.js.map +1 -1
- package/lib/types/card-organize.d.ts +27 -12
- package/lib/types/card-organize.d.ts.map +1 -1
- package/lib/types/card-organize.js +58 -73
- package/lib/types/card-organize.js.map +1 -1
- package/lib/types/compiler/embedding-identity.d.ts +3 -0
- package/lib/types/compiler/embedding-identity.d.ts.map +1 -0
- package/lib/types/compiler/embedding-identity.js +24 -0
- package/lib/types/compiler/embedding-identity.js.map +1 -0
- package/lib/types/compiler/snapshot.d.ts +16 -0
- package/lib/types/compiler/snapshot.d.ts.map +1 -0
- package/lib/types/compiler/snapshot.js +2 -0
- package/lib/types/compiler/snapshot.js.map +1 -0
- package/lib/types/config-schema.d.ts +6 -3
- package/lib/types/config-schema.d.ts.map +1 -1
- package/lib/types/config-schema.js +6 -2
- package/lib/types/config-schema.js.map +1 -1
- package/lib/types/convert/host.d.ts +6 -5
- package/lib/types/convert/host.d.ts.map +1 -1
- package/lib/types/convert/host.js +58 -18
- package/lib/types/convert/host.js.map +1 -1
- package/lib/types/convert/ingest.d.ts +10 -1
- 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/derive-contract.d.ts +3 -0
- package/lib/types/derive-contract.d.ts.map +1 -0
- package/lib/types/derive-contract.js +15 -0
- package/lib/types/derive-contract.js.map +1 -0
- 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 +24 -18
- package/lib/types/diagnosis/store.d.ts.map +1 -1
- package/lib/types/diagnosis/store.js +130 -65
- package/lib/types/diagnosis/store.js.map +1 -1
- package/lib/types/diagnosis/types.d.ts +47 -15
- 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 +15 -0
- package/lib/types/environment/install.d.ts.map +1 -0
- package/lib/types/environment/install.js +297 -0
- package/lib/types/environment/install.js.map +1 -0
- package/lib/types/environment/manifest.d.ts +208 -0
- package/lib/types/environment/manifest.d.ts.map +1 -0
- package/lib/types/environment/manifest.js +26 -0
- package/lib/types/environment/manifest.js.map +1 -0
- package/lib/types/environment/model-catalog.d.ts +223 -0
- package/lib/types/environment/model-catalog.d.ts.map +1 -0
- package/lib/types/environment/model-catalog.js +129 -0
- package/lib/types/environment/model-catalog.js.map +1 -0
- package/lib/types/environment/process.d.ts +10 -0
- package/lib/types/environment/process.d.ts.map +1 -0
- package/lib/types/environment/process.js +54 -0
- package/lib/types/environment/process.js.map +1 -0
- package/lib/types/environment/selection.d.ts +9 -0
- package/lib/types/environment/selection.d.ts.map +1 -0
- package/lib/types/environment/selection.js +29 -0
- package/lib/types/environment/selection.js.map +1 -0
- package/lib/types/environment/service.d.ts +64 -0
- package/lib/types/environment/service.d.ts.map +1 -0
- package/lib/types/environment/service.js +424 -0
- package/lib/types/environment/service.js.map +1 -0
- package/lib/types/environment/types.d.ts +56 -0
- package/lib/types/environment/types.d.ts.map +1 -0
- package/lib/types/environment/types.js +2 -0
- package/lib/types/environment/types.js.map +1 -0
- package/lib/types/events.d.ts +76 -0
- package/lib/types/events.d.ts.map +1 -0
- package/lib/types/events.js +52 -0
- package/lib/types/events.js.map +1 -0
- package/lib/types/extract-contract.d.ts +3 -0
- package/lib/types/extract-contract.d.ts.map +1 -0
- package/lib/types/extract-contract.js +41 -0
- package/lib/types/extract-contract.js.map +1 -0
- package/lib/types/extract-model.d.ts +5 -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 +27 -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 +158 -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 +26 -0
- package/lib/types/images.d.ts.map +1 -0
- package/lib/types/images.js +196 -0
- package/lib/types/images.js.map +1 -0
- package/lib/types/index.d.ts +2 -2
- package/lib/types/index.d.ts.map +1 -1
- package/lib/types/index.js +598 -209
- package/lib/types/index.js.map +1 -1
- package/lib/types/intent/engine.d.ts +12 -4
- package/lib/types/intent/engine.d.ts.map +1 -1
- package/lib/types/intent/engine.js +43 -5
- package/lib/types/intent/engine.js.map +1 -1
- package/lib/types/intent/host.d.ts +5 -8
- package/lib/types/intent/host.d.ts.map +1 -1
- package/lib/types/intent/host.js +56 -11
- package/lib/types/intent/host.js.map +1 -1
- package/lib/types/intent/scope.d.ts +25 -0
- package/lib/types/intent/scope.d.ts.map +1 -0
- package/lib/types/intent/scope.js +116 -0
- package/lib/types/intent/scope.js.map +1 -0
- package/lib/types/intent/tools.d.ts +24 -20
- package/lib/types/intent/tools.d.ts.map +1 -1
- package/lib/types/intent/tools.js +65 -23
- package/lib/types/intent/tools.js.map +1 -1
- package/lib/types/intent/types.d.ts +11 -4
- package/lib/types/intent/types.d.ts.map +1 -1
- package/lib/types/knowledge-bases.d.ts +11 -5
- package/lib/types/knowledge-bases.d.ts.map +1 -1
- package/lib/types/knowledge-bases.js +52 -20
- package/lib/types/knowledge-bases.js.map +1 -1
- package/lib/types/knowledge-contract.d.ts +32 -0
- package/lib/types/knowledge-contract.d.ts.map +1 -0
- package/lib/types/knowledge-contract.js +116 -0
- package/lib/types/knowledge-contract.js.map +1 -0
- package/lib/types/knowledge.d.ts +31 -9
- package/lib/types/knowledge.d.ts.map +1 -1
- package/lib/types/knowledge.js +137 -60
- package/lib/types/knowledge.js.map +1 -1
- package/lib/types/layout.d.ts +10 -29
- package/lib/types/layout.d.ts.map +1 -1
- package/lib/types/layout.js +17 -36
- package/lib/types/layout.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/migrate.d.ts +11 -0
- package/lib/types/migrate.d.ts.map +1 -1
- package/lib/types/migrate.js +83 -2
- package/lib/types/migrate.js.map +1 -1
- package/lib/types/model-text.d.ts +58 -1
- package/lib/types/model-text.d.ts.map +1 -1
- package/lib/types/model-text.js +85 -30
- 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/organize-contract.d.ts +18 -0
- package/lib/types/organize-contract.d.ts.map +1 -0
- package/lib/types/organize-contract.js +82 -0
- package/lib/types/organize-contract.js.map +1 -0
- package/lib/types/pipeline/engine.d.ts +66 -0
- package/lib/types/pipeline/engine.d.ts.map +1 -0
- package/lib/types/pipeline/engine.js +352 -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 +102 -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/preset-ids.d.ts +4 -0
- package/lib/types/preset-ids.d.ts.map +1 -0
- package/lib/types/preset-ids.js +6 -0
- package/lib/types/preset-ids.js.map +1 -0
- package/lib/types/query-analysis.d.ts +13 -1
- package/lib/types/query-analysis.d.ts.map +1 -1
- package/lib/types/query-analysis.js +62 -6
- package/lib/types/query-analysis.js.map +1 -1
- package/lib/types/recall-rank.d.ts +3 -3
- package/lib/types/recall-rank.d.ts.map +1 -1
- package/lib/types/recall-rank.js +4 -4
- package/lib/types/recall-rank.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/retrieval/vector-index.d.ts +32 -0
- package/lib/types/retrieval/vector-index.d.ts.map +1 -0
- package/lib/types/retrieval/vector-index.js +15 -0
- package/lib/types/retrieval/vector-index.js.map +1 -0
- package/lib/types/retrieval/visibility.d.ts +6 -0
- package/lib/types/retrieval/visibility.d.ts.map +1 -0
- package/lib/types/retrieval/visibility.js +11 -0
- package/lib/types/retrieval/visibility.js.map +1 -0
- package/lib/types/runtime.d.ts +272 -27
- package/lib/types/runtime.d.ts.map +1 -1
- package/lib/types/runtime.js +2751 -537
- package/lib/types/runtime.js.map +1 -1
- package/lib/types/settings-schema.d.ts +17 -15
- package/lib/types/settings-schema.d.ts.map +1 -1
- package/lib/types/settings-schema.js +50 -47
- package/lib/types/settings-schema.js.map +1 -1
- package/lib/types/settings-volatile.d.ts +4 -4
- package/lib/types/settings-volatile.js +4 -4
- package/lib/types/source-evidence.d.ts +14 -0
- package/lib/types/source-evidence.d.ts.map +1 -0
- package/lib/types/source-evidence.js +36 -0
- package/lib/types/source-evidence.js.map +1 -0
- package/lib/types/store/axis-references.d.ts +5 -0
- package/lib/types/store/axis-references.d.ts.map +1 -0
- package/lib/types/store/axis-references.js +106 -0
- package/lib/types/store/axis-references.js.map +1 -0
- package/lib/types/store/derived.d.ts +12 -1
- package/lib/types/store/derived.d.ts.map +1 -1
- package/lib/types/store/derived.js +71 -12
- package/lib/types/store/derived.js.map +1 -1
- package/lib/types/store/knowledge-db.d.ts +31 -1
- package/lib/types/store/knowledge-db.d.ts.map +1 -1
- package/lib/types/store/knowledge-db.js +252 -18
- package/lib/types/store/knowledge-db.js.map +1 -1
- package/lib/types/store/migrate.d.ts.map +1 -1
- package/lib/types/store/migrate.js +17 -6
- 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 +59 -27
- package/lib/types/types.d.ts.map +1 -1
- package/lib/types/types.js +6 -15
- 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 +4 -0
- package/lib/types/wiki-materialize.d.ts.map +1 -1
- package/lib/types/wiki-materialize.js +21 -7
- package/lib/types/wiki-materialize.js.map +1 -1
- package/lib/types/wiki-trial-types.d.ts +87 -0
- package/lib/types/wiki-trial-types.d.ts.map +1 -1
- package/lib/types/wiki-trial-types.js +0 -16
- package/lib/types/wiki-trial-types.js.map +1 -1
- package/lib/types/wiki-trial.d.ts +40 -16
- package/lib/types/wiki-trial.d.ts.map +1 -1
- package/lib/types/wiki-trial.js +337 -224
- package/lib/types/wiki-trial.js.map +1 -1
- package/package.json +29 -20
- package/python/prepare-models.py +19 -0
- package/python/requirements.txt +8 -6
- package/scripts/doctor.mjs +13 -19
- package/scripts/environment.mjs +29 -0
- package/scripts/setup-embedding.sh +15 -5
- package/src/agent-behavior-rules.ts +37 -0
- package/src/axis-registry.ts +234 -27
- package/src/card-organize.ts +69 -75
- package/src/client/AgentBehaviorRulesCard.tsx +65 -0
- package/src/client/AttributionEventGuide.tsx +41 -39
- package/src/client/AxisRegistrySection.tsx +204 -164
- package/src/client/CompileDialogs.tsx +44 -29
- package/src/client/DeleteDocumentDialog.tsx +36 -5
- package/src/client/DirectoryPicker.tsx +0 -10
- package/src/client/EnvironmentSetup.tsx +243 -0
- package/src/client/ExpansionGraph.tsx +13 -7
- package/src/client/ExtractionTasksPage.tsx +578 -0
- package/src/client/GuideArchitectureCanvas.tsx +470 -0
- package/src/client/ImportFilesDialog.tsx +2 -13
- package/src/client/IntentLogic.tsx +2 -2
- package/src/client/IntentTab.tsx +118 -47
- package/src/client/KnowledgeBaseTab.tsx +277 -197
- package/src/client/KnowledgeLayerSection.tsx +24 -6
- package/src/client/KnowledgePanel.tsx +58 -93
- package/src/client/KnowledgeSettingsDrawer.tsx +90 -0
- package/src/client/ListPager.tsx +15 -5
- package/src/client/MaterializeTrajectoryView.tsx +124 -0
- package/src/client/NodeSettings.tsx +210 -89
- package/src/client/NormalizeTrajectoryView.tsx +136 -0
- package/src/client/PipelineNotice.tsx +26 -0
- package/src/client/PreviewSections.tsx +118 -51
- package/src/client/RecallPanel.tsx +89 -85
- package/src/client/TabGuide.tsx +159 -184
- package/src/client/TrialReportView.tsx +391 -214
- package/src/client/TypeDefinitionDrawer.tsx +150 -23
- package/src/client/WikiCardPreview.tsx +62 -16
- package/src/client/WikiFlowPanel.tsx +4 -4
- package/src/client/arch/NodeLogicPanel.tsx +3 -4
- package/src/client/arch/model.ts +6 -9
- package/src/client/architecture.tsx +23 -33
- package/src/client/attribution/AttributionGraph.tsx +10 -6
- package/src/client/attribution/AttributionTab.tsx +136 -84
- package/src/client/attribution/CaseView.tsx +3 -1
- package/src/client/attribution/EventView.tsx +108 -0
- package/src/client/attribution/RunView.tsx +2 -2
- package/src/client/attribution/StageChrome.tsx +34 -3
- package/src/client/attribution/ViewSwitch.tsx +20 -5
- package/src/client/attribution/graph-options.ts +47 -39
- package/src/client/attribution/model.ts +17 -10
- package/src/client/config-scope.ts +3 -3
- package/src/client/graph/palette.ts +165 -0
- package/src/client/index.ts +49 -42
- package/src/client/pages.css +459 -135
- package/src/client/pages.tsx +37 -9
- package/src/client/problem-domains.ts +1 -1
- package/src/client/session-remote.ts +9 -0
- package/src/client/styles.css +205 -62
- package/src/client/wiki-trial.ts +44 -3
- package/src/compiler/embedding-identity.ts +21 -0
- package/src/compiler/snapshot.ts +17 -0
- package/src/config-schema.ts +8 -3
- package/src/convert/host.ts +56 -21
- package/src/convert/ingest.ts +74 -10
- package/src/derive-contract.ts +14 -0
- 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 +138 -69
- package/src/diagnosis/types.ts +34 -15
- package/src/environment/dsh-python.ts +156 -0
- package/src/environment/install.ts +244 -0
- package/src/environment/manifest.ts +28 -0
- package/src/environment/model-catalog.ts +147 -0
- package/src/environment/process.ts +54 -0
- package/src/environment/selection.ts +29 -0
- package/src/environment/service.ts +348 -0
- package/src/environment/types.ts +45 -0
- package/src/events.ts +108 -0
- package/src/extract-contract.ts +42 -0
- package/src/extract-model.ts +10 -1
- package/src/extract-table-contract.ts +26 -0
- package/src/extract-table.ts +132 -0
- package/src/extraction-runs.ts +70 -11
- package/src/fs-atomic.ts +19 -2
- package/src/images.ts +203 -0
- package/src/index.ts +553 -205
- package/src/intent/README.md +8 -6
- package/src/intent/engine.ts +44 -6
- package/src/intent/host.ts +59 -18
- package/src/intent/scope.ts +125 -0
- package/src/intent/tools.ts +70 -28
- package/src/intent/types.ts +11 -4
- package/src/knowledge-bases.ts +56 -20
- package/src/knowledge-contract.ts +114 -0
- package/src/knowledge.ts +138 -69
- package/src/layout.ts +17 -37
- package/src/materialize-snapshot.ts +46 -0
- package/src/migrate.ts +75 -2
- package/src/model-text.ts +109 -4
- package/src/oplog.ts +7 -0
- package/src/organize-contract.ts +83 -0
- package/src/pipeline/engine.ts +323 -0
- package/src/pipeline/store.ts +147 -0
- package/src/pipeline/types.ts +93 -0
- package/src/present.ts +10 -4
- package/src/preset-ids.ts +6 -0
- package/src/python/embedding_worker.py +66 -2
- package/src/query-analysis.ts +68 -8
- package/src/recall-rank.ts +5 -5
- package/src/retention.ts +76 -0
- package/src/retrieval/vector-index.ts +45 -0
- package/src/retrieval/visibility.ts +14 -0
- package/src/runtime.ts +2708 -567
- package/src/settings-schema.ts +11 -8
- package/src/settings-volatile.ts +4 -4
- package/src/source-evidence.ts +37 -0
- package/src/store/axis-references.ts +96 -0
- package/src/store/derived.ts +70 -12
- package/src/store/knowledge-db.ts +248 -17
- package/src/store/migrate.ts +16 -6
- package/src/trajectory-types.ts +142 -0
- package/src/types.ts +61 -35
- package/src/wiki-finalize.ts +28 -0
- package/src/wiki-materialize.ts +25 -6
- package/src/wiki-trial-types.ts +83 -1
- package/src/wiki-trial.ts +357 -185
- package/lib/types/intent/preset-tools.d.ts +0 -37
- package/lib/types/intent/preset-tools.d.ts.map +0 -1
- package/lib/types/intent/preset-tools.js +0 -45
- package/lib/types/intent/preset-tools.js.map +0 -1
- package/preset/tita-data-agent/agent.cordis.yml +0 -394
- package/preset/tita-data-agent/preset.yml +0 -3
- package/scripts/install-agent-preset.mjs +0 -92
- package/scripts/sync-agent-preset.mjs +0 -188
- package/src/client/AgentPresetCard.tsx +0 -247
- package/src/client/agent-preset.ts +0 -121
- 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/intent/preset-tools.ts +0 -48
package/src/index.ts
CHANGED
|
@@ -1,17 +1,21 @@
|
|
|
1
|
+
import { embeddingModel } from './environment/model-catalog.js'
|
|
1
2
|
import type { IncomingMessage, ServerResponse } from 'node:http'
|
|
2
|
-
import { createReadStream
|
|
3
|
+
import { createReadStream } from 'node:fs'
|
|
3
4
|
import { readFile, stat } from 'node:fs/promises'
|
|
4
5
|
import { dirname, extname, isAbsolute, join, resolve, sep } from 'node:path'
|
|
5
6
|
import { fileURLToPath } from 'node:url'
|
|
6
7
|
import type { Context } from '@deepseek-ai/cordis'
|
|
8
|
+
import type {} from '@deepseek-ai/dsh-agent'
|
|
9
|
+
import type {} from '@deepseek-ai/dsh-system-prompt'
|
|
7
10
|
import z from '@deepseek-ai/schemastery'
|
|
8
11
|
import { defineTool, type PreToolDecision, type ToolRunContext } from '@deepseek-ai/dsh-tools'
|
|
9
|
-
import type { LineageContext } from '
|
|
12
|
+
import type { LineageContext } from '#contracts'
|
|
10
13
|
import { operationId, type OperationLog } from './oplog.js'
|
|
11
14
|
import { AXIS_GUIDANCE_KINDS, type AxisGuidanceKind } from './axis-registry.js'
|
|
12
15
|
import type { OperationDetail } from './types.js'
|
|
13
16
|
import {
|
|
14
|
-
DEFAULT_KNOWLEDGE_CONFIG, type CardStatus, type
|
|
17
|
+
DEFAULT_COMPILE_MODEL, DEFAULT_KNOWLEDGE_CONFIG, type CardStatus, type CompileModelSelection,
|
|
18
|
+
type Envelope, type KnowledgeBase, type KnowledgeConfig,
|
|
15
19
|
type KnowledgeStatus, type RecallMode, type SchemaType,
|
|
16
20
|
} from './types.js'
|
|
17
21
|
import {
|
|
@@ -26,16 +30,17 @@ import {
|
|
|
26
30
|
type ModelSeam,
|
|
27
31
|
} from './runtime.js'
|
|
28
32
|
import { unwrapGroup } from './settings-volatile.js'
|
|
33
|
+
import { behaviorPromptForPreset } from './agent-behavior-rules.js'
|
|
34
|
+
import { KnowledgeEnvironment } from './environment/service.js'
|
|
29
35
|
import { DOC_PAGE_CHARS, DOC_PAGE_CHARS_MAX, documentContentPage } from './document-page.js'
|
|
30
36
|
import type { ModelCall } from './extract-model.js'
|
|
31
37
|
import { runWikiTrial, trialSummary } from './wiki-trial.js'
|
|
32
38
|
import { extractionRunId } from './extraction-runs.js'
|
|
39
|
+
import type { PipelineStage } from './pipeline/types.js'
|
|
33
40
|
import {
|
|
34
|
-
agentLayout, defaultAgentRoot, ensureAgentLayout, intentRoot, knowledgeBaseRoot,
|
|
35
|
-
memoryRoot,
|
|
41
|
+
agentLayout, defaultAgentRoot, ensureAgentLayout, intentRoot, knowledgeBaseRoot,
|
|
42
|
+
memoryRoot, resourceRegistry, resourceRoot, type AgentLayout,
|
|
36
43
|
} from './layout.js'
|
|
37
|
-
import { migrateLegacyKnowledgeTree, migrateLegacyMemory, migrateLegacyResources, type MigrationOutcome } from './migrate.js'
|
|
38
|
-
import { legacyResourceHome } from './types.js'
|
|
39
44
|
import {
|
|
40
45
|
PickerError, createDirectory, listDirectory, pickDirectory, pickerKind, pickerOf,
|
|
41
46
|
} from './directory-picker.js'
|
|
@@ -44,6 +49,7 @@ import type { DiagnosisRunRecord } from './diagnosis/types.js'
|
|
|
44
49
|
import { KNOWLEDGE_SETTINGS_SCHEMA, SETTINGS_NAMESPACE } from './settings-schema.js'
|
|
45
50
|
import { DEFAULT_KB_ID, KnowledgeBases } from './knowledge-bases.js'
|
|
46
51
|
import type { KnowledgeQuery } from './knowledge.js'
|
|
52
|
+
import { ImagePublishError, publishKnowledgeImage } from './images.js'
|
|
47
53
|
import {
|
|
48
54
|
applyConvert, CONVERT_SETTINGS_NAMESPACE, CONVERT_SETTINGS_SCHEMA, MOUNT_PATH as CONVERT_MOUNT_PATH,
|
|
49
55
|
type ConvertHost,
|
|
@@ -53,7 +59,9 @@ import {
|
|
|
53
59
|
applyIntent, INTENT_MOUNT_PATH, INTENT_SERVICE,
|
|
54
60
|
type IntentHost,
|
|
55
61
|
type IntentLineageService,
|
|
62
|
+
type IntentService,
|
|
56
63
|
} from './intent/host.js'
|
|
64
|
+
import { registerIntentTools } from './intent/tools.js'
|
|
57
65
|
|
|
58
66
|
export * from './types.js'
|
|
59
67
|
export * from './runtime.js'
|
|
@@ -68,14 +76,14 @@ export const name = 'dsh-data-domain-knowledge'
|
|
|
68
76
|
* `settings` 在 0.1.7 之后**不再需要**:设置已经是本 entry 的 config
|
|
69
77
|
* (见下面的 `Config`),`settings.installSection` 那条路已经整个删除。
|
|
70
78
|
*/
|
|
71
|
-
export const inject = ['tools', 'webServer'] as const
|
|
79
|
+
export const inject = ['tools', 'webServer', 'systemPrompt'] as const
|
|
72
80
|
// schema 与命名空间搬到了 `settings-schema.ts`(可测)。这里再导出一次,保持
|
|
73
81
|
// `dsh-data-domain-knowledge` 的既有出口不变。
|
|
74
82
|
export { KNOWLEDGE_SETTINGS_SCHEMA, SETTINGS_NAMESPACE }
|
|
75
83
|
export { CONVERT_SETTINGS_SCHEMA }
|
|
76
84
|
/**
|
|
77
85
|
* 组合 schema 与它的取值形态住在 `./config-schema.js`:那里**没有工具层依赖**,
|
|
78
|
-
* 所以 `tests/settings-schema.test.mjs` 能 import
|
|
86
|
+
* 所以 `tests/settings-schema.test.mjs` 能 import 它并把设置解析一遍(装载器
|
|
79
87
|
* 仍然要从**本模块**读到 `Config`,所以这里 import 之后再 re-export)。
|
|
80
88
|
*
|
|
81
89
|
* 0.1.7 删掉了「设置命名空间 + `settings.installSection`」这一整套 API,改成
|
|
@@ -84,7 +92,7 @@ export { CONVERT_SETTINGS_SCHEMA }
|
|
|
84
92
|
* 客户端按**路径**寻址(例如 `{op:'set', path:['knowledge','recallTopK']}`)。
|
|
85
93
|
*/
|
|
86
94
|
import { Config, type KnowledgePluginConfig } from './config-schema.js'
|
|
87
|
-
import { buildModelRequest, collectModelText } from './model-text.js'
|
|
95
|
+
import { buildModelRequest, callModelWithRetry, collectModelText } from './model-text.js'
|
|
88
96
|
import {
|
|
89
97
|
addSourceCallView, addSourceMetaOf, addSourceResultMarkdown,
|
|
90
98
|
importCallView, importMetaOf, importResultMarkdown,
|
|
@@ -263,7 +271,7 @@ function escapeHtml(value: string): string {
|
|
|
263
271
|
/**
|
|
264
272
|
* 独立整页的 HTML 外壳。
|
|
265
273
|
*
|
|
266
|
-
*
|
|
274
|
+
* 与知识库原文整页相同的挂载形状,而且**只做三件事**:标题、挂载点、把自己那个 bundle
|
|
267
275
|
* 拉起来。正文不在外壳里 —— 页面自己去 `/knowledge/documents/:docId/content` 一页一页地取,
|
|
268
276
|
* 所以这份 HTML 的大小与文档大小无关。
|
|
269
277
|
*/
|
|
@@ -372,29 +380,23 @@ function normalizeKnowledge(next: Partial<KnowledgeConfig> | undefined): Knowled
|
|
|
372
380
|
return {
|
|
373
381
|
...DEFAULT_KNOWLEDGE_CONFIG,
|
|
374
382
|
...plain,
|
|
375
|
-
|
|
376
|
-
|
|
383
|
+
// 根目录是 DSH 下的 Tita Agent 专属目录,不再是用户可切换的设置。
|
|
384
|
+
// 旧值即使还存在于宿主配置中,也不参与路径解析。
|
|
385
|
+
agentRoot: defaultAgentRoot(),
|
|
386
|
+
// 仅保留字段形状,避免旧配置无法解析;新逻辑不读取旧知识树。
|
|
387
|
+
rootDir: '',
|
|
388
|
+
// 产品检索固定使用多路径融合。旧版单路选择仍可解析,但不再改变运行行为。
|
|
389
|
+
recallMode: 'hybrid',
|
|
377
390
|
}
|
|
378
391
|
}
|
|
379
392
|
|
|
380
393
|
|
|
381
394
|
export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): void {
|
|
382
|
-
/**
|
|
383
|
-
* 0029:配置里现在有两个路径字段,但只有 `agentRoot` 是「设置」。
|
|
384
|
-
*
|
|
385
|
-
* `rootDir` 是旧布局的知识树根,只作为迁移来源存在。初始值给空串而不是旧默认值
|
|
386
|
-
* ——迁移的来源由下面的 `legacySource()` 决定,它还要考虑「没设过 rootDir 的
|
|
387
|
-
* 用户,旧树在旧默认位置」这一种情况。
|
|
388
|
-
*
|
|
389
|
-
* 0.1.7:值来自本 entry 的 `config`(`apply` 的第二个参数),不再是
|
|
390
|
-
* `settings.installSection` 推回来的命名空间。这里先 `normalizeKnowledge` 补齐,
|
|
391
|
-
* 之后的迁移与 runtime 都只认这一份配置。
|
|
392
|
-
*/
|
|
393
395
|
let config: KnowledgeConfig = normalizeKnowledge(pluginConfig.knowledge)
|
|
394
396
|
/**
|
|
395
397
|
* entry config 的另外两组,**按需求值**。
|
|
396
398
|
*
|
|
397
|
-
*
|
|
399
|
+
* 为什么是函数而不是先解包成一个值:`convert` 的可编辑字段标了 volatile,schema
|
|
398
400
|
* 解析出来是引用;解包一次就是一张快照,而 volatile 改动**不会重载插件**
|
|
399
401
|
* (loader 只就地改引用、再发一个 `loader/volatile-update`)。函数让下游每次
|
|
400
402
|
* 「重灌」都从同一个引用重新读值,改设置才真的会生效。
|
|
@@ -409,38 +411,16 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
409
411
|
* `applyIntent`)。
|
|
410
412
|
*/
|
|
411
413
|
let convertHost: ConvertHost | undefined
|
|
412
|
-
/** 知识树迁移的结论。`/knowledge/layout` 会带上它,界面据此如实交代数据搬没搬。 */
|
|
413
|
-
let migration: MigrationOutcome = { status: 'skipped', reason: '尚未解析设置' }
|
|
414
|
-
/** 家当(模型 / Python 环境 / 注册表)迁移的结论。 */
|
|
415
|
-
let resourceMigration: MigrationOutcome = { status: 'skipped', reason: '尚未解析设置' }
|
|
416
|
-
/** 记忆段(知识树 → `<agentRoot>/memory/<库 id>`)迁移的结论(0032)。 */
|
|
417
|
-
let memoryMigration: MigrationOutcome = { status: 'skipped', reason: '尚未解析设置' }
|
|
418
414
|
/**
|
|
419
415
|
* 初始化失败的原因(如果有)。字段名保持 `settingsError`:`/knowledge/layout`
|
|
420
416
|
* 与面板上的告警都按这个名字读,改名的收益不如改语义的成本高。
|
|
421
417
|
*
|
|
422
418
|
* 旧的理由是「设置分区注册失败会被静默吞掉」(v0.7.5 的
|
|
423
419
|
* `z.string().min(1).default('')` 让整个命名空间注册不上)。0.1.7 之后 schema
|
|
424
|
-
* 错误会在装载 entry
|
|
425
|
-
*
|
|
420
|
+
* 错误会在装载 entry 时就抛、插件直接加载失败,不再是静默的;这里仍保留错误
|
|
421
|
+
* 视图,便于报告布局初始化失败。
|
|
426
422
|
*/
|
|
427
423
|
let settingsError: string | undefined
|
|
428
|
-
/**
|
|
429
|
-
* 迁移只该发生一次 —— 实际含义是「每次 apply 最多一次」。
|
|
430
|
-
*
|
|
431
|
-
* 0.1.7 下改设置会让插件**整个重载**,apply 因此会再跑一遍。用「已经跑过」而不是
|
|
432
|
-
* 「结果如何」做闸门,是为了不让一个失败的迁移在重载时被反复重试。
|
|
433
|
-
*/
|
|
434
|
-
let migrationDone = false
|
|
435
|
-
|
|
436
|
-
/**
|
|
437
|
-
* 迁移的旧位置来源。
|
|
438
|
-
*
|
|
439
|
-
* 显式设过 `rootDir` 就用它;否则退回旧默认位置(`DSH_KNOWLEDGE_ROOT` 或
|
|
440
|
-
* `~/Desktop/titaagent/domain_knowledge`)。判据交给 `migrateLegacyKnowledgeTree`,
|
|
441
|
-
* 这里只负责「谁可能是旧树」。
|
|
442
|
-
*/
|
|
443
|
-
const legacySource = (): string => config.rootDir.trim() || legacyDefaultKnowledgeRoot()
|
|
444
424
|
|
|
445
425
|
/**
|
|
446
426
|
* #3b:把 **DSH 的默认模型**包成运行时认识的纯文本调用。
|
|
@@ -449,10 +429,13 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
449
429
|
* DSH 自己的 `agentDefaultModel`(设置里那一项)。插件再存一份,就会出现「设置里显示
|
|
450
430
|
* A、实际调的是 B」—— 0029 想收拢的正是这类两处说法。
|
|
451
431
|
*
|
|
452
|
-
* 取不到 `llm`
|
|
453
|
-
*
|
|
432
|
+
* 取不到 `llm` 或所选模型时返回原因,链路**退回纯规则抽取**:模型是增强,不是前提。
|
|
433
|
+
* 选择“跟随默认”时才读取 `agentDefaultModel`;固定模型不依赖默认模型服务。
|
|
454
434
|
*/
|
|
455
|
-
|
|
435
|
+
/** 注册表在默认 runtime 之后才构造;用一个可替换的读取器避开构造期 TDZ。 */
|
|
436
|
+
let compileModelForBase: (id: string) => CompileModelSelection = () => ({ ...DEFAULT_COMPILE_MODEL })
|
|
437
|
+
|
|
438
|
+
const modelSeamFor = (kbId = DEFAULT_KB_ID): ModelSeam => {
|
|
456
439
|
/**
|
|
457
440
|
* **只能用 `ctx.get(name)`,不能用属性访问。**
|
|
458
441
|
*
|
|
@@ -464,8 +447,11 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
464
447
|
*
|
|
465
448
|
* 本机探针(cordis 4.0.4)实测:`ctx.get` 能看见后来才注册的服务,属性访问抛异常。
|
|
466
449
|
*/
|
|
467
|
-
const llm = ctx.get('llm') as
|
|
468
|
-
|
|
450
|
+
const llm = ctx.get('llm') as {
|
|
451
|
+
stream(options: Record<string, unknown>): AsyncIterable<{ type?: string; text?: string }>
|
|
452
|
+
resolveCallConfig?: (config: Record<string, unknown>) => Promise<unknown>
|
|
453
|
+
listModels?: (provider: string) => Promise<Array<{ id: string; name?: string; description?: string }>>
|
|
454
|
+
} | undefined
|
|
469
455
|
if (!llm) return { why: '当前上下文里取不到 `llm` 服务(模型调用入口)' }
|
|
470
456
|
|
|
471
457
|
/**
|
|
@@ -477,16 +463,48 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
477
463
|
* 所以那个类自己也是用 `this.config.provider.get()` 读的。第一版就是按普通属性读的,
|
|
478
464
|
* 结果是永远拿到 undefined —— 模型接缝静默失效。
|
|
479
465
|
*/
|
|
480
|
-
const
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
if (
|
|
485
|
-
|
|
466
|
+
const configured = compileModelForBase(kbId)
|
|
467
|
+
let defaultProvider: string | undefined
|
|
468
|
+
let defaultModel: string | undefined
|
|
469
|
+
let reasoningEffort: string | undefined
|
|
470
|
+
if (configured.mode === 'default') {
|
|
471
|
+
const service = ctx.get('agentDefaultModel') as
|
|
472
|
+
{ currentSelection?: () => { provider?: string; model?: string; reasoningEffort?: string } } | undefined
|
|
473
|
+
if (!service) return { why: '当前上下文里取不到 `agentDefaultModel` 服务 —— 默认模型那一行可能没挂上' }
|
|
474
|
+
const route = service.currentSelection?.()
|
|
475
|
+
defaultProvider = route?.provider
|
|
476
|
+
defaultModel = route?.model
|
|
477
|
+
reasoningEffort = route?.reasoningEffort
|
|
478
|
+
if (!defaultProvider || !defaultModel) {
|
|
479
|
+
return { why: `默认模型没有选好(provider=${defaultProvider ?? '空'} / model=${defaultModel ?? '空'})` }
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
const provider = configured.mode === 'fixed' ? configured.provider : defaultProvider
|
|
483
|
+
const model = configured.mode === 'fixed' ? configured.model : defaultModel
|
|
484
|
+
const effectiveReasoningEffort = configured.reasoningEffort || (configured.mode === 'default' ? reasoningEffort : undefined)
|
|
485
|
+
if (!provider || !model) {
|
|
486
|
+
return { why: `编译模型没有选好(provider=${provider ?? '空'} / model=${model ?? '空'})` }
|
|
486
487
|
}
|
|
487
|
-
const { provider, model, reasoningEffort } = route
|
|
488
488
|
return {
|
|
489
|
-
|
|
489
|
+
provider,
|
|
490
|
+
model,
|
|
491
|
+
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
492
|
+
call: async ({ system, prompt, sessionId, onUsage, onDelta }) => {
|
|
493
|
+
/**
|
|
494
|
+
* 先走 DSH 的公开解析接口,再开始发 prompt。这样固定模型过期、默认模型失效时,
|
|
495
|
+
* 编译会在模型调用边界明确失败,而不是落成一次看似成功的规则抽取。
|
|
496
|
+
*/
|
|
497
|
+
if (llm.resolveCallConfig) {
|
|
498
|
+
try {
|
|
499
|
+
await llm.resolveCallConfig({
|
|
500
|
+
provider, model,
|
|
501
|
+
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
502
|
+
})
|
|
503
|
+
} catch (cause) {
|
|
504
|
+
const detail = cause instanceof Error ? cause.message : String(cause)
|
|
505
|
+
throw new Error(`编译模型不可用(${provider}/${model}):${detail}`)
|
|
506
|
+
}
|
|
507
|
+
}
|
|
490
508
|
/**
|
|
491
509
|
* 请求形状与收流都交给 `model-text.ts`(0044 实机验证第三~五批)。
|
|
492
510
|
*
|
|
@@ -497,16 +515,34 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
497
515
|
* (OpenCode 系路由回 400 `MissingSessionID`)。
|
|
498
516
|
*/
|
|
499
517
|
const request = buildModelRequest({
|
|
500
|
-
provider, model, reasoningEffort, system, prompt,
|
|
518
|
+
provider, model, reasoningEffort: effectiveReasoningEffort, system, prompt,
|
|
501
519
|
// 运行时按知识库给;接缝被单独调用时(测试)退回一个稳定值,
|
|
502
520
|
// 而不是留空 —— 留空会在 OpenCode 路由上 400。
|
|
503
521
|
sessionId: sessionId ?? 'knowledge:default',
|
|
504
522
|
})
|
|
505
|
-
|
|
523
|
+
/**
|
|
524
|
+
* 流在**半路**结束要重发,不要一次失败就作废整份文档。
|
|
525
|
+
*
|
|
526
|
+
* 知识抽取是本插件里最长的一次模型调用(实机:约四万输出 token、两三分钟),
|
|
527
|
+
* 而上游网关 / 本机代理 / 账号额度任何一处抖一下,流就会在没给
|
|
528
|
+
* `finish_reason` 的情况下结束。适配器把这件事报成
|
|
529
|
+
* `TRANSPORT: Stream ended without finish_reason`,过去一次抖动就等于
|
|
530
|
+
* 「这份文档抽取失败」,人在界面上看到的是「全部失败」。
|
|
531
|
+
*
|
|
532
|
+
* 重发用的是**同一个请求**:提示词、sessionId、reasoningEffort 全不变,
|
|
533
|
+
* 所以缓存亲和仍然成立。不可重试的错误(凭据、请求体、截断、用户取消)
|
|
534
|
+
* 由 `isRetryableModelFailure` 放行,不在错的地方多等两分钟。
|
|
535
|
+
*/
|
|
536
|
+
return callModelWithRetry(
|
|
537
|
+
onDeltaOfAttempt => collectModelText(llm.stream(request) as AsyncIterable<never>, onUsage, onDeltaOfAttempt),
|
|
538
|
+
{ ...(onDelta ? { onDelta } : {}) },
|
|
539
|
+
)
|
|
506
540
|
},
|
|
507
541
|
}
|
|
508
542
|
}
|
|
543
|
+
let environment: KnowledgeEnvironment
|
|
509
544
|
const runtimeOptions = (): KnowledgeRuntimeOptions => ({
|
|
545
|
+
environmentGuard: kind => environment.requireReady(kind),
|
|
510
546
|
// 0029:家当目录。配置里三个路径为空时,运行时从这里解析模型与解释器。
|
|
511
547
|
resourceRoot: resourceRoot(config.agentRoot),
|
|
512
548
|
// 0032:记忆段。默认库的记忆落在 `<agentRoot>/memory/default`,与知识树平级。
|
|
@@ -543,10 +579,119 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
543
579
|
// 0044:非默认库的 runtime 也要拿到模型接缝。只传 `config` 不够 —— 默认库走上面
|
|
544
580
|
// 那个 `new KnowledgeRuntime(... runtimeOptions())`,别的库走注册表这条支路,
|
|
545
581
|
// 两边必须都带上,否则「新建一个库 → 导入文档」静默变成规则抽取。
|
|
546
|
-
|
|
582
|
+
runtimeOptions: () => ({ resourceRoot: resourceRoot(config.agentRoot), modelSeam: modelSeamFor, environmentGuard: kind => environment.requireReady(kind) }),
|
|
547
583
|
})
|
|
584
|
+
compileModelForBase = id => bases.compileModelOf(id)
|
|
548
585
|
runtime.baseId = bases.defaultId
|
|
549
586
|
activeKnowledgeBases = bases
|
|
587
|
+
environment = new KnowledgeEnvironment({
|
|
588
|
+
resourceRoot: resourceRoot(config.agentRoot),
|
|
589
|
+
snapshot: () => ({ config: runtime.config, paths: runtime.resourcePaths() }),
|
|
590
|
+
probe: kind => runtime.environmentProbe(kind),
|
|
591
|
+
compileStatus: () => runtime.compileModelStatus(),
|
|
592
|
+
reload: () => { runtime.reloadResources(); bases.reloadResources() },
|
|
593
|
+
activateSelection: async selection => {
|
|
594
|
+
const keys = ['embeddingProvider', 'embeddingModel', 'embeddingDimension', 'rerankProvider', 'rerankModel'] as const
|
|
595
|
+
const previous = Object.fromEntries(keys.map(key => [key, config[key]]))
|
|
596
|
+
const managed = { embeddingProvider: 'wemm-python', embeddingModel: '', embeddingDimension: embeddingModel(selection.embedding).dimension, rerankProvider: 'bert-python', rerankModel: '' }
|
|
597
|
+
const write = async (values: Record<string, unknown>): Promise<void> => {
|
|
598
|
+
const group = pluginConfig.knowledge as unknown as Record<string, unknown> | undefined
|
|
599
|
+
if (!group) throw new Error('模型设置不可写')
|
|
600
|
+
for (const key of keys) {
|
|
601
|
+
const reference = group[key] as { write?: (value: unknown) => unknown } | undefined
|
|
602
|
+
if (reference && typeof reference.write === 'function') await reference.write(values[key])
|
|
603
|
+
else group[key] = values[key]
|
|
604
|
+
}
|
|
605
|
+
config = normalizeKnowledge(pluginConfig.knowledge)
|
|
606
|
+
runtime.updateConfig(config); bases.updateRuntimeConfig(config)
|
|
607
|
+
}
|
|
608
|
+
try { await write(managed) } catch (cause) { await write(previous); throw cause }
|
|
609
|
+
return () => {
|
|
610
|
+
const current = normalizeKnowledge(pluginConfig.knowledge)
|
|
611
|
+
// A later manual setting change belongs to the user and must survive rollback.
|
|
612
|
+
const restored = Object.fromEntries(keys.map(key => [key, current[key] === managed[key] ? previous[key] : current[key]]))
|
|
613
|
+
return write(restored)
|
|
614
|
+
}
|
|
615
|
+
},
|
|
616
|
+
})
|
|
617
|
+
// Reuse a matching persisted probe after restart; only stale or missing reports need a resource check.
|
|
618
|
+
if (environment.view().report.state !== 'ready') void environment.check(false).catch(() => undefined)
|
|
619
|
+
|
|
620
|
+
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 } }
|
|
621
|
+
const compileModelCatalog = async (kbId: string, queryModelRoute: { provider?: string; model?: string } = {}): Promise<{
|
|
622
|
+
selection: CompileModelSelection
|
|
623
|
+
default: { provider?: string; model?: string; reasoningEffort?: string; available: boolean }
|
|
624
|
+
models: CompileModelCatalogItem[]
|
|
625
|
+
why?: string
|
|
626
|
+
}> => {
|
|
627
|
+
const service = ctx.get('agentDefaultModel') as
|
|
628
|
+
{ currentSelection?: () => { provider?: string; model?: string; reasoningEffort?: string } } | undefined
|
|
629
|
+
const defaultRoute = service?.currentSelection?.() ?? {}
|
|
630
|
+
const llm = ctx.get('llm') as {
|
|
631
|
+
listModels?: (provider: string) => Promise<Array<{ id: string; name?: string; description?: string }>>
|
|
632
|
+
listProviders?: () => Array<{ id: string; name: string }>
|
|
633
|
+
resolveModelInfo?: (provider: string, model: string) => Promise<{ reasoning?: CompileModelCatalogItem['reasoning'] }>
|
|
634
|
+
} | undefined
|
|
635
|
+
const providers = [...new Set([...(llm?.listProviders?.() ?? []).map(provider => provider.id), defaultRoute.provider, compileModelForBase(kbId).provider].filter((value): value is string => Boolean(value)))]
|
|
636
|
+
const models: CompileModelCatalogItem[] = []
|
|
637
|
+
const failures: string[] = []
|
|
638
|
+
if (!llm?.listModels) {
|
|
639
|
+
failures.push('当前上下文没有 DSH 模型目录服务')
|
|
640
|
+
} else {
|
|
641
|
+
for (const provider of providers) {
|
|
642
|
+
try {
|
|
643
|
+
const entries = await llm.listModels(provider)
|
|
644
|
+
for (const entry of entries) {
|
|
645
|
+
if (!entry?.id) continue
|
|
646
|
+
models.push({
|
|
647
|
+
provider,
|
|
648
|
+
id: entry.id,
|
|
649
|
+
name: entry.name?.trim() || entry.id,
|
|
650
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
651
|
+
available: true,
|
|
652
|
+
...(entry.id === defaultRoute.model && provider === defaultRoute.provider ? { current: true } : {}),
|
|
653
|
+
})
|
|
654
|
+
}
|
|
655
|
+
} catch (cause) {
|
|
656
|
+
failures.push(`${provider}: ${cause instanceof Error ? cause.message : String(cause)}`)
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
const selection = compileModelForBase(kbId)
|
|
662
|
+
const effortProvider = selection.mode === 'fixed' ? selection.provider : defaultRoute.provider
|
|
663
|
+
const effortModel = selection.mode === 'fixed' ? selection.model : defaultRoute.model
|
|
664
|
+
const requestedProvider = queryModelRoute.provider || effortProvider
|
|
665
|
+
const requestedModel = queryModelRoute.model || effortModel
|
|
666
|
+
if (requestedProvider && requestedModel && llm?.resolveModelInfo) {
|
|
667
|
+
const item = models.find(model => model.provider === requestedProvider && model.id === requestedModel)
|
|
668
|
+
if (item) {
|
|
669
|
+
try { item.reasoning = (await llm.resolveModelInfo(requestedProvider, requestedModel)).reasoning }
|
|
670
|
+
catch (cause) { failures.push(`思考强度目录:${cause instanceof Error ? cause.message : String(cause)}`) }
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
const selected = selection.mode === 'fixed' && selection.provider && selection.model
|
|
674
|
+
? models.find(item => item.provider === selection.provider && item.id === selection.model)
|
|
675
|
+
: undefined
|
|
676
|
+
if (selection.mode === 'fixed' && selection.provider && selection.model && !selected) {
|
|
677
|
+
models.unshift({ provider: selection.provider, id: selection.model, name: selection.model, available: false, current: true })
|
|
678
|
+
}
|
|
679
|
+
const defaultAvailable = Boolean(
|
|
680
|
+
defaultRoute.provider && defaultRoute.model
|
|
681
|
+
&& models.some(item => item.provider === defaultRoute.provider && item.id === defaultRoute.model && item.available),
|
|
682
|
+
)
|
|
683
|
+
return {
|
|
684
|
+
selection,
|
|
685
|
+
default: {
|
|
686
|
+
...(defaultRoute.provider ? { provider: defaultRoute.provider } : {}),
|
|
687
|
+
...(defaultRoute.model ? { model: defaultRoute.model } : {}),
|
|
688
|
+
...(defaultRoute.reasoningEffort ? { reasoningEffort: defaultRoute.reasoningEffort } : {}),
|
|
689
|
+
available: defaultAvailable,
|
|
690
|
+
},
|
|
691
|
+
models,
|
|
692
|
+
...(failures.length > 0 ? { why: failures.join(';') } : {}),
|
|
693
|
+
}
|
|
694
|
+
}
|
|
550
695
|
|
|
551
696
|
const kbIdOf = (value: unknown): string => {
|
|
552
697
|
if (value === undefined || value === null) return bases.defaultId
|
|
@@ -559,7 +704,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
559
704
|
*
|
|
560
705
|
* `Entry._commitVolatile()` 只调 `updateVolatile()` 就地改引用,再发一个
|
|
561
706
|
* `loader/volatile-update` 事件。所以「设置存下了但没生效」这件事只能在这里兜住 ——
|
|
562
|
-
* 设置 schema
|
|
707
|
+
* 设置 schema 里的可编辑字段标了 volatile(见 `settings-volatile.ts`),这两件事必须成对。
|
|
563
708
|
*
|
|
564
709
|
* 就地更新之所以够用,是因为 `pluginConfig` 就是宿主那份被改过的对象:重新
|
|
565
710
|
* `normalizeKnowledge` 一下就能拿到新值。
|
|
@@ -579,6 +724,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
579
724
|
const previousAgentRoot = config.agentRoot
|
|
580
725
|
config = normalizeKnowledge(pluginConfig.knowledge)
|
|
581
726
|
runtime.updateConfig(config)
|
|
727
|
+
bases.updateRuntimeConfig(config)
|
|
582
728
|
/**
|
|
583
729
|
* 转换设置挂在同一个 entry 上,volatile 事件需要让转换宿主重新读取。
|
|
584
730
|
*/
|
|
@@ -613,66 +759,12 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
613
759
|
}
|
|
614
760
|
|
|
615
761
|
/**
|
|
616
|
-
*
|
|
617
|
-
*
|
|
618
|
-
* 结论只保留**最后一个非 skipped 的**:默认库才是用户实际在用的那个,而面板上
|
|
619
|
-
* 一行位置有限。真有多个库搬失败时,`/knowledge/layout` 的 `resourceMigration`
|
|
620
|
-
* 与知识库列表里的路径仍然能对上账。
|
|
621
|
-
*/
|
|
622
|
-
const migrateAllMemory = (): MigrationOutcome => {
|
|
623
|
-
const bases = activeKnowledgeBases
|
|
624
|
-
if (!bases) return { status: 'skipped', reason: '知识库注册表尚未就绪' }
|
|
625
|
-
let outcome: MigrationOutcome = { status: 'skipped', reason: '每个知识库的记忆都在新位置' }
|
|
626
|
-
const roots = bases.list()
|
|
627
|
-
const defaultBase = roots.find(item => item.id === bases.defaultId)
|
|
628
|
-
// 默认库的根由配置推出来,可能还没出现在注册表里(首启);补上它。
|
|
629
|
-
const plan = defaultBase ? roots : [{ id: bases.defaultId, rootDir: defaultBaseRoot() }, ...roots]
|
|
630
|
-
for (const base of plan) {
|
|
631
|
-
try {
|
|
632
|
-
const moved = migrateLegacyMemory({ agentRoot: config.agentRoot, kbRoot: base.rootDir, kbId: base.id })
|
|
633
|
-
if (moved.status !== 'skipped') outcome = moved
|
|
634
|
-
} catch (cause) {
|
|
635
|
-
outcome = {
|
|
636
|
-
status: 'failed',
|
|
637
|
-
from: join(base.rootDir, '04_memory'),
|
|
638
|
-
to: memoryRoot(config.agentRoot, base.id),
|
|
639
|
-
reason: cause instanceof Error ? cause.message : String(cause),
|
|
640
|
-
}
|
|
641
|
-
}
|
|
642
|
-
}
|
|
643
|
-
return outcome
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
/**
|
|
647
|
-
* 初始化:迁移 → 布局 → 构造 runtime。
|
|
762
|
+
* 初始化:只创建新布局,再构造 runtime。
|
|
648
763
|
*
|
|
649
|
-
*
|
|
650
|
-
*
|
|
651
|
-
* 数据搬完之前就指着新路径,于是「刚启动时知识库是空的」——而那正是最容易让人以为
|
|
652
|
-
* 数据丢了的时刻。
|
|
653
|
-
*
|
|
654
|
-
* 0.1.7:这段以前挂在 `settings.installSection` 的 `setSource`/`onChange` 回调里,
|
|
655
|
-
* 因为配置要等设置层推回来。现在配置是 `apply` 的入参,所以直接在 apply 时跑一次;
|
|
656
|
-
* 改设置会让插件**整个重载**,于是它会随重载再跑,由 `migrationDone` 挡住重复迁移。
|
|
764
|
+
* 旧版的 `rootDir`、旧 Agent 根路径和旧家当目录都不再参与启动流程。这样新安装的
|
|
765
|
+
* 数据路径是确定的,也不会因为启动而改动用户现有目录。
|
|
657
766
|
*/
|
|
658
767
|
const initialize = (): void => {
|
|
659
|
-
if (!migrationDone) {
|
|
660
|
-
migrationDone = true
|
|
661
|
-
// 顺序:先把知识树搬进新根路径,再把家当搬进来。家当要在 runtime 构造**之前**
|
|
662
|
-
// 到位 —— 否则模型与解释器会先按旧位置解析一次,那一轮跑的是旧环境。
|
|
663
|
-
migration = migrateLegacyKnowledgeTree({ agentRoot: config.agentRoot, legacyRoot: legacySource() })
|
|
664
|
-
resourceMigration = migrateLegacyResources({ agentRoot: config.agentRoot, legacyHome: legacyResourceHome() })
|
|
665
|
-
/**
|
|
666
|
-
* 0032:记忆段从知识树搬出去。**每个知识库各搬一次** —— 它按库分叉,而
|
|
667
|
-
* 非默认库的 runtime 是懒建的(`runtimeOf`),懒建里做迁移会把「打开一个库」
|
|
668
|
-
* 变成一次可能的搬盘。放在这里,顺序与另外两个迁移一致:搬完才构造 runtime。
|
|
669
|
-
*
|
|
670
|
-
* 逐个 try:一个库搬不动不该让其余的也不搬,更不该让插件起不来。
|
|
671
|
-
*/
|
|
672
|
-
memoryMigration = migrateAllMemory()
|
|
673
|
-
// 注册表可能刚被搬进来:让它按新位置重新读一遍。
|
|
674
|
-
void bases.repoint()
|
|
675
|
-
}
|
|
676
768
|
ensureAgentLayout(config.agentRoot)
|
|
677
769
|
runtime.close()
|
|
678
770
|
runtime = new KnowledgeRuntime(defaultBaseRoot(), config, runtimeOptions())
|
|
@@ -684,9 +776,9 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
684
776
|
/**
|
|
685
777
|
* 初始化失败必须留下痕迹。
|
|
686
778
|
*
|
|
687
|
-
*
|
|
688
|
-
*
|
|
689
|
-
*
|
|
779
|
+
* 创建新布局或构造 runtime 会因为权限、占用等原因失败;而它抛出的异常如果没人接,
|
|
780
|
+
* 就只是一次没人看见的失败。抓住它、记下来、让 `/knowledge/layout` 报出去,
|
|
781
|
+
* 界面于是能说「为什么不对」,而不是只显示症状。
|
|
690
782
|
*
|
|
691
783
|
* 注意这里**不吞配置错误**:schema 不合法的 config 在装载 entry 时就已经让插件
|
|
692
784
|
* 加载失败了(那是响亮的)。这里接住的只是初始化期的数据问题。
|
|
@@ -726,45 +818,122 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
726
818
|
// 0.1.7:转换的设置不再走命名空间,随本 entry 的 config 一起下来。
|
|
727
819
|
// 给函数而不是值:字段是 volatile 引用,解包一次就是快照(见 `convertGroup`)。
|
|
728
820
|
config: convertGroup,
|
|
729
|
-
// 0019:
|
|
730
|
-
//
|
|
731
|
-
//
|
|
732
|
-
resolveTarget: (kbId
|
|
733
|
-
const target =
|
|
734
|
-
return target ? { runtime: target, knowledgeRoot: target.rootDir } :
|
|
821
|
+
// 0019: an omitted id selects the default base in the host. An explicit id
|
|
822
|
+
// must resolve here; otherwise ingestion fails instead of writing to a
|
|
823
|
+
// different knowledge base while reporting the requested id.
|
|
824
|
+
resolveTarget: (kbId: string) => {
|
|
825
|
+
const target = runtimeOf(kbId)
|
|
826
|
+
return target ? { runtime: target, knowledgeRoot: target.rootDir } : undefined
|
|
735
827
|
},
|
|
736
828
|
})
|
|
737
829
|
|
|
738
830
|
// 意图记录模块 (issue 0020). The 意图 view was already one of this plugin's
|
|
739
831
|
// five panel tabs, but its Host lived in a second mount unit; 0020 folds it in
|
|
740
832
|
// so the declared information architecture and the code layout agree again.
|
|
741
|
-
// It keeps the `intent_record` / `intent_store_view` tools and
|
|
833
|
+
// It keeps the `intent_record` / `intent_store_view` tools and `/intent/**` audit endpoints,
|
|
742
834
|
// and — critically — the `titaDataAgentIntent` Cordis service that `data-table`
|
|
743
835
|
// resolves by name.
|
|
744
836
|
// 意图只保留固定的会话审计契约;可编辑词表不再属于插件配置。
|
|
745
|
-
applyIntent(ctx, {
|
|
837
|
+
applyIntent(ctx, {
|
|
838
|
+
intentRoot: () => intentRoot(config.agentRoot),
|
|
839
|
+
})
|
|
840
|
+
// `applyIntent` provides this service from the same plugin fiber. During
|
|
841
|
+
// `apply()`, that fiber is still starting, so Cordis's default strict lookup
|
|
842
|
+
// hides the just-provided service until startup completes. The non-strict
|
|
843
|
+
// lookup is safe here because `applyIntent` ran immediately above.
|
|
844
|
+
const intentService = ctx.get(INTENT_SERVICE, false) as IntentService
|
|
845
|
+
/**
|
|
846
|
+
* `intent_*` 工具在 Host 层注册,使标准模式可以调用它们;工具执行端只接受
|
|
847
|
+
* standard 会话,其他 preset 不会写入或读取意图记录。
|
|
848
|
+
*/
|
|
849
|
+
ctx.effect(
|
|
850
|
+
() => registerIntentTools(ctx as unknown as { tools: { register(definition: unknown): unknown } }, intentService),
|
|
851
|
+
'tita knowledge: global intent tools',
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
/**
|
|
855
|
+
* 规则正文由当前会话 preset 决定;设置里的 volatile 引用在每次组装提示词时解包,
|
|
856
|
+
* 因此开关保存后立即生效,不需要重建 Agent 会话。
|
|
857
|
+
*/
|
|
858
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
859
|
+
name: 'dsh-data-domain-knowledge:behavior-rules',
|
|
860
|
+
order: ctx.systemPrompt.getSectionOrder('TOOLS_SDK') + 1,
|
|
861
|
+
text: ({ agent }) => {
|
|
862
|
+
return behaviorPromptForPreset(
|
|
863
|
+
agent?.session?.header?.agentPreset,
|
|
864
|
+
unwrapGroup<{ enabled?: boolean }>(pluginConfig.behaviorRules).enabled !== false,
|
|
865
|
+
)
|
|
866
|
+
},
|
|
867
|
+
}), 'tita knowledge: agent behavior rules')
|
|
746
868
|
|
|
747
869
|
/**
|
|
748
|
-
*
|
|
870
|
+
* 需要本轮用户审批的知识写工具。`image_publish` 只写入本地素材目录,按用户要求免审批。
|
|
749
871
|
*
|
|
750
872
|
* 0044 换过一批名字:`write_knowledge_card`(只接受已确认提议)退役了,因为正文现在
|
|
751
873
|
* 只读、卡片由编译直接写入;`list_card_proposals` 也退役了,因为待办队列不存在了。
|
|
752
874
|
* 新增的两个是**入库之后**的动作:加备注、review 抬分。
|
|
753
875
|
*/
|
|
754
|
-
|
|
876
|
+
ctx.tools.register(tool('run_knowledge_pipeline', 'Run one independent knowledge stage. Import only saves sources by default. Stages: extract Fact/Procedure/Case, normalize axes, derive Procedure from Case, materialize Wiki Fact/Wiki Way, build section indexes, or all. Returns a durable background task; inspect it with get_knowledge_pipeline.', {
|
|
877
|
+
stage: { type: 'string', required: true, enum: ['extract', 'normalize', 'derive', 'materialize', 'build', 'all'] },
|
|
878
|
+
kb: { type: 'string' }, source_ids: { type: 'array', description: 'Optional explicit source IDs; omit for all sources. An empty selection is rejected.' },
|
|
879
|
+
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.' },
|
|
880
|
+
}, async args => withRuntime(args.kb, async target => {
|
|
881
|
+
const pipeline = await target.pipeline()
|
|
882
|
+
const { task } = pipeline.start(args.stage as PipelineStage, { sourceIds: args.source_ids, groupKeys: args.group_keys, force: args.force === true })
|
|
883
|
+
return success({ task, url: `/knowledge/pages/tasks?kb=${encodeURIComponent(target.baseId)}&task=${encodeURIComponent(task.id)}` })
|
|
884
|
+
})))
|
|
885
|
+
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.', {
|
|
886
|
+
kb: { type: 'string' }, task_id: { type: 'string' }, after: { type: 'integer' },
|
|
887
|
+
}, async args => withRuntime(args.kb, async (target): Promise<Envelope<Record<string, unknown>>> => {
|
|
888
|
+
const pipeline = await target.pipeline()
|
|
889
|
+
return args.task_id
|
|
890
|
+
? success({ task: pipeline.store.task(args.task_id) ?? null, ...pipeline.store.events(args.task_id, Math.max(0, args.after ?? 0)) })
|
|
891
|
+
: success({ status: await target.pipelineStatus(), ...pipeline.store.tasks() })
|
|
892
|
+
})))
|
|
893
|
+
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'])
|
|
755
894
|
ctx.on('tools/pre-execute', (exec, next): Promise<PreToolDecision> => {
|
|
756
895
|
if (writeTools.has(exec.name)) return Promise.resolve({ kind: 'ask', reason: '该操作会写入 Tita Knowledge Store,需要本轮用户审批。' })
|
|
757
896
|
return next()
|
|
758
897
|
})
|
|
759
|
-
ctx.tools.register(tool('recall_knowledge', 'Recall
|
|
898
|
+
ctx.tools.register(tool('recall_knowledge', 'Recall Wiki Fact and Wiki Way through H2 title and body search. Use the current subquestion as query when seeking a Procedure. Return complete Wiki files with conditions and evidence. Use get_knowledge_cases for historical Case evidence; a past root cause does not establish the current root cause.', { 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 } : {}) })), {
|
|
760
899
|
call: recallCallView,
|
|
761
900
|
// 查询词从参数来,命中从结果来 —— 两者拼成一张能看懂的账本。
|
|
762
901
|
meta: (args, value) => recallMetaOf({ ...dataOf(value), query: args?.query }),
|
|
763
902
|
result: resultViewOf(recallResultMarkdown),
|
|
764
903
|
}))
|
|
904
|
+
ctx.tools.register(tool('get_knowledge_cases', 'Read historical Case records referenced by Wiki metadata.case_refs. Timelines are evidence from a past instance, not proof of the present diagnosis.', {
|
|
905
|
+
case_ids: { type: 'array', required: true, description: 'Case IDs from Wiki metadata.case_refs; maximum 50 IDs.' },
|
|
906
|
+
version: { type: 'integer', description: 'Optional historical Case version from metadata.case_refs; requires exactly one case ID.' },
|
|
907
|
+
kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' },
|
|
908
|
+
run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' },
|
|
909
|
+
}, async args => withRuntime(args.kb, async target => {
|
|
910
|
+
const ids = [...new Set((Array.isArray(args.case_ids) ? args.case_ids : [])
|
|
911
|
+
.filter((id): id is string => typeof id === 'string' && id.trim() !== '').map(id => id.trim()))]
|
|
912
|
+
if (ids.length === 0) return failure({ cases: [], missing_ids: [] }, 'CASE_IDS_REQUIRED', '请传入 Wiki 的 case_refs 中的 Case ID。', false)
|
|
913
|
+
if (ids.length > 50) return failure({ cases: [], missing_ids: [] }, 'CASE_IDS_LIMIT', '一次最多读取 50 个 Case ID。', false)
|
|
914
|
+
if (args.version !== undefined && (ids.length !== 1 || !Number.isInteger(args.version) || args.version < 1)) return failure({ cases: [], missing_ids: ids }, 'CASE_VERSION_INVALID', '读取历史版本时,请传一个 Case ID 和正整数 version。', false)
|
|
915
|
+
const records = await Promise.all(ids.map(id => target.readCase(id, args.version)))
|
|
916
|
+
return success({ cases: records.filter(record => record !== undefined), missing_ids: ids.filter((_, index) => records[index] === undefined) })
|
|
917
|
+
})))
|
|
918
|
+
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.', {
|
|
919
|
+
event_ids: { type: 'array', required: true, description: 'Event IDs from WikiCard metadata.event_refs; maximum 50 IDs.' },
|
|
920
|
+
kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' },
|
|
921
|
+
run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' },
|
|
922
|
+
}, async args => withRuntime(args.kb, async target => {
|
|
923
|
+
const ids = [...new Set((Array.isArray(args.event_ids) ? args.event_ids : [])
|
|
924
|
+
.filter((id): id is string => typeof id === 'string' && id.trim() !== '').map(id => id.trim()))]
|
|
925
|
+
if (ids.length === 0) return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_REQUIRED', '请传入 WikiCard 的 event_refs 中的 Event ID。', false)
|
|
926
|
+
if (ids.length > 50) return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_LIMIT', '一次最多读取 50 个 Event ID;请分批读取该 WikiCard 的 event_refs。', false)
|
|
927
|
+
const byId = new Map((await target.loadEvents()).map(event => [event.id, event]))
|
|
928
|
+
const events = ids.flatMap(id => {
|
|
929
|
+
const event = byId.get(id)
|
|
930
|
+
return event ? [event] : []
|
|
931
|
+
})
|
|
932
|
+
return success({ events, missing_ids: ids.filter(id => !byId.has(id)) })
|
|
933
|
+
})))
|
|
765
934
|
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())))
|
|
766
935
|
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)))))
|
|
767
|
-
ctx.tools.register(tool('query_source_truth', 'Read SourceRaw (原始可追溯文档) with access audit.
|
|
936
|
+
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))))
|
|
768
937
|
/**
|
|
769
938
|
* 知识库 id 的**发现入口**。
|
|
770
939
|
*
|
|
@@ -792,6 +961,29 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
792
961
|
})),
|
|
793
962
|
})))
|
|
794
963
|
|
|
964
|
+
const imagePublishParameters = {
|
|
965
|
+
file_path: { type: 'string', description: '宿主可读取的本地图片文件路径;与 content_base64 二选一' },
|
|
966
|
+
content_base64: { type: 'string', description: '图片的标准 Base64,或 data:image/...;base64,...;与 file_path 二选一' },
|
|
967
|
+
filename: { type: 'string', description: '可选文件名;默认使用 file_path 的文件名,Base64 输入默认 image-<hash>.<ext>' },
|
|
968
|
+
mime_type: { type: 'string', description: '可选 MIME 类型;必须与图片二进制内容一致' },
|
|
969
|
+
kb: { type: 'string', description: '知识库 id;省略时写入默认库' },
|
|
970
|
+
}
|
|
971
|
+
const publishImage = async (args: Args) => withRuntime(args.kb, async target => {
|
|
972
|
+
try {
|
|
973
|
+
return success(await publishKnowledgeImage(target.rootDir, target.baseId, {
|
|
974
|
+
filePath: args.file_path,
|
|
975
|
+
contentBase64: args.content_base64,
|
|
976
|
+
filename: args.filename,
|
|
977
|
+
mimeType: args.mime_type,
|
|
978
|
+
}))
|
|
979
|
+
} catch (cause) {
|
|
980
|
+
if (cause instanceof ImagePublishError) return failure({}, cause.code, cause.message, false, cause.suggestion)
|
|
981
|
+
return failure({}, 'IMAGE_PUBLISH_FAILED', `图片写入失败:${cause instanceof Error ? cause.message : String(cause)}`, true)
|
|
982
|
+
}
|
|
983
|
+
})
|
|
984
|
+
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.'
|
|
985
|
+
ctx.tools.register(tool('image_publish', imagePublishDescription, imagePublishParameters, publishImage))
|
|
986
|
+
|
|
795
987
|
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) })), {
|
|
796
988
|
call: addSourceCallView,
|
|
797
989
|
meta: (args, value) => addSourceMetaOf(args, dataOf(value)),
|
|
@@ -811,7 +1003,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
811
1003
|
* `compile` 默认开:批量导入的意图几乎总是「进来就能用」。关掉它的场景是「先全部
|
|
812
1004
|
* 落盘、稍后统一编译」——那时卡片还没生成,工具返回里会明说 `compile: null`。
|
|
813
1005
|
*/
|
|
814
|
-
ctx.tools.register(tool('import_sources', 'Import many SourceRaw documents
|
|
1006
|
+
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) }))
|
|
815
1007
|
/**
|
|
816
1008
|
* `propose_wiki_cards` —— 把一批卡片**直接写进 `02_wiki`**(工具名不变,语义在 0044 改了)。
|
|
817
1009
|
*
|
|
@@ -824,9 +1016,9 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
824
1016
|
* 这个工具服务于另一条路:Agent 手里已经有结构化的卡片草案(例如从一次排查里读出来的),
|
|
825
1017
|
* 直接写进来,不必再走一遍抽取。
|
|
826
1018
|
*/
|
|
827
|
-
ctx.tools.register(tool('propose_wiki_cards', 'Write Wiki cards compiled from a SourceRaw directly into 02_wiki. The confirmation gate was retired in 0044: cards land immediately and are marked unreviewed, so a human reviews them AFTER entry (review raises the card score; the body itself is read-only). Use the system fixed card template and coordinate entity identity. DSH requests one user approval before execution.', { source_id: { type: 'string', required: true, description: '这批卡片引用的那条 SourceRaw 的 source_id(先 add_source)' }, proposals: { type: 'array', required: true, description: '卡片数组,每项 {
|
|
1019
|
+
ctx.tools.register(tool('propose_wiki_cards', 'Write Wiki cards compiled from a SourceRaw directly into 02_wiki. The confirmation gate was retired in 0044: cards land immediately and are marked unreviewed, so a human reviews them AFTER entry (review raises the card score; the body itself is read-only). Use the system fixed card template and coordinate entity identity. Existing cards are safely merged by appending new content and unioning source references; the only supported explicit action is `merge`. DSH requests one user approval before execution.', { source_id: { type: 'string', required: true, description: '这批卡片引用的那条 SourceRaw 的 source_id(先 add_source)' }, proposals: { type: 'array', required: true, description: '卡片数组,每项 { entity_type, entity_id, title, summary?, content, source_refs?, metadata? };content 使用系统固定卡片模板。不要传 action;兼容旧调用只接受 action=merge。' }, 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.writeCards({
|
|
828
1020
|
sourceId: args.source_id,
|
|
829
|
-
cards: (Array.isArray(args.proposals) ? args.proposals : []).map(item => ({ action: item
|
|
1021
|
+
cards: (Array.isArray(args.proposals) ? args.proposals : []).map(item => ({ ...(item?.action ? { action: item.action } : {}), entityType: item?.entity_type, entityId: item?.entity_id, title: item?.title, summary: item?.summary, content: item?.content, sourceRefs: item?.source_refs, metadata: item?.metadata })),
|
|
830
1022
|
lineage: lineageOf(args, exec),
|
|
831
1023
|
})), {
|
|
832
1024
|
call: writeCardsCallView,
|
|
@@ -842,7 +1034,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
842
1034
|
* 有效评分压到 `ANOMALY_SCORE` —— 异常卡片照样召回得到,只是排在后面,
|
|
843
1035
|
* 而不是被藏起来(藏起来就没人会去修它)。
|
|
844
1036
|
*/
|
|
845
|
-
ctx.tools.register(tool('annotate_wiki_card', 'Attach a human annotation to a Wiki card, or remove one. Wiki bodies are read-only — this is how a person records a judgement about a card ("this number disagrees with the source", "these two definitions conflict"). kind=anomaly additionally lowers the card score for recall ranking while keeping it retrievable. DSH requests one user approval before execution.', { entity_id: { type: 'string', required: true }, entity_type: { type: 'string' }, kind: { type: 'string', enum: ['note', 'anomaly'], description: '默认 note;anomaly 表示异常点' }, note: { type: 'string', description: '
|
|
1037
|
+
ctx.tools.register(tool('annotate_wiki_card', 'Attach a human annotation to a Wiki card, or remove one. Wiki bodies are read-only — this is how a person records a judgement about a card ("this number disagrees with the source", "these two definitions conflict"). kind=anomaly additionally lowers the card score for recall ranking while keeping it retrievable. Provide `note` to add, or `remove_id` to delete; do not provide both. DSH requests one user approval before execution.', { entity_id: { type: 'string', required: true }, entity_type: { type: 'string' }, kind: { type: 'string', enum: ['note', 'anomaly'], description: '默认 note;anomaly 表示异常点' }, note: { type: 'string', description: '备注正文;添加备注时提供' }, author: { type: 'string' }, source_ref: { type: 'string', description: '这条备注针对哪一份来源(可选)' }, remove_id: { type: 'string', description: '要撤销的备注 id;删除时只传此字段,不要同时传 note' }, 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.annotateCard({
|
|
846
1038
|
entityId: args.entity_id,
|
|
847
1039
|
...(args.entity_type ? { entityType: args.entity_type as SchemaType } : {}),
|
|
848
1040
|
...(args.kind ? { kind: args.kind as 'note' | 'anomaly' } : {}),
|
|
@@ -867,7 +1059,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
867
1059
|
lineage: lineageOf(args, exec),
|
|
868
1060
|
}))))
|
|
869
1061
|
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)))))
|
|
870
|
-
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))), {
|
|
1062
|
+
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 })), {
|
|
871
1063
|
call: rebuildCallView,
|
|
872
1064
|
meta: (args, value) => rebuildMetaOf(args?.only ?? 'all', dataOf(value)),
|
|
873
1065
|
result: resultViewOf(rebuildResultMarkdown),
|
|
@@ -912,7 +1104,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
912
1104
|
* - `canLoadSample` 是空态的开关 —— 这个库还什么都没有时才为真。
|
|
913
1105
|
*/
|
|
914
1106
|
const diagnosisPayload = (snapshot: Awaited<ReturnType<DiagnosisStore['snapshot']>>): Record<string, unknown> => ({
|
|
915
|
-
graph: snapshot.graph, cases: snapshot.cases, issues: snapshot.issues,
|
|
1107
|
+
graph: snapshot.graph, cases: snapshot.cases, events: snapshot.events, issues: snapshot.issues,
|
|
916
1108
|
seeded: snapshot.seeded, migrated: snapshot.migrated, source: snapshot.source,
|
|
917
1109
|
canLoadSample: snapshot.canLoadSample, problems: snapshot.problems,
|
|
918
1110
|
})
|
|
@@ -931,17 +1123,41 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
931
1123
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
932
1124
|
const swept = await resolved.runtime.sweepOrphans()
|
|
933
1125
|
if (swept === null) return sendEnvelope(res, failure({}, 'KNOWLEDGE_UNAVAILABLE', '这个部署上没有 Knowledge 库', false))
|
|
934
|
-
const { knowledge, cards } = swept
|
|
935
|
-
const total = knowledge.knowledgeDeleted + cards.deleted
|
|
1126
|
+
const { knowledge, cards, events } = swept
|
|
1127
|
+
const total = knowledge.knowledgeDeleted + cards.deleted + events.deleted + events.updated + swept.assets.files
|
|
936
1128
|
return send(res, 200, success(swept, total === 0
|
|
937
|
-
? ['
|
|
938
|
-
: [`清掉了 ${knowledge.knowledgeDeleted}
|
|
1129
|
+
? ['没有孤儿:Knowledge、Event 和 WikiCard 的来源都还在库里。']
|
|
1130
|
+
: [`清掉了 ${knowledge.knowledgeDeleted} 条无来源 Knowledge、${events.deleted} 个无来源 Event(更新 ${events.updated} 个共享 Event)、${cards.deleted} 张无依据 WikiCard。`]))
|
|
1131
|
+
} },
|
|
1132
|
+
/**
|
|
1133
|
+
* DSH 模型目录(知识库编译设置)。编译模型不是插件再维护一套供应商列表,
|
|
1134
|
+
* 而是每次从 DSH 的 `llm.listModels()` 读取;这样固定模型下拉框与真正可调用的
|
|
1135
|
+
* 模型来源保持一致,默认模型失效时也能把状态说清楚。
|
|
1136
|
+
*/
|
|
1137
|
+
{ kind: 'exact' as const, path: '/knowledge/models', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1138
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 GET'))
|
|
1139
|
+
const url = query(req)
|
|
1140
|
+
const id = kbIdOf(url.searchParams.get('kb'))
|
|
1141
|
+
if (!bases.get(id)) return sendEnvelope(res, failure({}, 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true, '先 GET /knowledge/bases 拿到可用 id。'))
|
|
1142
|
+
return send(res, 200, success(await compileModelCatalog(id, { provider: url.searchParams.get('provider') ?? undefined, model: url.searchParams.get('model') ?? undefined })))
|
|
939
1143
|
} },
|
|
940
1144
|
{ kind: 'exact' as const, path: '/knowledge/stats', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
941
1145
|
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
942
1146
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
943
1147
|
return send(res, 200, success(await resolved.runtime.stats()))
|
|
944
1148
|
} },
|
|
1149
|
+
{ kind: 'exact' as const, path: '/knowledge/cases', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1150
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', 'Case 只接受 GET'))
|
|
1151
|
+
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
1152
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1153
|
+
return send(res, 200, success({ cases: await resolved.runtime.loadEvents() }))
|
|
1154
|
+
} },
|
|
1155
|
+
{ kind: 'exact' as const, path: '/knowledge/events', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1156
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', 'Event 只接受 GET'))
|
|
1157
|
+
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
1158
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1159
|
+
return send(res, 200, success({ events: await resolved.runtime.diagnosisStore().listEvents() }))
|
|
1160
|
+
} },
|
|
945
1161
|
{ kind: 'exact' as const, path: '/knowledge/search', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
946
1162
|
const url = query(req)
|
|
947
1163
|
const resolved = resolveBase(url.searchParams.get('kb'))
|
|
@@ -966,6 +1182,19 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
966
1182
|
},
|
|
967
1183
|
))
|
|
968
1184
|
} },
|
|
1185
|
+
{ kind: 'exact' as const, path: '/knowledge/environment', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1186
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '只支持 GET'))
|
|
1187
|
+
return send(res, 200, success(environment.view()))
|
|
1188
|
+
} },
|
|
1189
|
+
{ kind: 'exact' as const, path: '/knowledge/environment/prepare', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1190
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '只支持 POST'))
|
|
1191
|
+
try {
|
|
1192
|
+
const body = await readBody(req, 4000) as { action?: string; selection?: unknown } | null
|
|
1193
|
+
if (body?.action === 'cancel') { environment.cancel(); return send(res, 200, success(environment.view())) }
|
|
1194
|
+
if (body?.action !== 'check' && body?.action !== 'install') return send(res, 400, failure({}, 'ACTION_INVALID', 'action 必须是 check、install 或 cancel', false))
|
|
1195
|
+
return send(res, 200, success(environment.start(body.action, body.selection)))
|
|
1196
|
+
} catch (cause) { return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause))) }
|
|
1197
|
+
} },
|
|
969
1198
|
{ kind: 'exact' as const, path: '/knowledge/embedding/health', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
970
1199
|
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
971
1200
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
@@ -1005,6 +1234,72 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1005
1234
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1006
1235
|
return send(res, 200, success({ progress: resolved.runtime.compileProgressView(url.searchParams.get('runId') ?? undefined) }))
|
|
1007
1236
|
} },
|
|
1237
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1238
|
+
try {
|
|
1239
|
+
const body = req.method === 'POST' ? await readBody(req, 100_000) : {}
|
|
1240
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'))
|
|
1241
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1242
|
+
if (req.method === 'GET') return send(res, 200, success(await resolved.runtime.pipelineStatus()))
|
|
1243
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '知识处理只接受 GET 或 POST'))
|
|
1244
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1245
|
+
const started = pipeline.start(body.stage as PipelineStage, { sourceIds: body.sourceIds, groupKeys: body.groupKeys, force: body.force === true })
|
|
1246
|
+
return send(res, 202, success({ task: started.task }))
|
|
1247
|
+
} catch (cause) { return send(res, 400, envelopeError('PIPELINE_INVALID', cause instanceof Error ? cause.message : String(cause))) }
|
|
1248
|
+
} },
|
|
1249
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline-tasks', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1250
|
+
try {
|
|
1251
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务历史只接受 GET'))
|
|
1252
|
+
const url = query(req)
|
|
1253
|
+
const resolved = resolveBase(url.searchParams.get('kb'))
|
|
1254
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1255
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1256
|
+
const limit = Math.min(100, Math.max(1, Number(url.searchParams.get('limit')) || 20))
|
|
1257
|
+
const offset = Math.max(0, Number(url.searchParams.get('offset')) || 0)
|
|
1258
|
+
return send(res, 200, success({ ...pipeline.store.tasks(limit, offset), extractions: await resolved.runtime.listExtractionRuns({ limit, offset }) }))
|
|
1259
|
+
} catch (cause) { return send(res, 400, envelopeError('PIPELINE_HISTORY_FAILED', cause instanceof Error ? cause.message : String(cause))) }
|
|
1260
|
+
} },
|
|
1261
|
+
{ kind: 'exact' as const, path: '/knowledge/pipeline-task', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1262
|
+
try {
|
|
1263
|
+
const url = query(req)
|
|
1264
|
+
const body = req.method === 'POST' ? await readBody(req, 20_000) : {}
|
|
1265
|
+
const resolved = resolveBase(body.kb ?? url.searchParams.get('kb'))
|
|
1266
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1267
|
+
const pipeline = await resolved.runtime.pipeline()
|
|
1268
|
+
const id = typeof body.id === 'string' ? body.id : url.searchParams.get('id') ?? ''
|
|
1269
|
+
const task = pipeline.store.task(id)
|
|
1270
|
+
if (!task) return send(res, 404, envelopeError('PIPELINE_NOT_FOUND', '任务不存在'))
|
|
1271
|
+
if (req.method === 'GET') return send(res, 200, success({ task, ...pipeline.store.events(id, Math.max(0, Number(url.searchParams.get('after')) || 0)) }))
|
|
1272
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务只接受 GET 或 POST'))
|
|
1273
|
+
if (body.action === 'retry') return send(res, 202, success({ task: pipeline.start(task.stage, { ...task.selection, force: false }).task }))
|
|
1274
|
+
if (!['pause', 'resume', 'cancel'].includes(body.action)) throw new Error('不支持的任务操作')
|
|
1275
|
+
return send(res, 200, success({ task: pipeline.control(id, body.action) }))
|
|
1276
|
+
} catch (cause) { return send(res, 409, envelopeError('PIPELINE_TASK_REJECTED', cause instanceof Error ? cause.message : String(cause))) }
|
|
1277
|
+
} },
|
|
1278
|
+
{ kind: 'exact' as const, path: '/knowledge/extraction-tasks', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1279
|
+
if (req.method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '抽取任务只接受 GET'))
|
|
1280
|
+
const resolved = resolveBase(query(req).searchParams.get('kb'))
|
|
1281
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1282
|
+
return send(res, 200, success({ tasks: resolved.runtime.listCompileTasks() }))
|
|
1283
|
+
} },
|
|
1284
|
+
{ kind: 'exact' as const, path: '/knowledge/extraction-task', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1285
|
+
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '抽取任务控制只接受 POST'))
|
|
1286
|
+
try {
|
|
1287
|
+
const body = await readBody(req, 20_000)
|
|
1288
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'))
|
|
1289
|
+
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1290
|
+
const runId = typeof body.run_id === 'string' ? body.run_id.trim() : ''
|
|
1291
|
+
const action = body.action
|
|
1292
|
+
if (!runId || (action !== 'pause' && action !== 'resume' && action !== 'cancel')) {
|
|
1293
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', '需要 run_id 和 pause / resume / cancel 操作'))
|
|
1294
|
+
}
|
|
1295
|
+
const result = resolved.runtime.controlCompileTask(runId, action)
|
|
1296
|
+
return result.ok
|
|
1297
|
+
? send(res, 200, success({ task: result.task }))
|
|
1298
|
+
: send(res, 409, envelopeError('TASK_CONTROL_REJECTED', result.why ?? '任务状态不支持此操作'))
|
|
1299
|
+
} catch (cause) {
|
|
1300
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)))
|
|
1301
|
+
}
|
|
1302
|
+
} },
|
|
1008
1303
|
/**
|
|
1009
1304
|
* 把一个知识库**导出成目录形态**(0062 v2 §3.7 末)。
|
|
1010
1305
|
*
|
|
@@ -1043,7 +1338,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1043
1338
|
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'))
|
|
1044
1339
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1045
1340
|
return send(res, 202, success(resolved.runtime.startCompile(body.only ?? 'all', lineageOf(body), {
|
|
1046
|
-
|
|
1341
|
+
extract: body.extract === true,
|
|
1047
1342
|
...(body.forceExtract === true ? { forceExtract: true } : {}),
|
|
1048
1343
|
})))
|
|
1049
1344
|
} catch (cause) {
|
|
@@ -1070,7 +1365,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1070
1365
|
...(item?.external_id ? { externalId: String(item.external_id) } : {}),
|
|
1071
1366
|
...(item?.metadata && typeof item.metadata === 'object' ? { metadata: item.metadata as Record<string, unknown> } : {}),
|
|
1072
1367
|
})),
|
|
1073
|
-
|
|
1368
|
+
compile: body.compile === true,
|
|
1074
1369
|
lineage: lineageOf(body),
|
|
1075
1370
|
}))
|
|
1076
1371
|
} catch (cause) {
|
|
@@ -1095,6 +1390,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1095
1390
|
Number(url.searchParams.get('max_chars') ?? 40000) || 40000,
|
|
1096
1391
|
lineageFromUrl(url),
|
|
1097
1392
|
Number(url.searchParams.get('offset') ?? 0) || 0,
|
|
1393
|
+
Number(url.searchParams.get('source_offset') ?? 0) || 0,
|
|
1098
1394
|
))
|
|
1099
1395
|
} catch (cause) {
|
|
1100
1396
|
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)))
|
|
@@ -1162,7 +1458,8 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1162
1458
|
* 任何一条轨迹 —— 「这个实体是哪次抽出来的」就答不上了。
|
|
1163
1459
|
*/
|
|
1164
1460
|
const runId = extractionRunId()
|
|
1165
|
-
const seam = modelSeamFor()
|
|
1461
|
+
const seam = modelSeamFor(resolved.runtime.baseId)
|
|
1462
|
+
const draft = await (await resolved.runtime.axisRegistry())?.draft()
|
|
1166
1463
|
const axesBlock = await resolved.runtime.axisPromptBlockView()
|
|
1167
1464
|
const note = resolved.runtime.extractionNote()
|
|
1168
1465
|
const report = await runWikiTrial({
|
|
@@ -1170,14 +1467,17 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1170
1467
|
text,
|
|
1171
1468
|
...(docId ? { docId } : {}),
|
|
1172
1469
|
...(typeof body.subject === 'string' && body.subject.trim() ? { subject: body.subject.trim() } : {}),
|
|
1173
|
-
/**
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1470
|
+
/** 与编译相同:先查注册表,再把有候选的坐标合并成一次模型补判。 */
|
|
1471
|
+
resolveAxisSet: axes => resolved.runtime.resolveTrialAxisSet({
|
|
1472
|
+
registry: draft?.registry, axes,
|
|
1473
|
+
axesBlock,
|
|
1474
|
+
runId,
|
|
1475
|
+
call: seam.call,
|
|
1476
|
+
modelRequest: {
|
|
1477
|
+
...(seam.provider ? { provider: seam.provider } : {}),
|
|
1478
|
+
...(seam.model ? { model: seam.model } : {}),
|
|
1479
|
+
...(seam.reasoningEffort ? { reasoningEffort: seam.reasoningEffort } : {}),
|
|
1480
|
+
},
|
|
1181
1481
|
}),
|
|
1182
1482
|
/**
|
|
1183
1483
|
* 已有坐标(0068)。**与真编译下发同一份** —— 试跑看到的世界与真编译看到的
|
|
@@ -1185,9 +1485,14 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1185
1485
|
*/
|
|
1186
1486
|
...(axesBlock === undefined ? {} : { axes: axesBlock }),
|
|
1187
1487
|
...(note ? { note } : {}),
|
|
1488
|
+
modelRequest: {
|
|
1489
|
+
...(seam.provider ? { provider: seam.provider } : {}),
|
|
1490
|
+
...(seam.model ? { model: seam.model } : {}),
|
|
1491
|
+
...(seam.reasoningEffort ? { reasoningEffort: seam.reasoningEffort } : {}),
|
|
1492
|
+
},
|
|
1188
1493
|
...('call' in seam ? { call: seam.call } : {}),
|
|
1189
1494
|
...('why' in seam ? { callWhy: seam.why } : {}),
|
|
1190
|
-
})
|
|
1495
|
+
}).finally(() => draft?.close())
|
|
1191
1496
|
const summary = trialSummary(report)
|
|
1192
1497
|
|
|
1193
1498
|
/**
|
|
@@ -1203,24 +1508,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1203
1508
|
lineage: lineageOf(body),
|
|
1204
1509
|
})
|
|
1205
1510
|
|
|
1206
|
-
|
|
1207
|
-
* 把这次抽出来的 Knowledge 收进真源(0063 §2)。
|
|
1208
|
-
*
|
|
1209
|
-
* **只在跑一份已入库的文档时收。** 理由有两条:来源要指得回一份**真实的**
|
|
1210
|
-
* 文档(临时正文的 `documentId` 是一个标题字符串,存下来就是悬空引用);
|
|
1211
|
-
* 而「这条知识是哪份文档说的」正是它可验证的前提。
|
|
1212
|
-
*
|
|
1213
|
-
* **只收引文核对得上的那些。** 引文对不上原文 = 模型改写了,那条判断不是从
|
|
1214
|
-
* 原文推出来的(§5.7 的引文核验)。把它当知识存进真源,等于让一句编出来的话
|
|
1215
|
-
* 看起来像有出处 —— 而报告里已经把它报成例外了,那里才是它该待的地方。
|
|
1216
|
-
*/
|
|
1217
|
-
const ingest = docId
|
|
1218
|
-
? await resolved.runtime.ingestTrialKnowledge({
|
|
1219
|
-
runId, knowledge: report.knowledge,
|
|
1220
|
-
objects: report.objects, actions: report.actions, scenes: report.scenes,
|
|
1221
|
-
})
|
|
1222
|
-
: { saved: 0, merged: 0, skipped: 0 }
|
|
1223
|
-
return send(res, 200, success({ ...report, summary, run, ingest }))
|
|
1511
|
+
return send(res, 200, success({ ...report, summary, run }))
|
|
1224
1512
|
} catch (cause) {
|
|
1225
1513
|
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)))
|
|
1226
1514
|
}
|
|
@@ -1266,7 +1554,9 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1266
1554
|
* 必须分开:`get()` 给 `''`,缺席给 `null`,这个区别正好用得上。
|
|
1267
1555
|
*/
|
|
1268
1556
|
const status = url.searchParams.get('status')
|
|
1557
|
+
const kind = url.searchParams.get('kind')
|
|
1269
1558
|
const filter: KnowledgeQuery = {
|
|
1559
|
+
...(kind === 'fact' || kind === 'procedure' ? { kind } : {}),
|
|
1270
1560
|
...(status === 'candidate' || status === 'active' || status === 'disputed' || status === 'deprecated' ? { status } : {}),
|
|
1271
1561
|
...(url.searchParams.get('run_id') === null ? {} : { runId: url.searchParams.get('run_id')! }),
|
|
1272
1562
|
...(url.searchParams.get('object_id') === null ? {} : { objectId: url.searchParams.get('object_id')! }),
|
|
@@ -1352,22 +1642,40 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1352
1642
|
const kind = kindParam === 'object' || kindParam === 'action' || kindParam === 'scene' ? kindParam : undefined
|
|
1353
1643
|
const status = statusParam === 'candidate' || statusParam === 'active' || statusParam === 'deprecated' || statusParam === 'merged' ? statusParam : undefined
|
|
1354
1644
|
if (req.method === 'GET') {
|
|
1355
|
-
const [entities, stats] = await Promise.all([
|
|
1645
|
+
const [entities, stats, relations] = await Promise.all([
|
|
1356
1646
|
resolved.runtime.listAxisEntities(kind, status),
|
|
1357
1647
|
resolved.runtime.axisStats(),
|
|
1648
|
+
resolved.runtime.listAxisRelations(),
|
|
1358
1649
|
])
|
|
1359
|
-
if (entities === null || stats === null) return send(res, 200, success({ entities: [], stats: null, guidance: {}, available: false }))
|
|
1650
|
+
if (entities === null || stats === null || relations === null) return send(res, 200, success({ entities: [], stats: null, guidance: {}, relations: [], available: false }))
|
|
1360
1651
|
/*
|
|
1361
1652
|
* 指导随实体一起给(0068)。分开一条路由要多一次往返,而两样东西永远一起显示 ——
|
|
1362
1653
|
* 一条指导离开它挂着的那个实体就没有意义。
|
|
1363
1654
|
*/
|
|
1364
1655
|
const guidance = await resolved.runtime.axisGuidanceMap()
|
|
1365
|
-
return send(res, 200, success({ entities, stats, guidance, available: true }))
|
|
1656
|
+
return send(res, 200, success({ entities, stats, guidance, relations, available: true }))
|
|
1366
1657
|
}
|
|
1367
1658
|
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '轴注册表只接受 GET 或 POST'))
|
|
1368
1659
|
const body = await readBody(req)
|
|
1369
1660
|
const id = typeof body.id === 'string' ? body.id.trim() : ''
|
|
1370
1661
|
const action = typeof body.action === 'string' ? body.action.trim() : ''
|
|
1662
|
+
if (action === 'relation-add') {
|
|
1663
|
+
const fromId = typeof body.from_id === 'string' ? body.from_id.trim() : ''
|
|
1664
|
+
const toId = typeof body.to_id === 'string' ? body.to_id.trim() : ''
|
|
1665
|
+
const relationType = typeof body.relation_type === 'string' ? body.relation_type.trim() : 'related_to'
|
|
1666
|
+
const evidence = typeof body.evidence === 'string' ? body.evidence : ''
|
|
1667
|
+
if (!fromId || !toId) return send(res, 400, envelopeError('REQUEST_INVALID', '添加相关对象关系需要 from_id 与 to_id'))
|
|
1668
|
+
if (relationType !== 'related_to') return send(res, 400, envelopeError('REQUEST_INVALID', '当前只支持 related_to 对象关系'))
|
|
1669
|
+
const relation = await resolved.runtime.addAxisRelation(fromId, toId, evidence)
|
|
1670
|
+
if (relation === null) return sendEnvelope(res, failure({}, 'AXIS_RELATION_INVALID', '关系无效:两个对象必须存在、未合并且不能是同一个对象', false))
|
|
1671
|
+
return send(res, 200, success({ relation }))
|
|
1672
|
+
}
|
|
1673
|
+
if (action === 'relation-remove') {
|
|
1674
|
+
const relationId = typeof body.relation_id === 'string' ? body.relation_id.trim() : ''
|
|
1675
|
+
if (!relationId) return send(res, 400, envelopeError('REQUEST_INVALID', '移除关系需要 relation_id'))
|
|
1676
|
+
const removed = await resolved.runtime.removeAxisRelation(relationId)
|
|
1677
|
+
return send(res, 200, success({ removed }))
|
|
1678
|
+
}
|
|
1371
1679
|
if (action === 'merge') {
|
|
1372
1680
|
const into = typeof body.into === 'string' ? body.into.trim() : ''
|
|
1373
1681
|
if (!id || !into) return send(res, 400, envelopeError('REQUEST_INVALID', '并两个实体要同时给 id 与 into'))
|
|
@@ -1430,7 +1738,10 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1430
1738
|
return send(res, 400, envelopeError('REQUEST_INVALID', `不认识的 action: ${action || '(空)'}`))
|
|
1431
1739
|
} },
|
|
1432
1740
|
/**
|
|
1433
|
-
*
|
|
1741
|
+
* 轨迹(`extraction_run`):按文档或阶段列、按 id 读全文。
|
|
1742
|
+
*
|
|
1743
|
+
* 2026-10-18 起三段共用这张表(`stage` 列:`extract` / `normalize` / `materialize`)。
|
|
1744
|
+
* `stage` 省略 = 三段一起 —— 面板里那份「这份文档的轨迹」就是这么读的。
|
|
1434
1745
|
*
|
|
1435
1746
|
* 列表**不返回报告本体** —— 一条报告几十到几百 KB,翻一页列表不该按全文付费。
|
|
1436
1747
|
* 点开某一条走 `/knowledge/extraction-run?id=…`(单独一条路由,不是路径参数:
|
|
@@ -1442,8 +1753,11 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1442
1753
|
const resolved = resolveBase(url.searchParams.get('kb'))
|
|
1443
1754
|
if ('failure' in resolved) return sendEnvelope(res, resolved.failure)
|
|
1444
1755
|
const docId = url.searchParams.get('doc_id')
|
|
1756
|
+
const stageParam = url.searchParams.get('stage')
|
|
1757
|
+
const stage = stageParam === 'extract' || stageParam === 'normalize' || stageParam === 'materialize' ? stageParam : undefined
|
|
1445
1758
|
const page = await resolved.runtime.listExtractionRuns({
|
|
1446
1759
|
...(docId === null ? {} : { docId }),
|
|
1760
|
+
...(stage === undefined ? {} : { stage }),
|
|
1447
1761
|
limit: clampInt(url.searchParams.get('limit'), 20, 1, 100),
|
|
1448
1762
|
offset: clampInt(url.searchParams.get('offset'), 0, 0, Number.MAX_SAFE_INTEGER),
|
|
1449
1763
|
})
|
|
@@ -1463,7 +1777,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1463
1777
|
} },
|
|
1464
1778
|
// ---------------------------------------------------------- 目录选择(0029)
|
|
1465
1779
|
/**
|
|
1466
|
-
*
|
|
1780
|
+
* 根路径的**只读**视图:新布局、家当解析结果、宿主提供的是哪种选择交互。
|
|
1467
1781
|
*
|
|
1468
1782
|
* 设置面板要显示「你这个根路径下会长出哪几段」,而那是服务端算出来的
|
|
1469
1783
|
* (`layout.ts` 是唯一真源),所以不能让前端自己拼字符串。
|
|
@@ -1471,22 +1785,13 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1471
1785
|
{ kind: 'exact' as const, path: '/knowledge/layout', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1472
1786
|
const dirs = agentLayout(config.agentRoot)
|
|
1473
1787
|
const resources = runtime.resourcePaths()
|
|
1474
|
-
// preset 的真源目录。它随插件分发,所以路径由插件自己算 —— 客户端不该拼这个
|
|
1475
|
-
// 字符串,也不该猜包装在哪。`exists` 是给界面用的:路径不在时如实说「这个安装
|
|
1476
|
-
// 里没有 preset 真源」,而不是给一个点了弹空目录的按钮。
|
|
1477
|
-
const presetDir = presetSourceDir()
|
|
1478
1788
|
return send(res, 200, success({
|
|
1479
1789
|
...dirs,
|
|
1480
|
-
presetDir,
|
|
1481
|
-
presetDirExists: existsSync(presetDir),
|
|
1482
|
-
migration,
|
|
1483
|
-
resourceMigration,
|
|
1484
|
-
memoryMigration,
|
|
1485
1790
|
// 三个「家当」路径**解析后**的值。设置面板用它显示「自动」到底指向哪里 ——
|
|
1486
1791
|
// 配置里是空串(= 自动),但用户要看到的是真实路径。
|
|
1487
1792
|
resources,
|
|
1488
|
-
// 家当有没有真的落在 `resource/`
|
|
1489
|
-
//
|
|
1793
|
+
// 家当有没有真的落在 `resource/` 下。`resource` 是家当目录本身,
|
|
1794
|
+
// 用它做前缀判断:不在里面的可能是一个裸的 HuggingFace 仓库名。
|
|
1490
1795
|
resourcesOutsideResource: outsideResource(resources, dirs.resource),
|
|
1491
1796
|
// 设置分区没注册上时的原因。有它,「设置服务不可用」才不是一句空话。
|
|
1492
1797
|
...(settingsError ? { settingsError } : {}),
|
|
@@ -1559,10 +1864,8 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1559
1864
|
return send(res, 200, success({ cases: (await storeOf(resolved.runtime).snapshot()).cases }))
|
|
1560
1865
|
} },
|
|
1561
1866
|
/**
|
|
1562
|
-
*
|
|
1563
|
-
*
|
|
1564
|
-
* 0044:卡片不再只提议 —— 它直接入库并标成 `unreviewed`。响应给 `sourceId` 与
|
|
1565
|
-
* `entityId`,界面据此说清「记录留下了,卡片也建了,等人 review」。
|
|
1867
|
+
* 一次排查结束只落一条 SourceRaw。Case 视图从记录派生;Knowledge / Event 的抽取、
|
|
1868
|
+
* 归一和 WikiCard 物化由知识处理阶段分别执行。
|
|
1566
1869
|
*/
|
|
1567
1870
|
{ kind: 'exact' as const, path: '/diagnosis/run', handler: async (req: IncomingMessage, res: ServerResponse) => {
|
|
1568
1871
|
if (req.method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 endpoint 只接受 POST'))
|
|
@@ -1575,9 +1878,9 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1575
1878
|
}
|
|
1576
1879
|
const outcome = await storeOf(resolved.runtime).recordRun(record)
|
|
1577
1880
|
if (outcome.problems.length > 0 && outcome.sourceId === undefined) {
|
|
1578
|
-
return send(res, 200, failure({ sourceId: ''
|
|
1881
|
+
return send(res, 200, failure({ sourceId: '' }, 'RUN_WRITE_FAILED', outcome.problems.join(';'), true))
|
|
1579
1882
|
}
|
|
1580
|
-
return send(res, 200, success({ sourceId: outcome.sourceId ?? ''
|
|
1883
|
+
return send(res, 200, success({ sourceId: outcome.sourceId ?? '' }, outcome.problems))
|
|
1581
1884
|
} },
|
|
1582
1885
|
/**
|
|
1583
1886
|
* 入库之后的两个人工动作(0044):加备注、review 抬分。
|
|
@@ -1661,6 +1964,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1661
1964
|
* ```
|
|
1662
1965
|
* /knowledge/pages/trial?kb=<知识库 id> 抽取试跑
|
|
1663
1966
|
* /knowledge/pages/run?kb=<库 id>&run=<轨迹 id> 一次抽取的完整报告
|
|
1967
|
+
* /knowledge/pages/tasks?kb=<知识库 id> 抽取任务
|
|
1664
1968
|
* /knowledge/pages/help/compile 编译链路
|
|
1665
1969
|
* /knowledge/pages/help/intent 意图的逻辑
|
|
1666
1970
|
* /knowledge/pages/help/overview?node=... 五个 Tab 的方案说明
|
|
@@ -1687,6 +1991,8 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1687
1991
|
? '抽取试跑'
|
|
1688
1992
|
: rest[1] === 'run'
|
|
1689
1993
|
? '抽取轨迹'
|
|
1994
|
+
: rest[1] === 'tasks'
|
|
1995
|
+
? '抽取任务'
|
|
1690
1996
|
: rest[1] === 'help' && rest[2] === 'intent'
|
|
1691
1997
|
? '意图的逻辑'
|
|
1692
1998
|
: rest[1] === 'help' && rest[2] === 'compile'
|
|
@@ -1722,9 +2028,30 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1722
2028
|
if (rest.length === 2) {
|
|
1723
2029
|
if (method === 'PATCH') {
|
|
1724
2030
|
const body = await readBody(req)
|
|
2031
|
+
if (body.compileModel && typeof body.compileModel === 'object') {
|
|
2032
|
+
const selection = body.compileModel as CompileModelSelection
|
|
2033
|
+
const catalog = await compileModelCatalog(baseId)
|
|
2034
|
+
const provider = selection.mode === 'fixed' ? selection.provider : catalog.default.provider
|
|
2035
|
+
const model = selection.mode === 'fixed' ? selection.model : catalog.default.model
|
|
2036
|
+
if (selection.mode !== 'fixed' && selection.mode !== 'default') return send(res, 400, envelopeError('MODEL_INVALID', '请选择跟随默认或固定模型'))
|
|
2037
|
+
if (selection.mode === 'fixed' && (!provider || !model)) return send(res, 400, envelopeError('MODEL_INVALID', '固定模型需要 provider 和 model'))
|
|
2038
|
+
const llm = ctx.get('llm')
|
|
2039
|
+
if (provider && model && llm?.resolveCallConfig) {
|
|
2040
|
+
try {
|
|
2041
|
+
const info = selection.reasoningEffort ? await llm.resolveModelInfo(provider, model) : undefined
|
|
2042
|
+
const effort = info?.reasoning?.efforts.find(item => item.id === selection.reasoningEffort)
|
|
2043
|
+
if (selection.reasoningEffort && !effort) throw new Error('所选模型不支持这个思考强度,请重新选择')
|
|
2044
|
+
await llm.resolveCallConfig({ provider, model, ...(effort ? { reasoningEffort: effort.id } : {}) })
|
|
2045
|
+
}
|
|
2046
|
+
catch (cause) { return send(res, 400, envelopeError('MODEL_INVALID', cause instanceof Error ? cause.message : String(cause))) }
|
|
2047
|
+
} else if (selection.reasoningEffort) return send(res, 400, envelopeError('MODEL_INVALID', '模型不可用,无法校验思考强度'))
|
|
2048
|
+
}
|
|
1725
2049
|
const updated = await bases.update(baseId, {
|
|
1726
2050
|
name: body.name, description: body.description,
|
|
1727
2051
|
extractionNote: typeof body.extractionNote === 'string' ? body.extractionNote : undefined,
|
|
2052
|
+
compileModel: body.compileModel && typeof body.compileModel === 'object'
|
|
2053
|
+
? body.compileModel
|
|
2054
|
+
: undefined,
|
|
1728
2055
|
})
|
|
1729
2056
|
if (!updated.ok) return sendEnvelope(res, updated)
|
|
1730
2057
|
return send(res, 200, success({ base: updated.data }, updated.warnings))
|
|
@@ -1858,8 +2185,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1858
2185
|
|
|
1859
2186
|
// ----------------------------------------------- /knowledge/cards
|
|
1860
2187
|
// wiki 卡片详情与版本(0025)。列表走上面的 exact 路由;这里只处理
|
|
1861
|
-
// `/cards/:entityId`
|
|
1862
|
-
// SourceRaw 一样有历史,用户点开一张卡片要能看到它被改过几次。
|
|
2188
|
+
// `/cards/:entityId` 与 `/versions` 子路径 —— 卡片保留只读历史,供查看和比较。
|
|
1863
2189
|
if (rest[0] === 'cards' && rest.length >= 2) {
|
|
1864
2190
|
const kbParam = url.searchParams.get('kb')
|
|
1865
2191
|
const resolved = resolveBase(kbParam)
|
|
@@ -1915,12 +2241,14 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1915
2241
|
}
|
|
1916
2242
|
if (rest.length === 4 && rest[2] === 'versions') {
|
|
1917
2243
|
if (method !== 'GET') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 GET'))
|
|
1918
|
-
const
|
|
2244
|
+
const previewChars = clampInt(url.searchParams.get('preview_chars'), VERSION_DIFF_CHARS, 0, VERSION_DIFF_CHARS)
|
|
2245
|
+
const version = await resolved.runtime.readCardVersion(entityId, rest[3]!, entityType, previewChars)
|
|
1919
2246
|
if (version === undefined) return sendEnvelope(res, failure({}, 'VERSION_NOT_FOUND', `版本不存在: ${rest[3]}`, true))
|
|
1920
2247
|
return send(res, 200, success({ version }))
|
|
1921
2248
|
}
|
|
1922
2249
|
if (rest.length === 3 && rest[2] === 'revert') {
|
|
1923
2250
|
if (method !== 'POST') return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 POST'))
|
|
2251
|
+
// 兼容旧客户端的路径;运行时会明确拒绝恢复 WikiCard。
|
|
1924
2252
|
const body = await readBody(req)
|
|
1925
2253
|
const reverted = await resolved.runtime.revertCard(entityId, String(body.versionId ?? ''), entityType)
|
|
1926
2254
|
if (!reverted.ok) return sendEnvelope(res, reverted)
|
|
@@ -1939,6 +2267,26 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1939
2267
|
let documents = await resolved.runtime.listDocuments()
|
|
1940
2268
|
const search = (url.searchParams.get('query') ?? '').trim().toLocaleLowerCase()
|
|
1941
2269
|
if (search) documents = documents.filter(item => `${item.docId}\n${item.title}\n${item.externalId}`.toLocaleLowerCase().includes(search))
|
|
2270
|
+
/**
|
|
2271
|
+
* 文件类型的计数对着**搜索之后、类型筛选之前**的集合算。
|
|
2272
|
+
*
|
|
2273
|
+
* 下拉要能切换类型,所以每个类型的数量必须是「当前搜索下有多少份」,
|
|
2274
|
+
* 而不是「当前这一页里有多少份」—— 后者会让选项随翻页变来变去。
|
|
2275
|
+
*/
|
|
2276
|
+
const extCounts: Record<string, number> = {}
|
|
2277
|
+
for (const item of documents) {
|
|
2278
|
+
const key = item.ext || '—'
|
|
2279
|
+
extCounts[key] = (extCounts[key] ?? 0) + 1
|
|
2280
|
+
}
|
|
2281
|
+
/**
|
|
2282
|
+
* 文件类型筛选**在服务端**做(0070)。
|
|
2283
|
+
*
|
|
2284
|
+
* 原来是「客户端取前 500 份、再在这一页里筛」:超过 500 份的库,第 501 份起
|
|
2285
|
+
* 在面板上根本不可达,而表头把这种截断显示成「筛出 500 / 600 个」——
|
|
2286
|
+
* 读起来像用户自己开了筛选。既然这里已经支持 `offset`,筛选就该和分页同层。
|
|
2287
|
+
*/
|
|
2288
|
+
const ext = (url.searchParams.get('ext') ?? '').trim()
|
|
2289
|
+
if (ext) documents = documents.filter(item => item.ext === ext)
|
|
1942
2290
|
const total = documents.length
|
|
1943
2291
|
const offset = clampInt(url.searchParams.get('offset'), 0, 0, Number.MAX_SAFE_INTEGER)
|
|
1944
2292
|
const limit = clampInt(url.searchParams.get('limit'), 50, 1, 500)
|
|
@@ -1949,7 +2297,7 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
1949
2297
|
* 两件事。放成并列的一张表,前端按 `docId` 查,文档类型不用为审计字段让步。
|
|
1950
2298
|
*/
|
|
1951
2299
|
const extractionRuns = await resolved.runtime.extractionRunCounts()
|
|
1952
|
-
return send(res, 200, success({ documents: documents.slice(offset, offset + limit), total, extractionRuns }))
|
|
2300
|
+
return send(res, 200, success({ documents: documents.slice(offset, offset + limit), total, extCounts, extractionRuns }))
|
|
1953
2301
|
}
|
|
1954
2302
|
const docId = rest[1]!
|
|
1955
2303
|
const resolved = resolveBase(kbParam)
|
|
@@ -2067,6 +2415,6 @@ export function apply(ctx: Context, pluginConfig: KnowledgePluginConfig = {}): v
|
|
|
2067
2415
|
} },
|
|
2068
2416
|
]
|
|
2069
2417
|
const disposers = routes.map(register)
|
|
2070
|
-
return () => { runtime.close(); for (const dispose of disposers) void dispose() }
|
|
2418
|
+
return () => { environment.close(); runtime.close(); bases.closeAll(); for (const dispose of disposers) void dispose() }
|
|
2071
2419
|
})
|
|
2072
2420
|
}
|