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/lib/types/index.js
CHANGED
|
@@ -1,24 +1,27 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { embeddingModel } from './environment/model-catalog.js';
|
|
2
|
+
import { createReadStream } from 'node:fs';
|
|
2
3
|
import { readFile, stat } from 'node:fs/promises';
|
|
3
|
-
import { dirname, extname, isAbsolute,
|
|
4
|
+
import { dirname, extname, isAbsolute, resolve, sep } from 'node:path';
|
|
4
5
|
import { fileURLToPath } from 'node:url';
|
|
5
6
|
import { defineTool } from '@deepseek-ai/dsh-tools';
|
|
6
7
|
import { operationId } from './oplog.js';
|
|
7
8
|
import { AXIS_GUIDANCE_KINDS } from './axis-registry.js';
|
|
8
|
-
import { DEFAULT_KNOWLEDGE_CONFIG, } from './types.js';
|
|
9
|
+
import { DEFAULT_COMPILE_MODEL, DEFAULT_KNOWLEDGE_CONFIG, } from './types.js';
|
|
9
10
|
import { KnowledgeRuntime, MAX_IMPORT_SOURCES, SOURCE_PREVIEW_CHARS, VERSION_DIFF_CHARS, success, failure, } from './runtime.js';
|
|
10
11
|
import { unwrapGroup } from './settings-volatile.js';
|
|
12
|
+
import { behaviorPromptForPreset } from './agent-behavior-rules.js';
|
|
13
|
+
import { KnowledgeEnvironment } from './environment/service.js';
|
|
11
14
|
import { DOC_PAGE_CHARS, DOC_PAGE_CHARS_MAX, documentContentPage } from './document-page.js';
|
|
12
15
|
import { runWikiTrial, trialSummary } from './wiki-trial.js';
|
|
13
16
|
import { extractionRunId } from './extraction-runs.js';
|
|
14
|
-
import { agentLayout, defaultAgentRoot, ensureAgentLayout, intentRoot, knowledgeBaseRoot,
|
|
15
|
-
import { migrateLegacyKnowledgeTree, migrateLegacyMemory, migrateLegacyResources } from './migrate.js';
|
|
16
|
-
import { legacyResourceHome } from './types.js';
|
|
17
|
+
import { agentLayout, defaultAgentRoot, ensureAgentLayout, intentRoot, knowledgeBaseRoot, memoryRoot, resourceRegistry, resourceRoot, } from './layout.js';
|
|
17
18
|
import { PickerError, createDirectory, listDirectory, pickDirectory, pickerKind, pickerOf, } from './directory-picker.js';
|
|
18
19
|
import { KNOWLEDGE_SETTINGS_SCHEMA, SETTINGS_NAMESPACE } from './settings-schema.js';
|
|
19
20
|
import { DEFAULT_KB_ID, KnowledgeBases } from './knowledge-bases.js';
|
|
21
|
+
import { ImagePublishError, publishKnowledgeImage } from './images.js';
|
|
20
22
|
import { applyConvert, CONVERT_SETTINGS_NAMESPACE, CONVERT_SETTINGS_SCHEMA, MOUNT_PATH as CONVERT_MOUNT_PATH, } from './convert/host.js';
|
|
21
23
|
import { applyIntent, INTENT_MOUNT_PATH, INTENT_SERVICE, } from './intent/host.js';
|
|
24
|
+
import { registerIntentTools } from './intent/tools.js';
|
|
22
25
|
export * from './types.js';
|
|
23
26
|
export * from './runtime.js';
|
|
24
27
|
export * from './knowledge-bases.js';
|
|
@@ -31,14 +34,14 @@ export const name = 'dsh-data-domain-knowledge';
|
|
|
31
34
|
* `settings` 在 0.1.7 之后**不再需要**:设置已经是本 entry 的 config
|
|
32
35
|
* (见下面的 `Config`),`settings.installSection` 那条路已经整个删除。
|
|
33
36
|
*/
|
|
34
|
-
export const inject = ['tools', 'webServer'];
|
|
37
|
+
export const inject = ['tools', 'webServer', 'systemPrompt'];
|
|
35
38
|
// schema 与命名空间搬到了 `settings-schema.ts`(可测)。这里再导出一次,保持
|
|
36
39
|
// `dsh-data-domain-knowledge` 的既有出口不变。
|
|
37
40
|
export { KNOWLEDGE_SETTINGS_SCHEMA, SETTINGS_NAMESPACE };
|
|
38
41
|
export { CONVERT_SETTINGS_SCHEMA };
|
|
39
42
|
/**
|
|
40
43
|
* 组合 schema 与它的取值形态住在 `./config-schema.js`:那里**没有工具层依赖**,
|
|
41
|
-
* 所以 `tests/settings-schema.test.mjs` 能 import
|
|
44
|
+
* 所以 `tests/settings-schema.test.mjs` 能 import 它并把设置解析一遍(装载器
|
|
42
45
|
* 仍然要从**本模块**读到 `Config`,所以这里 import 之后再 re-export)。
|
|
43
46
|
*
|
|
44
47
|
* 0.1.7 删掉了「设置命名空间 + `settings.installSection`」这一整套 API,改成
|
|
@@ -47,7 +50,7 @@ export { CONVERT_SETTINGS_SCHEMA };
|
|
|
47
50
|
* 客户端按**路径**寻址(例如 `{op:'set', path:['knowledge','recallTopK']}`)。
|
|
48
51
|
*/
|
|
49
52
|
import { Config } from './config-schema.js';
|
|
50
|
-
import { buildModelRequest, collectModelText } from './model-text.js';
|
|
53
|
+
import { buildModelRequest, callModelWithRetry, collectModelText } from './model-text.js';
|
|
51
54
|
import { addSourceCallView, addSourceMetaOf, addSourceResultMarkdown, importCallView, importMetaOf, importResultMarkdown, recallCallView, recallMetaOf, recallResultMarkdown, rebuildCallView, rebuildMetaOf, rebuildResultMarkdown, writeCardsCallView, writeCardsMetaOf, writeCardsResultMarkdown, } from './present.js';
|
|
52
55
|
export { Config };
|
|
53
56
|
/**
|
|
@@ -198,7 +201,7 @@ function escapeHtml(value) {
|
|
|
198
201
|
/**
|
|
199
202
|
* 独立整页的 HTML 外壳。
|
|
200
203
|
*
|
|
201
|
-
*
|
|
204
|
+
* 与知识库原文整页相同的挂载形状,而且**只做三件事**:标题、挂载点、把自己那个 bundle
|
|
202
205
|
* 拉起来。正文不在外壳里 —— 页面自己去 `/knowledge/documents/:docId/content` 一页一页地取,
|
|
203
206
|
* 所以这份 HTML 的大小与文档大小无关。
|
|
204
207
|
*/
|
|
@@ -307,27 +310,21 @@ function normalizeKnowledge(next) {
|
|
|
307
310
|
return {
|
|
308
311
|
...DEFAULT_KNOWLEDGE_CONFIG,
|
|
309
312
|
...plain,
|
|
310
|
-
|
|
311
|
-
|
|
313
|
+
// 根目录是 DSH 下的 Tita Agent 专属目录,不再是用户可切换的设置。
|
|
314
|
+
// 旧值即使还存在于宿主配置中,也不参与路径解析。
|
|
315
|
+
agentRoot: defaultAgentRoot(),
|
|
316
|
+
// 仅保留字段形状,避免旧配置无法解析;新逻辑不读取旧知识树。
|
|
317
|
+
rootDir: '',
|
|
318
|
+
// 产品检索固定使用多路径融合。旧版单路选择仍可解析,但不再改变运行行为。
|
|
319
|
+
recallMode: 'hybrid',
|
|
312
320
|
};
|
|
313
321
|
}
|
|
314
322
|
export function apply(ctx, pluginConfig = {}) {
|
|
315
|
-
/**
|
|
316
|
-
* 0029:配置里现在有两个路径字段,但只有 `agentRoot` 是「设置」。
|
|
317
|
-
*
|
|
318
|
-
* `rootDir` 是旧布局的知识树根,只作为迁移来源存在。初始值给空串而不是旧默认值
|
|
319
|
-
* ——迁移的来源由下面的 `legacySource()` 决定,它还要考虑「没设过 rootDir 的
|
|
320
|
-
* 用户,旧树在旧默认位置」这一种情况。
|
|
321
|
-
*
|
|
322
|
-
* 0.1.7:值来自本 entry 的 `config`(`apply` 的第二个参数),不再是
|
|
323
|
-
* `settings.installSection` 推回来的命名空间。这里先 `normalizeKnowledge` 补齐,
|
|
324
|
-
* 之后的迁移与 runtime 都只认这一份配置。
|
|
325
|
-
*/
|
|
326
323
|
let config = normalizeKnowledge(pluginConfig.knowledge);
|
|
327
324
|
/**
|
|
328
325
|
* entry config 的另外两组,**按需求值**。
|
|
329
326
|
*
|
|
330
|
-
*
|
|
327
|
+
* 为什么是函数而不是先解包成一个值:`convert` 的可编辑字段标了 volatile,schema
|
|
331
328
|
* 解析出来是引用;解包一次就是一张快照,而 volatile 改动**不会重载插件**
|
|
332
329
|
* (loader 只就地改引用、再发一个 `loader/volatile-update`)。函数让下游每次
|
|
333
330
|
* 「重灌」都从同一个引用重新读值,改设置才真的会生效。
|
|
@@ -342,37 +339,16 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
342
339
|
* `applyIntent`)。
|
|
343
340
|
*/
|
|
344
341
|
let convertHost;
|
|
345
|
-
/** 知识树迁移的结论。`/knowledge/layout` 会带上它,界面据此如实交代数据搬没搬。 */
|
|
346
|
-
let migration = { status: 'skipped', reason: '尚未解析设置' };
|
|
347
|
-
/** 家当(模型 / Python 环境 / 注册表)迁移的结论。 */
|
|
348
|
-
let resourceMigration = { status: 'skipped', reason: '尚未解析设置' };
|
|
349
|
-
/** 记忆段(知识树 → `<agentRoot>/memory/<库 id>`)迁移的结论(0032)。 */
|
|
350
|
-
let memoryMigration = { status: 'skipped', reason: '尚未解析设置' };
|
|
351
342
|
/**
|
|
352
343
|
* 初始化失败的原因(如果有)。字段名保持 `settingsError`:`/knowledge/layout`
|
|
353
344
|
* 与面板上的告警都按这个名字读,改名的收益不如改语义的成本高。
|
|
354
345
|
*
|
|
355
346
|
* 旧的理由是「设置分区注册失败会被静默吞掉」(v0.7.5 的
|
|
356
347
|
* `z.string().min(1).default('')` 让整个命名空间注册不上)。0.1.7 之后 schema
|
|
357
|
-
* 错误会在装载 entry
|
|
358
|
-
*
|
|
348
|
+
* 错误会在装载 entry 时就抛、插件直接加载失败,不再是静默的;这里仍保留错误
|
|
349
|
+
* 视图,便于报告布局初始化失败。
|
|
359
350
|
*/
|
|
360
351
|
let settingsError;
|
|
361
|
-
/**
|
|
362
|
-
* 迁移只该发生一次 —— 实际含义是「每次 apply 最多一次」。
|
|
363
|
-
*
|
|
364
|
-
* 0.1.7 下改设置会让插件**整个重载**,apply 因此会再跑一遍。用「已经跑过」而不是
|
|
365
|
-
* 「结果如何」做闸门,是为了不让一个失败的迁移在重载时被反复重试。
|
|
366
|
-
*/
|
|
367
|
-
let migrationDone = false;
|
|
368
|
-
/**
|
|
369
|
-
* 迁移的旧位置来源。
|
|
370
|
-
*
|
|
371
|
-
* 显式设过 `rootDir` 就用它;否则退回旧默认位置(`DSH_KNOWLEDGE_ROOT` 或
|
|
372
|
-
* `~/Desktop/titaagent/domain_knowledge`)。判据交给 `migrateLegacyKnowledgeTree`,
|
|
373
|
-
* 这里只负责「谁可能是旧树」。
|
|
374
|
-
*/
|
|
375
|
-
const legacySource = () => config.rootDir.trim() || legacyDefaultKnowledgeRoot();
|
|
376
352
|
/**
|
|
377
353
|
* #3b:把 **DSH 的默认模型**包成运行时认识的纯文本调用。
|
|
378
354
|
*
|
|
@@ -380,10 +356,12 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
380
356
|
* DSH 自己的 `agentDefaultModel`(设置里那一项)。插件再存一份,就会出现「设置里显示
|
|
381
357
|
* A、实际调的是 B」—— 0029 想收拢的正是这类两处说法。
|
|
382
358
|
*
|
|
383
|
-
* 取不到 `llm`
|
|
384
|
-
*
|
|
359
|
+
* 取不到 `llm` 或所选模型时返回原因,链路**退回纯规则抽取**:模型是增强,不是前提。
|
|
360
|
+
* 选择“跟随默认”时才读取 `agentDefaultModel`;固定模型不依赖默认模型服务。
|
|
385
361
|
*/
|
|
386
|
-
|
|
362
|
+
/** 注册表在默认 runtime 之后才构造;用一个可替换的读取器避开构造期 TDZ。 */
|
|
363
|
+
let compileModelForBase = () => ({ ...DEFAULT_COMPILE_MODEL });
|
|
364
|
+
const modelSeamFor = (kbId = DEFAULT_KB_ID) => {
|
|
387
365
|
/**
|
|
388
366
|
* **只能用 `ctx.get(name)`,不能用属性访问。**
|
|
389
367
|
*
|
|
@@ -407,16 +385,49 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
407
385
|
* 所以那个类自己也是用 `this.config.provider.get()` 读的。第一版就是按普通属性读的,
|
|
408
386
|
* 结果是永远拿到 undefined —— 模型接缝静默失效。
|
|
409
387
|
*/
|
|
410
|
-
const
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
if (
|
|
415
|
-
|
|
388
|
+
const configured = compileModelForBase(kbId);
|
|
389
|
+
let defaultProvider;
|
|
390
|
+
let defaultModel;
|
|
391
|
+
let reasoningEffort;
|
|
392
|
+
if (configured.mode === 'default') {
|
|
393
|
+
const service = ctx.get('agentDefaultModel');
|
|
394
|
+
if (!service)
|
|
395
|
+
return { why: '当前上下文里取不到 `agentDefaultModel` 服务 —— 默认模型那一行可能没挂上' };
|
|
396
|
+
const route = service.currentSelection?.();
|
|
397
|
+
defaultProvider = route?.provider;
|
|
398
|
+
defaultModel = route?.model;
|
|
399
|
+
reasoningEffort = route?.reasoningEffort;
|
|
400
|
+
if (!defaultProvider || !defaultModel) {
|
|
401
|
+
return { why: `默认模型没有选好(provider=${defaultProvider ?? '空'} / model=${defaultModel ?? '空'})` };
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
const provider = configured.mode === 'fixed' ? configured.provider : defaultProvider;
|
|
405
|
+
const model = configured.mode === 'fixed' ? configured.model : defaultModel;
|
|
406
|
+
const effectiveReasoningEffort = configured.reasoningEffort || (configured.mode === 'default' ? reasoningEffort : undefined);
|
|
407
|
+
if (!provider || !model) {
|
|
408
|
+
return { why: `编译模型没有选好(provider=${provider ?? '空'} / model=${model ?? '空'})` };
|
|
416
409
|
}
|
|
417
|
-
const { provider, model, reasoningEffort } = route;
|
|
418
410
|
return {
|
|
419
|
-
|
|
411
|
+
provider,
|
|
412
|
+
model,
|
|
413
|
+
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
414
|
+
call: async ({ system, prompt, sessionId, onUsage, onDelta }) => {
|
|
415
|
+
/**
|
|
416
|
+
* 先走 DSH 的公开解析接口,再开始发 prompt。这样固定模型过期、默认模型失效时,
|
|
417
|
+
* 编译会在模型调用边界明确失败,而不是落成一次看似成功的规则抽取。
|
|
418
|
+
*/
|
|
419
|
+
if (llm.resolveCallConfig) {
|
|
420
|
+
try {
|
|
421
|
+
await llm.resolveCallConfig({
|
|
422
|
+
provider, model,
|
|
423
|
+
...(effectiveReasoningEffort ? { reasoningEffort: effectiveReasoningEffort } : {}),
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
catch (cause) {
|
|
427
|
+
const detail = cause instanceof Error ? cause.message : String(cause);
|
|
428
|
+
throw new Error(`编译模型不可用(${provider}/${model}):${detail}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
420
431
|
/**
|
|
421
432
|
* 请求形状与收流都交给 `model-text.ts`(0044 实机验证第三~五批)。
|
|
422
433
|
*
|
|
@@ -427,16 +438,31 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
427
438
|
* (OpenCode 系路由回 400 `MissingSessionID`)。
|
|
428
439
|
*/
|
|
429
440
|
const request = buildModelRequest({
|
|
430
|
-
provider, model, reasoningEffort, system, prompt,
|
|
441
|
+
provider, model, reasoningEffort: effectiveReasoningEffort, system, prompt,
|
|
431
442
|
// 运行时按知识库给;接缝被单独调用时(测试)退回一个稳定值,
|
|
432
443
|
// 而不是留空 —— 留空会在 OpenCode 路由上 400。
|
|
433
444
|
sessionId: sessionId ?? 'knowledge:default',
|
|
434
445
|
});
|
|
435
|
-
|
|
446
|
+
/**
|
|
447
|
+
* 流在**半路**结束要重发,不要一次失败就作废整份文档。
|
|
448
|
+
*
|
|
449
|
+
* 知识抽取是本插件里最长的一次模型调用(实机:约四万输出 token、两三分钟),
|
|
450
|
+
* 而上游网关 / 本机代理 / 账号额度任何一处抖一下,流就会在没给
|
|
451
|
+
* `finish_reason` 的情况下结束。适配器把这件事报成
|
|
452
|
+
* `TRANSPORT: Stream ended without finish_reason`,过去一次抖动就等于
|
|
453
|
+
* 「这份文档抽取失败」,人在界面上看到的是「全部失败」。
|
|
454
|
+
*
|
|
455
|
+
* 重发用的是**同一个请求**:提示词、sessionId、reasoningEffort 全不变,
|
|
456
|
+
* 所以缓存亲和仍然成立。不可重试的错误(凭据、请求体、截断、用户取消)
|
|
457
|
+
* 由 `isRetryableModelFailure` 放行,不在错的地方多等两分钟。
|
|
458
|
+
*/
|
|
459
|
+
return callModelWithRetry(onDeltaOfAttempt => collectModelText(llm.stream(request), onUsage, onDeltaOfAttempt), { ...(onDelta ? { onDelta } : {}) });
|
|
436
460
|
},
|
|
437
461
|
};
|
|
438
462
|
};
|
|
463
|
+
let environment;
|
|
439
464
|
const runtimeOptions = () => ({
|
|
465
|
+
environmentGuard: kind => environment.requireReady(kind),
|
|
440
466
|
// 0029:家当目录。配置里三个路径为空时,运行时从这里解析模型与解释器。
|
|
441
467
|
resourceRoot: resourceRoot(config.agentRoot),
|
|
442
468
|
// 0032:记忆段。默认库的记忆落在 `<agentRoot>/memory/default`,与知识树平级。
|
|
@@ -472,10 +498,122 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
472
498
|
// 0044:非默认库的 runtime 也要拿到模型接缝。只传 `config` 不够 —— 默认库走上面
|
|
473
499
|
// 那个 `new KnowledgeRuntime(... runtimeOptions())`,别的库走注册表这条支路,
|
|
474
500
|
// 两边必须都带上,否则「新建一个库 → 导入文档」静默变成规则抽取。
|
|
475
|
-
runtimeOptions: () => ({ modelSeam: modelSeamFor }),
|
|
501
|
+
runtimeOptions: () => ({ resourceRoot: resourceRoot(config.agentRoot), modelSeam: modelSeamFor, environmentGuard: kind => environment.requireReady(kind) }),
|
|
476
502
|
});
|
|
503
|
+
compileModelForBase = id => bases.compileModelOf(id);
|
|
477
504
|
runtime.baseId = bases.defaultId;
|
|
478
505
|
activeKnowledgeBases = bases;
|
|
506
|
+
environment = new KnowledgeEnvironment({
|
|
507
|
+
resourceRoot: resourceRoot(config.agentRoot),
|
|
508
|
+
snapshot: () => ({ config: runtime.config, paths: runtime.resourcePaths() }),
|
|
509
|
+
probe: kind => runtime.environmentProbe(kind),
|
|
510
|
+
compileStatus: () => runtime.compileModelStatus(),
|
|
511
|
+
reload: () => { runtime.reloadResources(); bases.reloadResources(); },
|
|
512
|
+
activateSelection: async (selection) => {
|
|
513
|
+
const keys = ['embeddingProvider', 'embeddingModel', 'embeddingDimension', 'rerankProvider', 'rerankModel'];
|
|
514
|
+
const previous = Object.fromEntries(keys.map(key => [key, config[key]]));
|
|
515
|
+
const managed = { embeddingProvider: 'wemm-python', embeddingModel: '', embeddingDimension: embeddingModel(selection.embedding).dimension, rerankProvider: 'bert-python', rerankModel: '' };
|
|
516
|
+
const write = async (values) => {
|
|
517
|
+
const group = pluginConfig.knowledge;
|
|
518
|
+
if (!group)
|
|
519
|
+
throw new Error('模型设置不可写');
|
|
520
|
+
for (const key of keys) {
|
|
521
|
+
const reference = group[key];
|
|
522
|
+
if (reference && typeof reference.write === 'function')
|
|
523
|
+
await reference.write(values[key]);
|
|
524
|
+
else
|
|
525
|
+
group[key] = values[key];
|
|
526
|
+
}
|
|
527
|
+
config = normalizeKnowledge(pluginConfig.knowledge);
|
|
528
|
+
runtime.updateConfig(config);
|
|
529
|
+
bases.updateRuntimeConfig(config);
|
|
530
|
+
};
|
|
531
|
+
try {
|
|
532
|
+
await write(managed);
|
|
533
|
+
}
|
|
534
|
+
catch (cause) {
|
|
535
|
+
await write(previous);
|
|
536
|
+
throw cause;
|
|
537
|
+
}
|
|
538
|
+
return () => {
|
|
539
|
+
const current = normalizeKnowledge(pluginConfig.knowledge);
|
|
540
|
+
// A later manual setting change belongs to the user and must survive rollback.
|
|
541
|
+
const restored = Object.fromEntries(keys.map(key => [key, current[key] === managed[key] ? previous[key] : current[key]]));
|
|
542
|
+
return write(restored);
|
|
543
|
+
};
|
|
544
|
+
},
|
|
545
|
+
});
|
|
546
|
+
// Reuse a matching persisted probe after restart; only stale or missing reports need a resource check.
|
|
547
|
+
if (environment.view().report.state !== 'ready')
|
|
548
|
+
void environment.check(false).catch(() => undefined);
|
|
549
|
+
const compileModelCatalog = async (kbId, queryModelRoute = {}) => {
|
|
550
|
+
const service = ctx.get('agentDefaultModel');
|
|
551
|
+
const defaultRoute = service?.currentSelection?.() ?? {};
|
|
552
|
+
const llm = ctx.get('llm');
|
|
553
|
+
const providers = [...new Set([...(llm?.listProviders?.() ?? []).map(provider => provider.id), defaultRoute.provider, compileModelForBase(kbId).provider].filter((value) => Boolean(value)))];
|
|
554
|
+
const models = [];
|
|
555
|
+
const failures = [];
|
|
556
|
+
if (!llm?.listModels) {
|
|
557
|
+
failures.push('当前上下文没有 DSH 模型目录服务');
|
|
558
|
+
}
|
|
559
|
+
else {
|
|
560
|
+
for (const provider of providers) {
|
|
561
|
+
try {
|
|
562
|
+
const entries = await llm.listModels(provider);
|
|
563
|
+
for (const entry of entries) {
|
|
564
|
+
if (!entry?.id)
|
|
565
|
+
continue;
|
|
566
|
+
models.push({
|
|
567
|
+
provider,
|
|
568
|
+
id: entry.id,
|
|
569
|
+
name: entry.name?.trim() || entry.id,
|
|
570
|
+
...(entry.description ? { description: entry.description } : {}),
|
|
571
|
+
available: true,
|
|
572
|
+
...(entry.id === defaultRoute.model && provider === defaultRoute.provider ? { current: true } : {}),
|
|
573
|
+
});
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (cause) {
|
|
577
|
+
failures.push(`${provider}: ${cause instanceof Error ? cause.message : String(cause)}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
const selection = compileModelForBase(kbId);
|
|
582
|
+
const effortProvider = selection.mode === 'fixed' ? selection.provider : defaultRoute.provider;
|
|
583
|
+
const effortModel = selection.mode === 'fixed' ? selection.model : defaultRoute.model;
|
|
584
|
+
const requestedProvider = queryModelRoute.provider || effortProvider;
|
|
585
|
+
const requestedModel = queryModelRoute.model || effortModel;
|
|
586
|
+
if (requestedProvider && requestedModel && llm?.resolveModelInfo) {
|
|
587
|
+
const item = models.find(model => model.provider === requestedProvider && model.id === requestedModel);
|
|
588
|
+
if (item) {
|
|
589
|
+
try {
|
|
590
|
+
item.reasoning = (await llm.resolveModelInfo(requestedProvider, requestedModel)).reasoning;
|
|
591
|
+
}
|
|
592
|
+
catch (cause) {
|
|
593
|
+
failures.push(`思考强度目录:${cause instanceof Error ? cause.message : String(cause)}`);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
const selected = selection.mode === 'fixed' && selection.provider && selection.model
|
|
598
|
+
? models.find(item => item.provider === selection.provider && item.id === selection.model)
|
|
599
|
+
: undefined;
|
|
600
|
+
if (selection.mode === 'fixed' && selection.provider && selection.model && !selected) {
|
|
601
|
+
models.unshift({ provider: selection.provider, id: selection.model, name: selection.model, available: false, current: true });
|
|
602
|
+
}
|
|
603
|
+
const defaultAvailable = Boolean(defaultRoute.provider && defaultRoute.model
|
|
604
|
+
&& models.some(item => item.provider === defaultRoute.provider && item.id === defaultRoute.model && item.available));
|
|
605
|
+
return {
|
|
606
|
+
selection,
|
|
607
|
+
default: {
|
|
608
|
+
...(defaultRoute.provider ? { provider: defaultRoute.provider } : {}),
|
|
609
|
+
...(defaultRoute.model ? { model: defaultRoute.model } : {}),
|
|
610
|
+
...(defaultRoute.reasoningEffort ? { reasoningEffort: defaultRoute.reasoningEffort } : {}),
|
|
611
|
+
available: defaultAvailable,
|
|
612
|
+
},
|
|
613
|
+
models,
|
|
614
|
+
...(failures.length > 0 ? { why: failures.join(';') } : {}),
|
|
615
|
+
};
|
|
616
|
+
};
|
|
479
617
|
const kbIdOf = (value) => {
|
|
480
618
|
if (value === undefined || value === null)
|
|
481
619
|
return bases.defaultId;
|
|
@@ -487,7 +625,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
487
625
|
*
|
|
488
626
|
* `Entry._commitVolatile()` 只调 `updateVolatile()` 就地改引用,再发一个
|
|
489
627
|
* `loader/volatile-update` 事件。所以「设置存下了但没生效」这件事只能在这里兜住 ——
|
|
490
|
-
* 设置 schema
|
|
628
|
+
* 设置 schema 里的可编辑字段标了 volatile(见 `settings-volatile.ts`),这两件事必须成对。
|
|
491
629
|
*
|
|
492
630
|
* 就地更新之所以够用,是因为 `pluginConfig` 就是宿主那份被改过的对象:重新
|
|
493
631
|
* `normalizeKnowledge` 一下就能拿到新值。
|
|
@@ -507,6 +645,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
507
645
|
const previousAgentRoot = config.agentRoot;
|
|
508
646
|
config = normalizeKnowledge(pluginConfig.knowledge);
|
|
509
647
|
runtime.updateConfig(config);
|
|
648
|
+
bases.updateRuntimeConfig(config);
|
|
510
649
|
/**
|
|
511
650
|
* 转换设置挂在同一个 entry 上,volatile 事件需要让转换宿主重新读取。
|
|
512
651
|
*/
|
|
@@ -540,68 +679,12 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
540
679
|
return run(target);
|
|
541
680
|
};
|
|
542
681
|
/**
|
|
543
|
-
*
|
|
682
|
+
* 初始化:只创建新布局,再构造 runtime。
|
|
544
683
|
*
|
|
545
|
-
*
|
|
546
|
-
*
|
|
547
|
-
* 与知识库列表里的路径仍然能对上账。
|
|
548
|
-
*/
|
|
549
|
-
const migrateAllMemory = () => {
|
|
550
|
-
const bases = activeKnowledgeBases;
|
|
551
|
-
if (!bases)
|
|
552
|
-
return { status: 'skipped', reason: '知识库注册表尚未就绪' };
|
|
553
|
-
let outcome = { status: 'skipped', reason: '每个知识库的记忆都在新位置' };
|
|
554
|
-
const roots = bases.list();
|
|
555
|
-
const defaultBase = roots.find(item => item.id === bases.defaultId);
|
|
556
|
-
// 默认库的根由配置推出来,可能还没出现在注册表里(首启);补上它。
|
|
557
|
-
const plan = defaultBase ? roots : [{ id: bases.defaultId, rootDir: defaultBaseRoot() }, ...roots];
|
|
558
|
-
for (const base of plan) {
|
|
559
|
-
try {
|
|
560
|
-
const moved = migrateLegacyMemory({ agentRoot: config.agentRoot, kbRoot: base.rootDir, kbId: base.id });
|
|
561
|
-
if (moved.status !== 'skipped')
|
|
562
|
-
outcome = moved;
|
|
563
|
-
}
|
|
564
|
-
catch (cause) {
|
|
565
|
-
outcome = {
|
|
566
|
-
status: 'failed',
|
|
567
|
-
from: join(base.rootDir, '04_memory'),
|
|
568
|
-
to: memoryRoot(config.agentRoot, base.id),
|
|
569
|
-
reason: cause instanceof Error ? cause.message : String(cause),
|
|
570
|
-
};
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
return outcome;
|
|
574
|
-
};
|
|
575
|
-
/**
|
|
576
|
-
* 初始化:迁移 → 布局 → 构造 runtime。
|
|
577
|
-
*
|
|
578
|
-
* 顺序是**硬要求**:配置已由外层的 `normalizeKnowledge` 补齐,这里先迁移(迁移要读
|
|
579
|
-
* 新的 `agentRoot` 与旧的 `rootDir`),最后才构造 runtime。反过来的话,runtime 会在
|
|
580
|
-
* 数据搬完之前就指着新路径,于是「刚启动时知识库是空的」——而那正是最容易让人以为
|
|
581
|
-
* 数据丢了的时刻。
|
|
582
|
-
*
|
|
583
|
-
* 0.1.7:这段以前挂在 `settings.installSection` 的 `setSource`/`onChange` 回调里,
|
|
584
|
-
* 因为配置要等设置层推回来。现在配置是 `apply` 的入参,所以直接在 apply 时跑一次;
|
|
585
|
-
* 改设置会让插件**整个重载**,于是它会随重载再跑,由 `migrationDone` 挡住重复迁移。
|
|
684
|
+
* 旧版的 `rootDir`、旧 Agent 根路径和旧家当目录都不再参与启动流程。这样新安装的
|
|
685
|
+
* 数据路径是确定的,也不会因为启动而改动用户现有目录。
|
|
586
686
|
*/
|
|
587
687
|
const initialize = () => {
|
|
588
|
-
if (!migrationDone) {
|
|
589
|
-
migrationDone = true;
|
|
590
|
-
// 顺序:先把知识树搬进新根路径,再把家当搬进来。家当要在 runtime 构造**之前**
|
|
591
|
-
// 到位 —— 否则模型与解释器会先按旧位置解析一次,那一轮跑的是旧环境。
|
|
592
|
-
migration = migrateLegacyKnowledgeTree({ agentRoot: config.agentRoot, legacyRoot: legacySource() });
|
|
593
|
-
resourceMigration = migrateLegacyResources({ agentRoot: config.agentRoot, legacyHome: legacyResourceHome() });
|
|
594
|
-
/**
|
|
595
|
-
* 0032:记忆段从知识树搬出去。**每个知识库各搬一次** —— 它按库分叉,而
|
|
596
|
-
* 非默认库的 runtime 是懒建的(`runtimeOf`),懒建里做迁移会把「打开一个库」
|
|
597
|
-
* 变成一次可能的搬盘。放在这里,顺序与另外两个迁移一致:搬完才构造 runtime。
|
|
598
|
-
*
|
|
599
|
-
* 逐个 try:一个库搬不动不该让其余的也不搬,更不该让插件起不来。
|
|
600
|
-
*/
|
|
601
|
-
memoryMigration = migrateAllMemory();
|
|
602
|
-
// 注册表可能刚被搬进来:让它按新位置重新读一遍。
|
|
603
|
-
void bases.repoint();
|
|
604
|
-
}
|
|
605
688
|
ensureAgentLayout(config.agentRoot);
|
|
606
689
|
runtime.close();
|
|
607
690
|
runtime = new KnowledgeRuntime(defaultBaseRoot(), config, runtimeOptions());
|
|
@@ -612,9 +695,9 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
612
695
|
/**
|
|
613
696
|
* 初始化失败必须留下痕迹。
|
|
614
697
|
*
|
|
615
|
-
*
|
|
616
|
-
*
|
|
617
|
-
*
|
|
698
|
+
* 创建新布局或构造 runtime 会因为权限、占用等原因失败;而它抛出的异常如果没人接,
|
|
699
|
+
* 就只是一次没人看见的失败。抓住它、记下来、让 `/knowledge/layout` 报出去,
|
|
700
|
+
* 界面于是能说「为什么不对」,而不是只显示症状。
|
|
618
701
|
*
|
|
619
702
|
* 注意这里**不吞配置错误**:schema 不合法的 config 在装载 entry 时就已经让插件
|
|
620
703
|
* 加载失败了(那是响亮的)。这里接住的只是初始化期的数据问题。
|
|
@@ -654,44 +737,119 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
654
737
|
// 0.1.7:转换的设置不再走命名空间,随本 entry 的 config 一起下来。
|
|
655
738
|
// 给函数而不是值:字段是 volatile 引用,解包一次就是快照(见 `convertGroup`)。
|
|
656
739
|
config: convertGroup,
|
|
657
|
-
// 0019:
|
|
658
|
-
//
|
|
659
|
-
//
|
|
740
|
+
// 0019: an omitted id selects the default base in the host. An explicit id
|
|
741
|
+
// must resolve here; otherwise ingestion fails instead of writing to a
|
|
742
|
+
// different knowledge base while reporting the requested id.
|
|
660
743
|
resolveTarget: (kbId) => {
|
|
661
|
-
const target =
|
|
662
|
-
return target ? { runtime: target, knowledgeRoot: target.rootDir } :
|
|
744
|
+
const target = runtimeOf(kbId);
|
|
745
|
+
return target ? { runtime: target, knowledgeRoot: target.rootDir } : undefined;
|
|
663
746
|
},
|
|
664
747
|
});
|
|
665
748
|
// 意图记录模块 (issue 0020). The 意图 view was already one of this plugin's
|
|
666
749
|
// five panel tabs, but its Host lived in a second mount unit; 0020 folds it in
|
|
667
750
|
// so the declared information architecture and the code layout agree again.
|
|
668
|
-
// It keeps the `intent_record` / `intent_store_view` tools and
|
|
751
|
+
// It keeps the `intent_record` / `intent_store_view` tools and `/intent/**` audit endpoints,
|
|
669
752
|
// and — critically — the `titaDataAgentIntent` Cordis service that `data-table`
|
|
670
753
|
// resolves by name.
|
|
671
754
|
// 意图只保留固定的会话审计契约;可编辑词表不再属于插件配置。
|
|
672
|
-
applyIntent(ctx, {
|
|
755
|
+
applyIntent(ctx, {
|
|
756
|
+
intentRoot: () => intentRoot(config.agentRoot),
|
|
757
|
+
});
|
|
758
|
+
// `applyIntent` provides this service from the same plugin fiber. During
|
|
759
|
+
// `apply()`, that fiber is still starting, so Cordis's default strict lookup
|
|
760
|
+
// hides the just-provided service until startup completes. The non-strict
|
|
761
|
+
// lookup is safe here because `applyIntent` ran immediately above.
|
|
762
|
+
const intentService = ctx.get(INTENT_SERVICE, false);
|
|
763
|
+
/**
|
|
764
|
+
* `intent_*` 工具在 Host 层注册,使标准模式可以调用它们;工具执行端只接受
|
|
765
|
+
* standard 会话,其他 preset 不会写入或读取意图记录。
|
|
766
|
+
*/
|
|
767
|
+
ctx.effect(() => registerIntentTools(ctx, intentService), 'tita knowledge: global intent tools');
|
|
673
768
|
/**
|
|
674
|
-
*
|
|
769
|
+
* 规则正文由当前会话 preset 决定;设置里的 volatile 引用在每次组装提示词时解包,
|
|
770
|
+
* 因此开关保存后立即生效,不需要重建 Agent 会话。
|
|
771
|
+
*/
|
|
772
|
+
ctx.effect(() => ctx.systemPrompt.section({
|
|
773
|
+
name: 'dsh-data-domain-knowledge:behavior-rules',
|
|
774
|
+
order: ctx.systemPrompt.getSectionOrder('TOOLS_SDK') + 1,
|
|
775
|
+
text: ({ agent }) => {
|
|
776
|
+
return behaviorPromptForPreset(agent?.session?.header?.agentPreset, unwrapGroup(pluginConfig.behaviorRules).enabled !== false);
|
|
777
|
+
},
|
|
778
|
+
}), 'tita knowledge: agent behavior rules');
|
|
779
|
+
/**
|
|
780
|
+
* 需要本轮用户审批的知识写工具。`image_publish` 只写入本地素材目录,按用户要求免审批。
|
|
675
781
|
*
|
|
676
782
|
* 0044 换过一批名字:`write_knowledge_card`(只接受已确认提议)退役了,因为正文现在
|
|
677
783
|
* 只读、卡片由编译直接写入;`list_card_proposals` 也退役了,因为待办队列不存在了。
|
|
678
784
|
* 新增的两个是**入库之后**的动作:加备注、review 抬分。
|
|
679
785
|
*/
|
|
680
|
-
|
|
786
|
+
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.', {
|
|
787
|
+
stage: { type: 'string', required: true, enum: ['extract', 'normalize', 'derive', 'materialize', 'build', 'all'] },
|
|
788
|
+
kb: { type: 'string' }, source_ids: { type: 'array', description: 'Optional explicit source IDs; omit for all sources. An empty selection is rejected.' },
|
|
789
|
+
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.' },
|
|
790
|
+
}, async (args) => withRuntime(args.kb, async (target) => {
|
|
791
|
+
const pipeline = await target.pipeline();
|
|
792
|
+
const { task } = pipeline.start(args.stage, { sourceIds: args.source_ids, groupKeys: args.group_keys, force: args.force === true });
|
|
793
|
+
return success({ task, url: `/knowledge/pages/tasks?kb=${encodeURIComponent(target.baseId)}&task=${encodeURIComponent(task.id)}` });
|
|
794
|
+
})));
|
|
795
|
+
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.', {
|
|
796
|
+
kb: { type: 'string' }, task_id: { type: 'string' }, after: { type: 'integer' },
|
|
797
|
+
}, async (args) => withRuntime(args.kb, async (target) => {
|
|
798
|
+
const pipeline = await target.pipeline();
|
|
799
|
+
return args.task_id
|
|
800
|
+
? success({ task: pipeline.store.task(args.task_id) ?? null, ...pipeline.store.events(args.task_id, Math.max(0, args.after ?? 0)) })
|
|
801
|
+
: success({ status: await target.pipelineStatus(), ...pipeline.store.tasks() });
|
|
802
|
+
})));
|
|
803
|
+
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']);
|
|
681
804
|
ctx.on('tools/pre-execute', (exec, next) => {
|
|
682
805
|
if (writeTools.has(exec.name))
|
|
683
806
|
return Promise.resolve({ kind: 'ask', reason: '该操作会写入 Tita Knowledge Store,需要本轮用户审批。' });
|
|
684
807
|
return next();
|
|
685
808
|
});
|
|
686
|
-
ctx.tools.register(tool('recall_knowledge', 'Recall
|
|
809
|
+
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, lineageOf(args, exec), { contextTopN: args.top_n, rerank: args.rerank, ...(typeof args.context_max_chars === 'number' ? { contextMaxChars: args.context_max_chars } : {}) })), {
|
|
687
810
|
call: recallCallView,
|
|
688
811
|
// 查询词从参数来,命中从结果来 —— 两者拼成一张能看懂的账本。
|
|
689
812
|
meta: (args, value) => recallMetaOf({ ...dataOf(value), query: args?.query }),
|
|
690
813
|
result: resultViewOf(recallResultMarkdown),
|
|
691
814
|
}));
|
|
815
|
+
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.', {
|
|
816
|
+
case_ids: { type: 'array', required: true, description: 'Case IDs from Wiki metadata.case_refs; maximum 50 IDs.' },
|
|
817
|
+
version: { type: 'integer', description: 'Optional historical Case version from metadata.case_refs; requires exactly one case ID.' },
|
|
818
|
+
kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' },
|
|
819
|
+
run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' },
|
|
820
|
+
}, async (args) => withRuntime(args.kb, async (target) => {
|
|
821
|
+
const ids = [...new Set((Array.isArray(args.case_ids) ? args.case_ids : [])
|
|
822
|
+
.filter((id) => typeof id === 'string' && id.trim() !== '').map(id => id.trim()))];
|
|
823
|
+
if (ids.length === 0)
|
|
824
|
+
return failure({ cases: [], missing_ids: [] }, 'CASE_IDS_REQUIRED', '请传入 Wiki 的 case_refs 中的 Case ID。', false);
|
|
825
|
+
if (ids.length > 50)
|
|
826
|
+
return failure({ cases: [], missing_ids: [] }, 'CASE_IDS_LIMIT', '一次最多读取 50 个 Case ID。', false);
|
|
827
|
+
if (args.version !== undefined && (ids.length !== 1 || !Number.isInteger(args.version) || args.version < 1))
|
|
828
|
+
return failure({ cases: [], missing_ids: ids }, 'CASE_VERSION_INVALID', '读取历史版本时,请传一个 Case ID 和正整数 version。', false);
|
|
829
|
+
const records = await Promise.all(ids.map(id => target.readCase(id, args.version)));
|
|
830
|
+
return success({ cases: records.filter(record => record !== undefined), missing_ids: ids.filter((_, index) => records[index] === undefined) });
|
|
831
|
+
})));
|
|
832
|
+
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.', {
|
|
833
|
+
event_ids: { type: 'array', required: true, description: 'Event IDs from WikiCard metadata.event_refs; maximum 50 IDs.' },
|
|
834
|
+
kb: { type: 'string' }, session_key: { type: 'string' }, task_id: { type: 'string' }, subtask_id: { type: 'string' },
|
|
835
|
+
run_id: { type: 'string' }, turn_id: { type: 'string' }, trace_id: { type: 'string' }, domain: { type: 'string' },
|
|
836
|
+
}, async (args) => withRuntime(args.kb, async (target) => {
|
|
837
|
+
const ids = [...new Set((Array.isArray(args.event_ids) ? args.event_ids : [])
|
|
838
|
+
.filter((id) => typeof id === 'string' && id.trim() !== '').map(id => id.trim()))];
|
|
839
|
+
if (ids.length === 0)
|
|
840
|
+
return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_REQUIRED', '请传入 WikiCard 的 event_refs 中的 Event ID。', false);
|
|
841
|
+
if (ids.length > 50)
|
|
842
|
+
return failure({ events: [], missing_ids: [] }, 'EVENT_IDS_LIMIT', '一次最多读取 50 个 Event ID;请分批读取该 WikiCard 的 event_refs。', false);
|
|
843
|
+
const byId = new Map((await target.loadEvents()).map(event => [event.id, event]));
|
|
844
|
+
const events = ids.flatMap(id => {
|
|
845
|
+
const event = byId.get(id);
|
|
846
|
+
return event ? [event] : [];
|
|
847
|
+
});
|
|
848
|
+
return success({ events, missing_ids: ids.filter(id => !byId.has(id)) });
|
|
849
|
+
})));
|
|
692
850
|
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())));
|
|
693
851
|
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)))));
|
|
694
|
-
ctx.tools.register(tool('query_source_truth', 'Read SourceRaw (原始可追溯文档) with access audit.
|
|
852
|
+
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))));
|
|
695
853
|
/**
|
|
696
854
|
* 知识库 id 的**发现入口**。
|
|
697
855
|
*
|
|
@@ -718,6 +876,30 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
718
876
|
buildVersion: base.buildVersion,
|
|
719
877
|
})),
|
|
720
878
|
})));
|
|
879
|
+
const imagePublishParameters = {
|
|
880
|
+
file_path: { type: 'string', description: '宿主可读取的本地图片文件路径;与 content_base64 二选一' },
|
|
881
|
+
content_base64: { type: 'string', description: '图片的标准 Base64,或 data:image/...;base64,...;与 file_path 二选一' },
|
|
882
|
+
filename: { type: 'string', description: '可选文件名;默认使用 file_path 的文件名,Base64 输入默认 image-<hash>.<ext>' },
|
|
883
|
+
mime_type: { type: 'string', description: '可选 MIME 类型;必须与图片二进制内容一致' },
|
|
884
|
+
kb: { type: 'string', description: '知识库 id;省略时写入默认库' },
|
|
885
|
+
};
|
|
886
|
+
const publishImage = async (args) => withRuntime(args.kb, async (target) => {
|
|
887
|
+
try {
|
|
888
|
+
return success(await publishKnowledgeImage(target.rootDir, target.baseId, {
|
|
889
|
+
filePath: args.file_path,
|
|
890
|
+
contentBase64: args.content_base64,
|
|
891
|
+
filename: args.filename,
|
|
892
|
+
mimeType: args.mime_type,
|
|
893
|
+
}));
|
|
894
|
+
}
|
|
895
|
+
catch (cause) {
|
|
896
|
+
if (cause instanceof ImagePublishError)
|
|
897
|
+
return failure({}, cause.code, cause.message, false, cause.suggestion);
|
|
898
|
+
return failure({}, 'IMAGE_PUBLISH_FAILED', `图片写入失败:${cause instanceof Error ? cause.message : String(cause)}`, true);
|
|
899
|
+
}
|
|
900
|
+
});
|
|
901
|
+
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.';
|
|
902
|
+
ctx.tools.register(tool('image_publish', imagePublishDescription, imagePublishParameters, publishImage));
|
|
721
903
|
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) })), {
|
|
722
904
|
call: addSourceCallView,
|
|
723
905
|
meta: (args, value) => addSourceMetaOf(args, dataOf(value)),
|
|
@@ -737,7 +919,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
737
919
|
* `compile` 默认开:批量导入的意图几乎总是「进来就能用」。关掉它的场景是「先全部
|
|
738
920
|
* 落盘、稍后统一编译」——那时卡片还没生成,工具返回里会明说 `compile: null`。
|
|
739
921
|
*/
|
|
740
|
-
ctx.tools.register(tool('import_sources', 'Import many SourceRaw documents
|
|
922
|
+
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) }));
|
|
741
923
|
/**
|
|
742
924
|
* `propose_wiki_cards` —— 把一批卡片**直接写进 `02_wiki`**(工具名不变,语义在 0044 改了)。
|
|
743
925
|
*
|
|
@@ -750,9 +932,9 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
750
932
|
* 这个工具服务于另一条路:Agent 手里已经有结构化的卡片草案(例如从一次排查里读出来的),
|
|
751
933
|
* 直接写进来,不必再走一遍抽取。
|
|
752
934
|
*/
|
|
753
|
-
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: '卡片数组,每项 {
|
|
935
|
+
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({
|
|
754
936
|
sourceId: args.source_id,
|
|
755
|
-
cards: (Array.isArray(args.proposals) ? args.proposals : []).map(item => ({ action: item
|
|
937
|
+
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 })),
|
|
756
938
|
lineage: lineageOf(args, exec),
|
|
757
939
|
})), {
|
|
758
940
|
call: writeCardsCallView,
|
|
@@ -767,7 +949,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
767
949
|
* 有效评分压到 `ANOMALY_SCORE` —— 异常卡片照样召回得到,只是排在后面,
|
|
768
950
|
* 而不是被藏起来(藏起来就没人会去修它)。
|
|
769
951
|
*/
|
|
770
|
-
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: '
|
|
952
|
+
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({
|
|
771
953
|
entityId: args.entity_id,
|
|
772
954
|
...(args.entity_type ? { entityType: args.entity_type } : {}),
|
|
773
955
|
...(args.kind ? { kind: args.kind } : {}),
|
|
@@ -791,7 +973,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
791
973
|
lineage: lineageOf(args, exec),
|
|
792
974
|
}))));
|
|
793
975
|
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, args.problem_note, lineageOf(args, exec)))));
|
|
794
|
-
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))), {
|
|
976
|
+
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 })), {
|
|
795
977
|
call: rebuildCallView,
|
|
796
978
|
meta: (args, value) => rebuildMetaOf(args?.only ?? 'all', dataOf(value)),
|
|
797
979
|
result: resultViewOf(rebuildResultMarkdown),
|
|
@@ -834,7 +1016,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
834
1016
|
* - `canLoadSample` 是空态的开关 —— 这个库还什么都没有时才为真。
|
|
835
1017
|
*/
|
|
836
1018
|
const diagnosisPayload = (snapshot) => ({
|
|
837
|
-
graph: snapshot.graph, cases: snapshot.cases, issues: snapshot.issues,
|
|
1019
|
+
graph: snapshot.graph, cases: snapshot.cases, events: snapshot.events, issues: snapshot.issues,
|
|
838
1020
|
seeded: snapshot.seeded, migrated: snapshot.migrated, source: snapshot.source,
|
|
839
1021
|
canLoadSample: snapshot.canLoadSample, problems: snapshot.problems,
|
|
840
1022
|
});
|
|
@@ -855,11 +1037,25 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
855
1037
|
const swept = await resolved.runtime.sweepOrphans();
|
|
856
1038
|
if (swept === null)
|
|
857
1039
|
return sendEnvelope(res, failure({}, 'KNOWLEDGE_UNAVAILABLE', '这个部署上没有 Knowledge 库', false));
|
|
858
|
-
const { knowledge, cards } = swept;
|
|
859
|
-
const total = knowledge.knowledgeDeleted + cards.deleted;
|
|
1040
|
+
const { knowledge, cards, events } = swept;
|
|
1041
|
+
const total = knowledge.knowledgeDeleted + cards.deleted + events.deleted + events.updated + swept.assets.files;
|
|
860
1042
|
return send(res, 200, success(swept, total === 0
|
|
861
|
-
? ['
|
|
862
|
-
: [`清掉了 ${knowledge.knowledgeDeleted}
|
|
1043
|
+
? ['没有孤儿:Knowledge、Event 和 WikiCard 的来源都还在库里。']
|
|
1044
|
+
: [`清掉了 ${knowledge.knowledgeDeleted} 条无来源 Knowledge、${events.deleted} 个无来源 Event(更新 ${events.updated} 个共享 Event)、${cards.deleted} 张无依据 WikiCard。`]));
|
|
1045
|
+
} },
|
|
1046
|
+
/**
|
|
1047
|
+
* DSH 模型目录(知识库编译设置)。编译模型不是插件再维护一套供应商列表,
|
|
1048
|
+
* 而是每次从 DSH 的 `llm.listModels()` 读取;这样固定模型下拉框与真正可调用的
|
|
1049
|
+
* 模型来源保持一致,默认模型失效时也能把状态说清楚。
|
|
1050
|
+
*/
|
|
1051
|
+
{ kind: 'exact', path: '/knowledge/models', handler: async (req, res) => {
|
|
1052
|
+
if (req.method !== 'GET')
|
|
1053
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 GET'));
|
|
1054
|
+
const url = query(req);
|
|
1055
|
+
const id = kbIdOf(url.searchParams.get('kb'));
|
|
1056
|
+
if (!bases.get(id))
|
|
1057
|
+
return sendEnvelope(res, failure({}, 'BASE_NOT_FOUND', `知识库不存在: ${id}`, true, '先 GET /knowledge/bases 拿到可用 id。'));
|
|
1058
|
+
return send(res, 200, success(await compileModelCatalog(id, { provider: url.searchParams.get('provider') ?? undefined, model: url.searchParams.get('model') ?? undefined })));
|
|
863
1059
|
} },
|
|
864
1060
|
{ kind: 'exact', path: '/knowledge/stats', handler: async (req, res) => {
|
|
865
1061
|
const resolved = resolveBase(query(req).searchParams.get('kb'));
|
|
@@ -867,6 +1063,22 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
867
1063
|
return sendEnvelope(res, resolved.failure);
|
|
868
1064
|
return send(res, 200, success(await resolved.runtime.stats()));
|
|
869
1065
|
} },
|
|
1066
|
+
{ kind: 'exact', path: '/knowledge/cases', handler: async (req, res) => {
|
|
1067
|
+
if (req.method !== 'GET')
|
|
1068
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', 'Case 只接受 GET'));
|
|
1069
|
+
const resolved = resolveBase(query(req).searchParams.get('kb'));
|
|
1070
|
+
if ('failure' in resolved)
|
|
1071
|
+
return sendEnvelope(res, resolved.failure);
|
|
1072
|
+
return send(res, 200, success({ cases: await resolved.runtime.loadEvents() }));
|
|
1073
|
+
} },
|
|
1074
|
+
{ kind: 'exact', path: '/knowledge/events', handler: async (req, res) => {
|
|
1075
|
+
if (req.method !== 'GET')
|
|
1076
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', 'Event 只接受 GET'));
|
|
1077
|
+
const resolved = resolveBase(query(req).searchParams.get('kb'));
|
|
1078
|
+
if ('failure' in resolved)
|
|
1079
|
+
return sendEnvelope(res, resolved.failure);
|
|
1080
|
+
return send(res, 200, success({ events: await resolved.runtime.diagnosisStore().listEvents() }));
|
|
1081
|
+
} },
|
|
870
1082
|
{ kind: 'exact', path: '/knowledge/search', handler: async (req, res) => {
|
|
871
1083
|
const url = query(req);
|
|
872
1084
|
const resolved = resolveBase(url.searchParams.get('kb'));
|
|
@@ -885,6 +1097,28 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
885
1097
|
...(url.searchParams.get('detail') === 'true' ? { detail: true } : {}),
|
|
886
1098
|
}));
|
|
887
1099
|
} },
|
|
1100
|
+
{ kind: 'exact', path: '/knowledge/environment', handler: async (req, res) => {
|
|
1101
|
+
if (req.method !== 'GET')
|
|
1102
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '只支持 GET'));
|
|
1103
|
+
return send(res, 200, success(environment.view()));
|
|
1104
|
+
} },
|
|
1105
|
+
{ kind: 'exact', path: '/knowledge/environment/prepare', handler: async (req, res) => {
|
|
1106
|
+
if (req.method !== 'POST')
|
|
1107
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '只支持 POST'));
|
|
1108
|
+
try {
|
|
1109
|
+
const body = await readBody(req, 4000);
|
|
1110
|
+
if (body?.action === 'cancel') {
|
|
1111
|
+
environment.cancel();
|
|
1112
|
+
return send(res, 200, success(environment.view()));
|
|
1113
|
+
}
|
|
1114
|
+
if (body?.action !== 'check' && body?.action !== 'install')
|
|
1115
|
+
return send(res, 400, failure({}, 'ACTION_INVALID', 'action 必须是 check、install 或 cancel', false));
|
|
1116
|
+
return send(res, 200, success(environment.start(body.action, body.selection)));
|
|
1117
|
+
}
|
|
1118
|
+
catch (cause) {
|
|
1119
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
1120
|
+
}
|
|
1121
|
+
} },
|
|
888
1122
|
{ kind: 'exact', path: '/knowledge/embedding/health', handler: async (req, res) => {
|
|
889
1123
|
const resolved = resolveBase(query(req).searchParams.get('kb'));
|
|
890
1124
|
if ('failure' in resolved)
|
|
@@ -928,6 +1162,97 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
928
1162
|
return sendEnvelope(res, resolved.failure);
|
|
929
1163
|
return send(res, 200, success({ progress: resolved.runtime.compileProgressView(url.searchParams.get('runId') ?? undefined) }));
|
|
930
1164
|
} },
|
|
1165
|
+
{ kind: 'exact', path: '/knowledge/pipeline', handler: async (req, res) => {
|
|
1166
|
+
try {
|
|
1167
|
+
const body = req.method === 'POST' ? await readBody(req, 100_000) : {};
|
|
1168
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'));
|
|
1169
|
+
if ('failure' in resolved)
|
|
1170
|
+
return sendEnvelope(res, resolved.failure);
|
|
1171
|
+
if (req.method === 'GET')
|
|
1172
|
+
return send(res, 200, success(await resolved.runtime.pipelineStatus()));
|
|
1173
|
+
if (req.method !== 'POST')
|
|
1174
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '知识处理只接受 GET 或 POST'));
|
|
1175
|
+
const pipeline = await resolved.runtime.pipeline();
|
|
1176
|
+
const started = pipeline.start(body.stage, { sourceIds: body.sourceIds, groupKeys: body.groupKeys, force: body.force === true });
|
|
1177
|
+
return send(res, 202, success({ task: started.task }));
|
|
1178
|
+
}
|
|
1179
|
+
catch (cause) {
|
|
1180
|
+
return send(res, 400, envelopeError('PIPELINE_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
1181
|
+
}
|
|
1182
|
+
} },
|
|
1183
|
+
{ kind: 'exact', path: '/knowledge/pipeline-tasks', handler: async (req, res) => {
|
|
1184
|
+
try {
|
|
1185
|
+
if (req.method !== 'GET')
|
|
1186
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务历史只接受 GET'));
|
|
1187
|
+
const url = query(req);
|
|
1188
|
+
const resolved = resolveBase(url.searchParams.get('kb'));
|
|
1189
|
+
if ('failure' in resolved)
|
|
1190
|
+
return sendEnvelope(res, resolved.failure);
|
|
1191
|
+
const pipeline = await resolved.runtime.pipeline();
|
|
1192
|
+
const limit = Math.min(100, Math.max(1, Number(url.searchParams.get('limit')) || 20));
|
|
1193
|
+
const offset = Math.max(0, Number(url.searchParams.get('offset')) || 0);
|
|
1194
|
+
return send(res, 200, success({ ...pipeline.store.tasks(limit, offset), extractions: await resolved.runtime.listExtractionRuns({ limit, offset }) }));
|
|
1195
|
+
}
|
|
1196
|
+
catch (cause) {
|
|
1197
|
+
return send(res, 400, envelopeError('PIPELINE_HISTORY_FAILED', cause instanceof Error ? cause.message : String(cause)));
|
|
1198
|
+
}
|
|
1199
|
+
} },
|
|
1200
|
+
{ kind: 'exact', path: '/knowledge/pipeline-task', handler: async (req, res) => {
|
|
1201
|
+
try {
|
|
1202
|
+
const url = query(req);
|
|
1203
|
+
const body = req.method === 'POST' ? await readBody(req, 20_000) : {};
|
|
1204
|
+
const resolved = resolveBase(body.kb ?? url.searchParams.get('kb'));
|
|
1205
|
+
if ('failure' in resolved)
|
|
1206
|
+
return sendEnvelope(res, resolved.failure);
|
|
1207
|
+
const pipeline = await resolved.runtime.pipeline();
|
|
1208
|
+
const id = typeof body.id === 'string' ? body.id : url.searchParams.get('id') ?? '';
|
|
1209
|
+
const task = pipeline.store.task(id);
|
|
1210
|
+
if (!task)
|
|
1211
|
+
return send(res, 404, envelopeError('PIPELINE_NOT_FOUND', '任务不存在'));
|
|
1212
|
+
if (req.method === 'GET')
|
|
1213
|
+
return send(res, 200, success({ task, ...pipeline.store.events(id, Math.max(0, Number(url.searchParams.get('after')) || 0)) }));
|
|
1214
|
+
if (req.method !== 'POST')
|
|
1215
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '任务只接受 GET 或 POST'));
|
|
1216
|
+
if (body.action === 'retry')
|
|
1217
|
+
return send(res, 202, success({ task: pipeline.start(task.stage, { ...task.selection, force: false }).task }));
|
|
1218
|
+
if (!['pause', 'resume', 'cancel'].includes(body.action))
|
|
1219
|
+
throw new Error('不支持的任务操作');
|
|
1220
|
+
return send(res, 200, success({ task: pipeline.control(id, body.action) }));
|
|
1221
|
+
}
|
|
1222
|
+
catch (cause) {
|
|
1223
|
+
return send(res, 409, envelopeError('PIPELINE_TASK_REJECTED', cause instanceof Error ? cause.message : String(cause)));
|
|
1224
|
+
}
|
|
1225
|
+
} },
|
|
1226
|
+
{ kind: 'exact', path: '/knowledge/extraction-tasks', handler: async (req, res) => {
|
|
1227
|
+
if (req.method !== 'GET')
|
|
1228
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '抽取任务只接受 GET'));
|
|
1229
|
+
const resolved = resolveBase(query(req).searchParams.get('kb'));
|
|
1230
|
+
if ('failure' in resolved)
|
|
1231
|
+
return sendEnvelope(res, resolved.failure);
|
|
1232
|
+
return send(res, 200, success({ tasks: resolved.runtime.listCompileTasks() }));
|
|
1233
|
+
} },
|
|
1234
|
+
{ kind: 'exact', path: '/knowledge/extraction-task', handler: async (req, res) => {
|
|
1235
|
+
if (req.method !== 'POST')
|
|
1236
|
+
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '抽取任务控制只接受 POST'));
|
|
1237
|
+
try {
|
|
1238
|
+
const body = await readBody(req, 20_000);
|
|
1239
|
+
const resolved = resolveBase(body.kb ?? query(req).searchParams.get('kb'));
|
|
1240
|
+
if ('failure' in resolved)
|
|
1241
|
+
return sendEnvelope(res, resolved.failure);
|
|
1242
|
+
const runId = typeof body.run_id === 'string' ? body.run_id.trim() : '';
|
|
1243
|
+
const action = body.action;
|
|
1244
|
+
if (!runId || (action !== 'pause' && action !== 'resume' && action !== 'cancel')) {
|
|
1245
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', '需要 run_id 和 pause / resume / cancel 操作'));
|
|
1246
|
+
}
|
|
1247
|
+
const result = resolved.runtime.controlCompileTask(runId, action);
|
|
1248
|
+
return result.ok
|
|
1249
|
+
? send(res, 200, success({ task: result.task }))
|
|
1250
|
+
: send(res, 409, envelopeError('TASK_CONTROL_REJECTED', result.why ?? '任务状态不支持此操作'));
|
|
1251
|
+
}
|
|
1252
|
+
catch (cause) {
|
|
1253
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
1254
|
+
}
|
|
1255
|
+
} },
|
|
931
1256
|
/**
|
|
932
1257
|
* 把一个知识库**导出成目录形态**(0062 v2 §3.7 末)。
|
|
933
1258
|
*
|
|
@@ -971,7 +1296,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
971
1296
|
if ('failure' in resolved)
|
|
972
1297
|
return sendEnvelope(res, resolved.failure);
|
|
973
1298
|
return send(res, 202, success(resolved.runtime.startCompile(body.only ?? 'all', lineageOf(body), {
|
|
974
|
-
|
|
1299
|
+
extract: body.extract === true,
|
|
975
1300
|
...(body.forceExtract === true ? { forceExtract: true } : {}),
|
|
976
1301
|
})));
|
|
977
1302
|
}
|
|
@@ -1001,7 +1326,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1001
1326
|
...(item?.external_id ? { externalId: String(item.external_id) } : {}),
|
|
1002
1327
|
...(item?.metadata && typeof item.metadata === 'object' ? { metadata: item.metadata } : {}),
|
|
1003
1328
|
})),
|
|
1004
|
-
|
|
1329
|
+
compile: body.compile === true,
|
|
1005
1330
|
lineage: lineageOf(body),
|
|
1006
1331
|
}));
|
|
1007
1332
|
}
|
|
@@ -1024,7 +1349,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1024
1349
|
const resolved = resolveBase(url.searchParams.get('kb'));
|
|
1025
1350
|
if ('failure' in resolved)
|
|
1026
1351
|
return sendEnvelope(res, resolved.failure);
|
|
1027
|
-
return send(res, 200, await resolved.runtime.sourceTruth(url.searchParams.get('query') ?? '', Number(url.searchParams.get('limit') ?? 20) || 20, Number(url.searchParams.get('max_chars') ?? 40000) || 40000, lineageFromUrl(url), Number(url.searchParams.get('offset') ?? 0) || 0));
|
|
1352
|
+
return send(res, 200, await resolved.runtime.sourceTruth(url.searchParams.get('query') ?? '', Number(url.searchParams.get('limit') ?? 20) || 20, Number(url.searchParams.get('max_chars') ?? 40000) || 40000, lineageFromUrl(url), Number(url.searchParams.get('offset') ?? 0) || 0, Number(url.searchParams.get('source_offset') ?? 0) || 0));
|
|
1028
1353
|
}
|
|
1029
1354
|
catch (cause) {
|
|
1030
1355
|
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
@@ -1094,7 +1419,8 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1094
1419
|
* 任何一条轨迹 —— 「这个实体是哪次抽出来的」就答不上了。
|
|
1095
1420
|
*/
|
|
1096
1421
|
const runId = extractionRunId();
|
|
1097
|
-
const seam = modelSeamFor();
|
|
1422
|
+
const seam = modelSeamFor(resolved.runtime.baseId);
|
|
1423
|
+
const draft = await (await resolved.runtime.axisRegistry())?.draft();
|
|
1098
1424
|
const axesBlock = await resolved.runtime.axisPromptBlockView();
|
|
1099
1425
|
const note = resolved.runtime.extractionNote();
|
|
1100
1426
|
const report = await runWikiTrial({
|
|
@@ -1102,14 +1428,17 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1102
1428
|
text,
|
|
1103
1429
|
...(docId ? { docId } : {}),
|
|
1104
1430
|
...(typeof body.subject === 'string' && body.subject.trim() ? { subject: body.subject.trim() } : {}),
|
|
1105
|
-
/**
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1431
|
+
/** 与编译相同:先查注册表,再把有候选的坐标合并成一次模型补判。 */
|
|
1432
|
+
resolveAxisSet: axes => resolved.runtime.resolveTrialAxisSet({
|
|
1433
|
+
registry: draft?.registry, axes,
|
|
1434
|
+
axesBlock,
|
|
1435
|
+
runId,
|
|
1436
|
+
call: seam.call,
|
|
1437
|
+
modelRequest: {
|
|
1438
|
+
...(seam.provider ? { provider: seam.provider } : {}),
|
|
1439
|
+
...(seam.model ? { model: seam.model } : {}),
|
|
1440
|
+
...(seam.reasoningEffort ? { reasoningEffort: seam.reasoningEffort } : {}),
|
|
1441
|
+
},
|
|
1113
1442
|
}),
|
|
1114
1443
|
/**
|
|
1115
1444
|
* 已有坐标(0068)。**与真编译下发同一份** —— 试跑看到的世界与真编译看到的
|
|
@@ -1117,9 +1446,14 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1117
1446
|
*/
|
|
1118
1447
|
...(axesBlock === undefined ? {} : { axes: axesBlock }),
|
|
1119
1448
|
...(note ? { note } : {}),
|
|
1449
|
+
modelRequest: {
|
|
1450
|
+
...(seam.provider ? { provider: seam.provider } : {}),
|
|
1451
|
+
...(seam.model ? { model: seam.model } : {}),
|
|
1452
|
+
...(seam.reasoningEffort ? { reasoningEffort: seam.reasoningEffort } : {}),
|
|
1453
|
+
},
|
|
1120
1454
|
...('call' in seam ? { call: seam.call } : {}),
|
|
1121
1455
|
...('why' in seam ? { callWhy: seam.why } : {}),
|
|
1122
|
-
});
|
|
1456
|
+
}).finally(() => draft?.close());
|
|
1123
1457
|
const summary = trialSummary(report);
|
|
1124
1458
|
/**
|
|
1125
1459
|
* 落库。写不进去**不改这次试跑的结论** —— 报告照样返回,只是多一句
|
|
@@ -1133,24 +1467,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1133
1467
|
report: { ...report, summary },
|
|
1134
1468
|
lineage: lineageOf(body),
|
|
1135
1469
|
});
|
|
1136
|
-
|
|
1137
|
-
* 把这次抽出来的 Knowledge 收进真源(0063 §2)。
|
|
1138
|
-
*
|
|
1139
|
-
* **只在跑一份已入库的文档时收。** 理由有两条:来源要指得回一份**真实的**
|
|
1140
|
-
* 文档(临时正文的 `documentId` 是一个标题字符串,存下来就是悬空引用);
|
|
1141
|
-
* 而「这条知识是哪份文档说的」正是它可验证的前提。
|
|
1142
|
-
*
|
|
1143
|
-
* **只收引文核对得上的那些。** 引文对不上原文 = 模型改写了,那条判断不是从
|
|
1144
|
-
* 原文推出来的(§5.7 的引文核验)。把它当知识存进真源,等于让一句编出来的话
|
|
1145
|
-
* 看起来像有出处 —— 而报告里已经把它报成例外了,那里才是它该待的地方。
|
|
1146
|
-
*/
|
|
1147
|
-
const ingest = docId
|
|
1148
|
-
? await resolved.runtime.ingestTrialKnowledge({
|
|
1149
|
-
runId, knowledge: report.knowledge,
|
|
1150
|
-
objects: report.objects, actions: report.actions, scenes: report.scenes,
|
|
1151
|
-
})
|
|
1152
|
-
: { saved: 0, merged: 0, skipped: 0 };
|
|
1153
|
-
return send(res, 200, success({ ...report, summary, run, ingest }));
|
|
1470
|
+
return send(res, 200, success({ ...report, summary, run }));
|
|
1154
1471
|
}
|
|
1155
1472
|
catch (cause) {
|
|
1156
1473
|
return send(res, 400, envelopeError('REQUEST_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
@@ -1199,7 +1516,9 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1199
1516
|
* 必须分开:`get()` 给 `''`,缺席给 `null`,这个区别正好用得上。
|
|
1200
1517
|
*/
|
|
1201
1518
|
const status = url.searchParams.get('status');
|
|
1519
|
+
const kind = url.searchParams.get('kind');
|
|
1202
1520
|
const filter = {
|
|
1521
|
+
...(kind === 'fact' || kind === 'procedure' ? { kind } : {}),
|
|
1203
1522
|
...(status === 'candidate' || status === 'active' || status === 'disputed' || status === 'deprecated' ? { status } : {}),
|
|
1204
1523
|
...(url.searchParams.get('run_id') === null ? {} : { runId: url.searchParams.get('run_id') }),
|
|
1205
1524
|
...(url.searchParams.get('object_id') === null ? {} : { objectId: url.searchParams.get('object_id') }),
|
|
@@ -1292,24 +1611,46 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1292
1611
|
const kind = kindParam === 'object' || kindParam === 'action' || kindParam === 'scene' ? kindParam : undefined;
|
|
1293
1612
|
const status = statusParam === 'candidate' || statusParam === 'active' || statusParam === 'deprecated' || statusParam === 'merged' ? statusParam : undefined;
|
|
1294
1613
|
if (req.method === 'GET') {
|
|
1295
|
-
const [entities, stats] = await Promise.all([
|
|
1614
|
+
const [entities, stats, relations] = await Promise.all([
|
|
1296
1615
|
resolved.runtime.listAxisEntities(kind, status),
|
|
1297
1616
|
resolved.runtime.axisStats(),
|
|
1617
|
+
resolved.runtime.listAxisRelations(),
|
|
1298
1618
|
]);
|
|
1299
|
-
if (entities === null || stats === null)
|
|
1300
|
-
return send(res, 200, success({ entities: [], stats: null, guidance: {}, available: false }));
|
|
1619
|
+
if (entities === null || stats === null || relations === null)
|
|
1620
|
+
return send(res, 200, success({ entities: [], stats: null, guidance: {}, relations: [], available: false }));
|
|
1301
1621
|
/*
|
|
1302
1622
|
* 指导随实体一起给(0068)。分开一条路由要多一次往返,而两样东西永远一起显示 ——
|
|
1303
1623
|
* 一条指导离开它挂着的那个实体就没有意义。
|
|
1304
1624
|
*/
|
|
1305
1625
|
const guidance = await resolved.runtime.axisGuidanceMap();
|
|
1306
|
-
return send(res, 200, success({ entities, stats, guidance, available: true }));
|
|
1626
|
+
return send(res, 200, success({ entities, stats, guidance, relations, available: true }));
|
|
1307
1627
|
}
|
|
1308
1628
|
if (req.method !== 'POST')
|
|
1309
1629
|
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '轴注册表只接受 GET 或 POST'));
|
|
1310
1630
|
const body = await readBody(req);
|
|
1311
1631
|
const id = typeof body.id === 'string' ? body.id.trim() : '';
|
|
1312
1632
|
const action = typeof body.action === 'string' ? body.action.trim() : '';
|
|
1633
|
+
if (action === 'relation-add') {
|
|
1634
|
+
const fromId = typeof body.from_id === 'string' ? body.from_id.trim() : '';
|
|
1635
|
+
const toId = typeof body.to_id === 'string' ? body.to_id.trim() : '';
|
|
1636
|
+
const relationType = typeof body.relation_type === 'string' ? body.relation_type.trim() : 'related_to';
|
|
1637
|
+
const evidence = typeof body.evidence === 'string' ? body.evidence : '';
|
|
1638
|
+
if (!fromId || !toId)
|
|
1639
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', '添加相关对象关系需要 from_id 与 to_id'));
|
|
1640
|
+
if (relationType !== 'related_to')
|
|
1641
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', '当前只支持 related_to 对象关系'));
|
|
1642
|
+
const relation = await resolved.runtime.addAxisRelation(fromId, toId, evidence);
|
|
1643
|
+
if (relation === null)
|
|
1644
|
+
return sendEnvelope(res, failure({}, 'AXIS_RELATION_INVALID', '关系无效:两个对象必须存在、未合并且不能是同一个对象', false));
|
|
1645
|
+
return send(res, 200, success({ relation }));
|
|
1646
|
+
}
|
|
1647
|
+
if (action === 'relation-remove') {
|
|
1648
|
+
const relationId = typeof body.relation_id === 'string' ? body.relation_id.trim() : '';
|
|
1649
|
+
if (!relationId)
|
|
1650
|
+
return send(res, 400, envelopeError('REQUEST_INVALID', '移除关系需要 relation_id'));
|
|
1651
|
+
const removed = await resolved.runtime.removeAxisRelation(relationId);
|
|
1652
|
+
return send(res, 200, success({ removed }));
|
|
1653
|
+
}
|
|
1313
1654
|
if (action === 'merge') {
|
|
1314
1655
|
const into = typeof body.into === 'string' ? body.into.trim() : '';
|
|
1315
1656
|
if (!id || !into)
|
|
@@ -1381,7 +1722,10 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1381
1722
|
return send(res, 400, envelopeError('REQUEST_INVALID', `不认识的 action: ${action || '(空)'}`));
|
|
1382
1723
|
} },
|
|
1383
1724
|
/**
|
|
1384
|
-
*
|
|
1725
|
+
* 轨迹(`extraction_run`):按文档或阶段列、按 id 读全文。
|
|
1726
|
+
*
|
|
1727
|
+
* 2026-10-18 起三段共用这张表(`stage` 列:`extract` / `normalize` / `materialize`)。
|
|
1728
|
+
* `stage` 省略 = 三段一起 —— 面板里那份「这份文档的轨迹」就是这么读的。
|
|
1385
1729
|
*
|
|
1386
1730
|
* 列表**不返回报告本体** —— 一条报告几十到几百 KB,翻一页列表不该按全文付费。
|
|
1387
1731
|
* 点开某一条走 `/knowledge/extraction-run?id=…`(单独一条路由,不是路径参数:
|
|
@@ -1395,8 +1739,11 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1395
1739
|
if ('failure' in resolved)
|
|
1396
1740
|
return sendEnvelope(res, resolved.failure);
|
|
1397
1741
|
const docId = url.searchParams.get('doc_id');
|
|
1742
|
+
const stageParam = url.searchParams.get('stage');
|
|
1743
|
+
const stage = stageParam === 'extract' || stageParam === 'normalize' || stageParam === 'materialize' ? stageParam : undefined;
|
|
1398
1744
|
const page = await resolved.runtime.listExtractionRuns({
|
|
1399
1745
|
...(docId === null ? {} : { docId }),
|
|
1746
|
+
...(stage === undefined ? {} : { stage }),
|
|
1400
1747
|
limit: clampInt(url.searchParams.get('limit'), 20, 1, 100),
|
|
1401
1748
|
offset: clampInt(url.searchParams.get('offset'), 0, 0, Number.MAX_SAFE_INTEGER),
|
|
1402
1749
|
});
|
|
@@ -1421,7 +1768,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1421
1768
|
} },
|
|
1422
1769
|
// ---------------------------------------------------------- 目录选择(0029)
|
|
1423
1770
|
/**
|
|
1424
|
-
*
|
|
1771
|
+
* 根路径的**只读**视图:新布局、家当解析结果、宿主提供的是哪种选择交互。
|
|
1425
1772
|
*
|
|
1426
1773
|
* 设置面板要显示「你这个根路径下会长出哪几段」,而那是服务端算出来的
|
|
1427
1774
|
* (`layout.ts` 是唯一真源),所以不能让前端自己拼字符串。
|
|
@@ -1429,22 +1776,13 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1429
1776
|
{ kind: 'exact', path: '/knowledge/layout', handler: async (req, res) => {
|
|
1430
1777
|
const dirs = agentLayout(config.agentRoot);
|
|
1431
1778
|
const resources = runtime.resourcePaths();
|
|
1432
|
-
// preset 的真源目录。它随插件分发,所以路径由插件自己算 —— 客户端不该拼这个
|
|
1433
|
-
// 字符串,也不该猜包装在哪。`exists` 是给界面用的:路径不在时如实说「这个安装
|
|
1434
|
-
// 里没有 preset 真源」,而不是给一个点了弹空目录的按钮。
|
|
1435
|
-
const presetDir = presetSourceDir();
|
|
1436
1779
|
return send(res, 200, success({
|
|
1437
1780
|
...dirs,
|
|
1438
|
-
presetDir,
|
|
1439
|
-
presetDirExists: existsSync(presetDir),
|
|
1440
|
-
migration,
|
|
1441
|
-
resourceMigration,
|
|
1442
|
-
memoryMigration,
|
|
1443
1781
|
// 三个「家当」路径**解析后**的值。设置面板用它显示「自动」到底指向哪里 ——
|
|
1444
1782
|
// 配置里是空串(= 自动),但用户要看到的是真实路径。
|
|
1445
1783
|
resources,
|
|
1446
|
-
// 家当有没有真的落在 `resource/`
|
|
1447
|
-
//
|
|
1784
|
+
// 家当有没有真的落在 `resource/` 下。`resource` 是家当目录本身,
|
|
1785
|
+
// 用它做前缀判断:不在里面的可能是一个裸的 HuggingFace 仓库名。
|
|
1448
1786
|
resourcesOutsideResource: outsideResource(resources, dirs.resource),
|
|
1449
1787
|
// 设置分区没注册上时的原因。有它,「设置服务不可用」才不是一句空话。
|
|
1450
1788
|
...(settingsError ? { settingsError } : {}),
|
|
@@ -1526,10 +1864,8 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1526
1864
|
return send(res, 200, success({ cases: (await storeOf(resolved.runtime).snapshot()).cases }));
|
|
1527
1865
|
} },
|
|
1528
1866
|
/**
|
|
1529
|
-
*
|
|
1530
|
-
*
|
|
1531
|
-
* 0044:卡片不再只提议 —— 它直接入库并标成 `unreviewed`。响应给 `sourceId` 与
|
|
1532
|
-
* `entityId`,界面据此说清「记录留下了,卡片也建了,等人 review」。
|
|
1867
|
+
* 一次排查结束只落一条 SourceRaw。Case 视图从记录派生;Knowledge / Event 的抽取、
|
|
1868
|
+
* 归一和 WikiCard 物化由知识处理阶段分别执行。
|
|
1533
1869
|
*/
|
|
1534
1870
|
{ kind: 'exact', path: '/diagnosis/run', handler: async (req, res) => {
|
|
1535
1871
|
if (req.method !== 'POST')
|
|
@@ -1544,9 +1880,9 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1544
1880
|
}
|
|
1545
1881
|
const outcome = await storeOf(resolved.runtime).recordRun(record);
|
|
1546
1882
|
if (outcome.problems.length > 0 && outcome.sourceId === undefined) {
|
|
1547
|
-
return send(res, 200, failure({ sourceId: ''
|
|
1883
|
+
return send(res, 200, failure({ sourceId: '' }, 'RUN_WRITE_FAILED', outcome.problems.join(';'), true));
|
|
1548
1884
|
}
|
|
1549
|
-
return send(res, 200, success({ sourceId: outcome.sourceId ?? ''
|
|
1885
|
+
return send(res, 200, success({ sourceId: outcome.sourceId ?? '' }, outcome.problems));
|
|
1550
1886
|
} },
|
|
1551
1887
|
/**
|
|
1552
1888
|
* 入库之后的两个人工动作(0044):加备注、review 抬分。
|
|
@@ -1636,6 +1972,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1636
1972
|
* ```
|
|
1637
1973
|
* /knowledge/pages/trial?kb=<知识库 id> 抽取试跑
|
|
1638
1974
|
* /knowledge/pages/run?kb=<库 id>&run=<轨迹 id> 一次抽取的完整报告
|
|
1975
|
+
* /knowledge/pages/tasks?kb=<知识库 id> 抽取任务
|
|
1639
1976
|
* /knowledge/pages/help/compile 编译链路
|
|
1640
1977
|
* /knowledge/pages/help/intent 意图的逻辑
|
|
1641
1978
|
* /knowledge/pages/help/overview?node=... 五个 Tab 的方案说明
|
|
@@ -1664,11 +2001,13 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1664
2001
|
? '抽取试跑'
|
|
1665
2002
|
: rest[1] === 'run'
|
|
1666
2003
|
? '抽取轨迹'
|
|
1667
|
-
: rest[1] === '
|
|
1668
|
-
? '
|
|
1669
|
-
: rest[1] === 'help' && rest[2] === '
|
|
1670
|
-
? '
|
|
1671
|
-
: rest[1] === 'help' && rest[2] === '
|
|
2004
|
+
: rest[1] === 'tasks'
|
|
2005
|
+
? '抽取任务'
|
|
2006
|
+
: rest[1] === 'help' && rest[2] === 'intent'
|
|
2007
|
+
? '意图的逻辑'
|
|
2008
|
+
: rest[1] === 'help' && rest[2] === 'compile'
|
|
2009
|
+
? '编译链路'
|
|
2010
|
+
: rest[1] === 'help' && rest[2] === 'overview' ? 'Agent 方案说明' : '整页';
|
|
1672
2011
|
res.writeHead(200, { 'content-type': 'text/html; charset=utf-8', 'cache-control': 'no-cache' });
|
|
1673
2012
|
res.end(standalonePageHtml(title));
|
|
1674
2013
|
return;
|
|
@@ -1700,9 +2039,37 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1700
2039
|
if (rest.length === 2) {
|
|
1701
2040
|
if (method === 'PATCH') {
|
|
1702
2041
|
const body = await readBody(req);
|
|
2042
|
+
if (body.compileModel && typeof body.compileModel === 'object') {
|
|
2043
|
+
const selection = body.compileModel;
|
|
2044
|
+
const catalog = await compileModelCatalog(baseId);
|
|
2045
|
+
const provider = selection.mode === 'fixed' ? selection.provider : catalog.default.provider;
|
|
2046
|
+
const model = selection.mode === 'fixed' ? selection.model : catalog.default.model;
|
|
2047
|
+
if (selection.mode !== 'fixed' && selection.mode !== 'default')
|
|
2048
|
+
return send(res, 400, envelopeError('MODEL_INVALID', '请选择跟随默认或固定模型'));
|
|
2049
|
+
if (selection.mode === 'fixed' && (!provider || !model))
|
|
2050
|
+
return send(res, 400, envelopeError('MODEL_INVALID', '固定模型需要 provider 和 model'));
|
|
2051
|
+
const llm = ctx.get('llm');
|
|
2052
|
+
if (provider && model && llm?.resolveCallConfig) {
|
|
2053
|
+
try {
|
|
2054
|
+
const info = selection.reasoningEffort ? await llm.resolveModelInfo(provider, model) : undefined;
|
|
2055
|
+
const effort = info?.reasoning?.efforts.find(item => item.id === selection.reasoningEffort);
|
|
2056
|
+
if (selection.reasoningEffort && !effort)
|
|
2057
|
+
throw new Error('所选模型不支持这个思考强度,请重新选择');
|
|
2058
|
+
await llm.resolveCallConfig({ provider, model, ...(effort ? { reasoningEffort: effort.id } : {}) });
|
|
2059
|
+
}
|
|
2060
|
+
catch (cause) {
|
|
2061
|
+
return send(res, 400, envelopeError('MODEL_INVALID', cause instanceof Error ? cause.message : String(cause)));
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
else if (selection.reasoningEffort)
|
|
2065
|
+
return send(res, 400, envelopeError('MODEL_INVALID', '模型不可用,无法校验思考强度'));
|
|
2066
|
+
}
|
|
1703
2067
|
const updated = await bases.update(baseId, {
|
|
1704
2068
|
name: body.name, description: body.description,
|
|
1705
2069
|
extractionNote: typeof body.extractionNote === 'string' ? body.extractionNote : undefined,
|
|
2070
|
+
compileModel: body.compileModel && typeof body.compileModel === 'object'
|
|
2071
|
+
? body.compileModel
|
|
2072
|
+
: undefined,
|
|
1706
2073
|
});
|
|
1707
2074
|
if (!updated.ok)
|
|
1708
2075
|
return sendEnvelope(res, updated);
|
|
@@ -1849,8 +2216,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1849
2216
|
}
|
|
1850
2217
|
// ----------------------------------------------- /knowledge/cards
|
|
1851
2218
|
// wiki 卡片详情与版本(0025)。列表走上面的 exact 路由;这里只处理
|
|
1852
|
-
// `/cards/:entityId`
|
|
1853
|
-
// SourceRaw 一样有历史,用户点开一张卡片要能看到它被改过几次。
|
|
2219
|
+
// `/cards/:entityId` 与 `/versions` 子路径 —— 卡片保留只读历史,供查看和比较。
|
|
1854
2220
|
if (rest[0] === 'cards' && rest.length >= 2) {
|
|
1855
2221
|
const kbParam = url.searchParams.get('kb');
|
|
1856
2222
|
const resolved = resolveBase(kbParam);
|
|
@@ -1914,7 +2280,8 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1914
2280
|
if (rest.length === 4 && rest[2] === 'versions') {
|
|
1915
2281
|
if (method !== 'GET')
|
|
1916
2282
|
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 GET'));
|
|
1917
|
-
const
|
|
2283
|
+
const previewChars = clampInt(url.searchParams.get('preview_chars'), VERSION_DIFF_CHARS, 0, VERSION_DIFF_CHARS);
|
|
2284
|
+
const version = await resolved.runtime.readCardVersion(entityId, rest[3], entityType, previewChars);
|
|
1918
2285
|
if (version === undefined)
|
|
1919
2286
|
return sendEnvelope(res, failure({}, 'VERSION_NOT_FOUND', `版本不存在: ${rest[3]}`, true));
|
|
1920
2287
|
return send(res, 200, success({ version }));
|
|
@@ -1922,6 +2289,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1922
2289
|
if (rest.length === 3 && rest[2] === 'revert') {
|
|
1923
2290
|
if (method !== 'POST')
|
|
1924
2291
|
return send(res, 405, envelopeError('METHOD_NOT_ALLOWED', '该 knowledge endpoint 只接受 POST'));
|
|
2292
|
+
// 兼容旧客户端的路径;运行时会明确拒绝恢复 WikiCard。
|
|
1925
2293
|
const body = await readBody(req);
|
|
1926
2294
|
const reverted = await resolved.runtime.revertCard(entityId, String(body.versionId ?? ''), entityType);
|
|
1927
2295
|
if (!reverted.ok)
|
|
@@ -1943,6 +2311,27 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1943
2311
|
const search = (url.searchParams.get('query') ?? '').trim().toLocaleLowerCase();
|
|
1944
2312
|
if (search)
|
|
1945
2313
|
documents = documents.filter(item => `${item.docId}\n${item.title}\n${item.externalId}`.toLocaleLowerCase().includes(search));
|
|
2314
|
+
/**
|
|
2315
|
+
* 文件类型的计数对着**搜索之后、类型筛选之前**的集合算。
|
|
2316
|
+
*
|
|
2317
|
+
* 下拉要能切换类型,所以每个类型的数量必须是「当前搜索下有多少份」,
|
|
2318
|
+
* 而不是「当前这一页里有多少份」—— 后者会让选项随翻页变来变去。
|
|
2319
|
+
*/
|
|
2320
|
+
const extCounts = {};
|
|
2321
|
+
for (const item of documents) {
|
|
2322
|
+
const key = item.ext || '—';
|
|
2323
|
+
extCounts[key] = (extCounts[key] ?? 0) + 1;
|
|
2324
|
+
}
|
|
2325
|
+
/**
|
|
2326
|
+
* 文件类型筛选**在服务端**做(0070)。
|
|
2327
|
+
*
|
|
2328
|
+
* 原来是「客户端取前 500 份、再在这一页里筛」:超过 500 份的库,第 501 份起
|
|
2329
|
+
* 在面板上根本不可达,而表头把这种截断显示成「筛出 500 / 600 个」——
|
|
2330
|
+
* 读起来像用户自己开了筛选。既然这里已经支持 `offset`,筛选就该和分页同层。
|
|
2331
|
+
*/
|
|
2332
|
+
const ext = (url.searchParams.get('ext') ?? '').trim();
|
|
2333
|
+
if (ext)
|
|
2334
|
+
documents = documents.filter(item => item.ext === ext);
|
|
1946
2335
|
const total = documents.length;
|
|
1947
2336
|
const offset = clampInt(url.searchParams.get('offset'), 0, 0, Number.MAX_SAFE_INTEGER);
|
|
1948
2337
|
const limit = clampInt(url.searchParams.get('limit'), 50, 1, 500);
|
|
@@ -1953,7 +2342,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
1953
2342
|
* 两件事。放成并列的一张表,前端按 `docId` 查,文档类型不用为审计字段让步。
|
|
1954
2343
|
*/
|
|
1955
2344
|
const extractionRuns = await resolved.runtime.extractionRunCounts();
|
|
1956
|
-
return send(res, 200, success({ documents: documents.slice(offset, offset + limit), total, extractionRuns }));
|
|
2345
|
+
return send(res, 200, success({ documents: documents.slice(offset, offset + limit), total, extCounts, extractionRuns }));
|
|
1957
2346
|
}
|
|
1958
2347
|
const docId = rest[1];
|
|
1959
2348
|
const resolved = resolveBase(kbParam);
|
|
@@ -2091,7 +2480,7 @@ export function apply(ctx, pluginConfig = {}) {
|
|
|
2091
2480
|
} },
|
|
2092
2481
|
];
|
|
2093
2482
|
const disposers = routes.map(register);
|
|
2094
|
-
return () => { runtime.close(); for (const dispose of disposers)
|
|
2483
|
+
return () => { environment.close(); runtime.close(); bases.closeAll(); for (const dispose of disposers)
|
|
2095
2484
|
void dispose(); };
|
|
2096
2485
|
});
|
|
2097
2486
|
}
|