dsh-plugin-dev-kb 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +186 -178
- package/README.en.md +172 -172
- package/README.md +183 -183
- package/kb/INDEX.md +236 -234
- package/kb/README.md +88 -88
- package/kb/extra/AGENTS.md +77 -77
- package/kb/extra/deepseek-llm-api-wire-extensions.md +162 -162
- package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +162 -162
- package/kb/extra/defensive-patterns/package-surface.md +233 -232
- package/kb/extra/defensive-patterns/package-surface.zh.md +233 -232
- package/kb/extra/defensive-patterns/remote-and-api.md +117 -117
- package/kb/extra/defensive-patterns/remote-and-api.zh.md +117 -117
- package/kb/extra/development.md +221 -221
- package/kb/extra/development.zh.md +228 -228
- package/kb/extra/event-producer-consumer.md +133 -123
- package/kb/extra/event-producer-consumer.zh.md +135 -125
- package/kb/extra/module-graph.md +1961 -1888
- package/kb/extra/module-graph.zh.md +1963 -1890
- package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.md +60 -0
- package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.zh.md +60 -0
- package/kb/extra/persistence-changes/README.md +92 -92
- package/kb/extra/persistence-changes/README.zh.md +92 -92
- package/kb/extra/persistence-changes/historical-formats/README.md +75 -75
- package/kb/extra/persistence-changes/historical-formats/README.zh.md +75 -75
- package/kb/extra/persistence-changes/releases/README.md +103 -103
- package/kb/extra/persistence-changes/releases/README.zh.md +103 -103
- package/kb/extra/rescope.md +55 -55
- package/kb/extra/rescope.zh.md +55 -55
- package/kb/extra/subsystems/boot.md +631 -621
- package/kb/extra/subsystems/boot.zh.md +631 -621
- package/kb/extra/subsystems/computer-use.md +179 -137
- package/kb/extra/subsystems/computer-use.zh.md +179 -137
- package/kb/extra/subsystems/extensions.md +387 -384
- package/kb/extra/subsystems/extensions.zh.md +387 -384
- package/kb/extra/subsystems/feedback.md +418 -400
- package/kb/extra/subsystems/feedback.zh.md +418 -400
- package/kb/extra/subsystems/forge.md +2169 -1226
- package/kb/extra/subsystems/forge.zh.md +2171 -1226
- package/kb/extra/subsystems/liveshare.md +167 -158
- package/kb/extra/subsystems/liveshare.zh.md +167 -158
- package/kb/extra/subsystems/mcp.md +237 -234
- package/kb/extra/subsystems/mcp.zh.md +237 -234
- package/kb/extra/subsystems/office-kit.md +122 -0
- package/kb/extra/subsystems/office-kit.zh.md +122 -0
- package/kb/extra/subsystems/working-directory.md +95 -64
- package/kb/extra/subsystems/working-directory.zh.md +95 -64
- package/kb/extra/subsystems/worktrees.md +187 -127
- package/kb/extra/subsystems/worktrees.zh.md +187 -127
- package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md +21 -21
- package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md +21 -21
- package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.zh.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.zh.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.md +34 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.zh.md +34 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.md +49 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.zh.md +49 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.md +36 -0
- package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.zh.md +36 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.zh.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.md +22 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.zh.md +22 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.md +21 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.zh.md +21 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.md +23 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.zh.md +23 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.md +20 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.zh.md +20 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.zh.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.md +23 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.zh.md +23 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.md +35 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.zh.md +35 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.zh.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.zh.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.zh.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.zh.md +26 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.md +22 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.zh.md +22 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.md +25 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.zh.md +25 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.md +48 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.zh.md +48 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.md +45 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.zh.md +45 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.zh.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.md +17 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.zh.md +17 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.md +27 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.zh.md +27 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.md +20 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.zh.md +20 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.zh.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.zh.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.md +41 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.zh.md +41 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.md +21 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.zh.md +21 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.md +29 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.zh.md +29 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.md +25 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.zh.md +25 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.md +31 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.zh.md +31 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.md +24 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.zh.md +24 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.md +24 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.zh.md +24 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.zh.md +19 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.md +15 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.zh.md +15 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.md +28 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.zh.md +28 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.md +36 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.zh.md +36 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.md +22 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.zh.md +22 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.md +31 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.zh.md +31 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.zh.md +18 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.md +30 -0
- package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.zh.md +30 -0
- package/kb/extra/web-styling.md +57 -57
- package/kb/extra/web-styling.zh.md +57 -57
- package/kb/meta/search-index.json +4508 -3980
- package/kb/meta/source.json +18 -18
- package/kb/site/develop/practice/llm-adapter.md +192 -192
- package/kb/site/en/develop/practice/llm-adapter.md +192 -192
- package/kb/site/en/guide/desktop-uninstall-cleanup.md +63 -0
- package/kb/site/en/guide/plugin-market.md +153 -151
- package/kb/site/en/guide/providers.md +234 -234
- package/kb/site/en/guide/quickstart.md +35 -34
- package/kb/site/en/reference/capability-seams.md +789 -767
- package/kb/site/en/reference/config-catalog.md +5432 -5128
- package/kb/site/en/reference/cookbook/adding-a-package.md +175 -175
- package/kb/site/en/reference/cookbook/adding-a-tool.md +105 -104
- package/kb/site/en/reference/cookbook/extension-cookbook.md +178 -135
- package/kb/site/en/reference/cordis-api/context.md +373 -369
- package/kb/site/en/reference/cordis-api/fiber.md +457 -433
- package/kb/site/en/reference/cordis-api/inherited.md +44 -44
- package/kb/site/en/reference/cordis-api/service.md +118 -107
- package/kb/site/en/reference/experimental-persistence-catalog.md +36 -23
- package/kb/site/en/reference/index.md +174 -174
- package/kb/site/en/reference/persistence-catalog.md +9453 -9432
- package/kb/site/en/reference/subsystems/compaction.md +288 -288
- package/kb/site/en/reference/subsystems/core.md +1172 -1171
- package/kb/site/en/reference/subsystems/credentials.md +526 -497
- package/kb/site/en/reference/subsystems/filesystem.md +708 -708
- package/kb/site/en/reference/subsystems/index.md +82 -82
- package/kb/site/en/reference/subsystems/llm-streaming.md +1290 -1288
- package/kb/site/en/reference/subsystems/lsp.md +230 -206
- package/kb/site/en/reference/subsystems/persistence.md +451 -432
- package/kb/site/en/reference/subsystems/plan.md +91 -91
- package/kb/site/en/reference/subsystems/ptc-runtime.md +295 -261
- package/kb/site/en/reference/subsystems/sandbox.md +250 -232
- package/kb/site/en/reference/subsystems/session-query.md +525 -515
- package/kb/site/en/reference/subsystems/session-reference.md +231 -229
- package/kb/site/en/reference/subsystems/session.md +1478 -1431
- package/kb/site/en/reference/subsystems/sidebar-right.md +165 -165
- package/kb/site/en/reference/subsystems/skills.md +356 -356
- package/kb/site/en/reference/subsystems/slots.md +235 -231
- package/kb/site/en/reference/subsystems/terminal.md +188 -188
- package/kb/site/en/reference/subsystems/tools.md +902 -891
- package/kb/site/en/reference/subsystems/user-questions.md +330 -327
- package/kb/site/en/reference/subsystems/web-client.md +161 -104
- package/kb/site/en/reference/subsystems/web-server.md +434 -351
- package/kb/site/en/reference/subsystems/web.md +268 -268
- package/kb/site/en/reference/subsystems/workflow.md +287 -282
- package/kb/site/en/reference/subsystems/workspace.md +743 -651
- package/kb/site/en/reference/tool-catalog.md +2867 -2704
- package/kb/site/guide/desktop-uninstall-cleanup.md +63 -0
- package/kb/site/guide/plugin-market.md +153 -151
- package/kb/site/guide/providers.md +234 -234
- package/kb/site/guide/quickstart.md +35 -34
- package/kb/site/reference/capability-seams.md +789 -767
- package/kb/site/reference/config-catalog.md +5432 -5128
- package/kb/site/reference/cookbook/adding-a-package.md +175 -175
- package/kb/site/reference/cookbook/adding-a-tool.md +107 -106
- package/kb/site/reference/cookbook/extension-cookbook.md +182 -139
- package/kb/site/reference/cordis-api/context.md +373 -369
- package/kb/site/reference/cordis-api/fiber.md +459 -435
- package/kb/site/reference/cordis-api/inherited.md +44 -44
- package/kb/site/reference/cordis-api/service.md +118 -107
- package/kb/site/reference/experimental-persistence-catalog.md +36 -23
- package/kb/site/reference/index.md +178 -178
- package/kb/site/reference/persistence-catalog.md +9453 -9432
- package/kb/site/reference/subsystems/compaction.md +288 -288
- package/kb/site/reference/subsystems/core.md +1184 -1183
- package/kb/site/reference/subsystems/credentials.md +526 -497
- package/kb/site/reference/subsystems/filesystem.md +708 -708
- package/kb/site/reference/subsystems/index.md +82 -82
- package/kb/site/reference/subsystems/llm-streaming.md +1296 -1294
- package/kb/site/reference/subsystems/lsp.md +230 -206
- package/kb/site/reference/subsystems/persistence.md +451 -432
- package/kb/site/reference/subsystems/plan.md +91 -91
- package/kb/site/reference/subsystems/ptc-runtime.md +295 -261
- package/kb/site/reference/subsystems/sandbox.md +250 -232
- package/kb/site/reference/subsystems/session-query.md +525 -515
- package/kb/site/reference/subsystems/session-reference.md +231 -229
- package/kb/site/reference/subsystems/session.md +1482 -1435
- package/kb/site/reference/subsystems/sidebar-right.md +165 -165
- package/kb/site/reference/subsystems/skills.md +356 -356
- package/kb/site/reference/subsystems/slots.md +239 -235
- package/kb/site/reference/subsystems/terminal.md +188 -188
- package/kb/site/reference/subsystems/tools.md +902 -891
- package/kb/site/reference/subsystems/user-questions.md +330 -327
- package/kb/site/reference/subsystems/web-client.md +161 -104
- package/kb/site/reference/subsystems/web-server.md +434 -351
- package/kb/site/reference/subsystems/web.md +268 -268
- package/kb/site/reference/subsystems/workflow.md +287 -282
- package/kb/site/reference/subsystems/workspace.md +743 -651
- package/kb/site/reference/tool-catalog.md +2872 -2709
- package/lib/index.js +359 -359
- package/package.json +68 -68
- package/skills/dsh-plugin-dev-kb.md +71 -70
- package/kb/extra/subsystems/office-to-pdf.md +0 -87
- package/kb/extra/subsystems/office-to-pdf.zh.md +0 -87
package/lib/index.js
CHANGED
|
@@ -1,359 +1,359 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-plugin-dev-kb — 主机插件(技能载体 + 知识库检索工具)。
|
|
3
|
-
*
|
|
4
|
-
* 挂载于 profile 根层(全局层),两部分职责:
|
|
5
|
-
*
|
|
6
|
-
* A. 技能层:把 skills/ 目录下的 dsh-plugin-dev-kb / dsh-dev-sandbox 技能
|
|
7
|
-
* 注册为 runtime skill,让每个会话的 agent 在插件开发 / 查询 dsh 能力 /
|
|
8
|
-
* 写 Tool / 配置 cordis.yml 类任务时按技能 description 自动加载。
|
|
9
|
-
*
|
|
10
|
-
* B. 工具层(1.6.0 起):注册 dsh_kb_search 工具——知识库本地打分检索,
|
|
11
|
-
* 一次调用返回命中文件 + 行号 + 上下文片段,替代 agent 多轮 grep/read。
|
|
12
|
-
* 索引在内存(宿主启动时构建中文索引,英文索引按需惰性构建),只读不写。
|
|
13
|
-
*
|
|
14
|
-
* 知识库数据(kb/ 目录)是纯文档,模型仍可通过文件工具按技能指示直接读取。
|
|
15
|
-
*
|
|
16
|
-
* 依赖纪律:本模块不 import 任何 @deepseek-ai/* 运行时包(插件以
|
|
17
|
-
* link: 方式装入 profile,Node ESM 按 realpath 解析链接包,外部依赖
|
|
18
|
-
* 从插件目录解析不到);工具走 ctx.tools.register 的原始 definition
|
|
19
|
-
* 形状(标准 JSON Schema,参照 dsh-plugin-windows-guard 的先例)。
|
|
20
|
-
*/
|
|
21
|
-
|
|
22
|
-
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
23
|
-
import { dirname, join, resolve, sep } from 'node:path'
|
|
24
|
-
import { fileURLToPath } from 'node:url'
|
|
25
|
-
import { buildIndex, search as kbSearch } from './kb-search.js'
|
|
26
|
-
import { detectHostVersion, versionBanner } from './kb-version.js'
|
|
27
|
-
|
|
28
|
-
export const name = 'dsh-plugin-dev-kb'
|
|
29
|
-
export const inject = ['skills', 'tools']
|
|
30
|
-
|
|
31
|
-
const PKG_DIR = dirname(fileURLToPath(import.meta.url))
|
|
32
|
-
const SKILLS_DIR = join(PKG_DIR, '..', 'skills')
|
|
33
|
-
const KB_DIR = join(PKG_DIR, '..', 'kb')
|
|
34
|
-
|
|
35
|
-
const DEFAULT_CONFIG = {
|
|
36
|
-
/** 注册 dsh_kb_search 知识库检索工具(false = 只保留技能,不注册工具)。 */
|
|
37
|
-
searchTool: true,
|
|
38
|
-
/** 注册 dsh_kb_read 知识库局部阅读工具(1.8.0 起,false = 只用 search)。 */
|
|
39
|
-
readTool: true,
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// ---------------------------------------------------------------------------
|
|
43
|
-
// dsh_kb_search 工具(原始 definition 形状,零依赖)
|
|
44
|
-
// ---------------------------------------------------------------------------
|
|
45
|
-
|
|
46
|
-
/** 把 search 结果渲染为模型可读 Markdown。 */
|
|
47
|
-
function renderResult(value) {
|
|
48
|
-
if (value.kind === 'topics') {
|
|
49
|
-
return [
|
|
50
|
-
`【知识库主题导航】${value.topicsPath ?? ''}`,
|
|
51
|
-
value.text ?? '',
|
|
52
|
-
value.hint ?? '',
|
|
53
|
-
].filter(Boolean).join('\n')
|
|
54
|
-
}
|
|
55
|
-
if (value.kind === 'empty') {
|
|
56
|
-
return `【无命中】query: ${value.query}\n${value.hint}`
|
|
57
|
-
}
|
|
58
|
-
const lines = [`【知识库检索】query: ${value.query}(scope=${value.scope},命中 ${value.files.length} 个文件${value.truncatedFiles ? `,另有 ${value.truncatedFiles} 个未展示` : ''})`]
|
|
59
|
-
for (const f of value.files) {
|
|
60
|
-
lines.push('')
|
|
61
|
-
lines.push(`### ${f.path}(score ${f.score}${f.title ? `,标题「${f.title}」` : ''})`)
|
|
62
|
-
lines.push(`绝对路径:${f.absPath}|命中行:${f.hitLines.join(', ')}`)
|
|
63
|
-
for (const s of f.snippets) {
|
|
64
|
-
lines.push('')
|
|
65
|
-
lines.push(`--- L${s.from} 起(命中行 L${s.line})---`)
|
|
66
|
-
lines.push(s.text)
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
lines.push('')
|
|
70
|
-
lines.push(value.hint ?? '')
|
|
71
|
-
return lines.join('\n')
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/** dsh_kb_search 工具定义。 */
|
|
75
|
-
function kbSearchTool(getIndex, hostVersion) {
|
|
76
|
-
return {
|
|
77
|
-
name: 'dsh_kb_search',
|
|
78
|
-
description:
|
|
79
|
-
'dsh 插件开发知识库本地打分检索(零 token、毫秒级):在官方文档完整镜像(站点
|
|
80
|
-
'按关键词检索,返回命中文件路径、行号与上下文片段,用 read 工具按行号局部读原文。' +
|
|
81
|
-
'query 为空或 "topics" 返回主题导航(任务场景 → 文档清单)。写 dsh 插件 / Tool / 配置 / 服务 / 事件 /' +
|
|
82
|
-
'查 ctx.* API 签名 / 打包发布 / LLM 适配器时先用本工具定位文档,找不到再用文件工具 grep。' +
|
|
83
|
-
'仅限 dsh(DeepSeek Harness)插件开发知识库。',
|
|
84
|
-
parameters: {
|
|
85
|
-
type: 'object',
|
|
86
|
-
additionalProperties: false,
|
|
87
|
-
required: ['query'],
|
|
88
|
-
properties: {
|
|
89
|
-
query: {
|
|
90
|
-
type: 'string',
|
|
91
|
-
description:
|
|
92
|
-
'检索关键词,空白分隔多词(如 "ctx.tools.register"、"插件配置 发布");空串或 "topics" 返回主题导航。'
|
|
93
|
-
},
|
|
94
|
-
scope: {
|
|
95
|
-
type: 'string',
|
|
96
|
-
enum: ['all', 'api'],
|
|
97
|
-
description:
|
|
98
|
-
'all = 全部文档(默认);api = 只搜 API 签名/目录类文档(tool-catalog、config-catalog、capability-seams、cordis-api、subsystems 等),适合查 ctx.* API。'
|
|
99
|
-
},
|
|
100
|
-
lang: {
|
|
101
|
-
type: 'string',
|
|
102
|
-
enum: ['zh', 'en'],
|
|
103
|
-
description: '文档语言(默认 zh;en 首次调用会现场建英文索引,稍慢)'
|
|
104
|
-
},
|
|
105
|
-
maxResults: {
|
|
106
|
-
type: 'integer',
|
|
107
|
-
description: '返回文件数上限(默认 8,最大 20)'
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
},
|
|
111
|
-
output: {
|
|
112
|
-
schema: {
|
|
113
|
-
type: 'object',
|
|
114
|
-
additionalProperties: false,
|
|
115
|
-
required: ['kind', 'text'],
|
|
116
|
-
properties: {
|
|
117
|
-
kind: { type: 'string', enum: ['hits', 'empty', 'topics'] },
|
|
118
|
-
text: { type: 'string' }
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
render: (_args, value) => [{ type: 'text', text: value.text }]
|
|
122
|
-
},
|
|
123
|
-
async execute(args) {
|
|
124
|
-
const { index, error } = getIndex(args.lang === 'en' ? 'en' : 'zh')
|
|
125
|
-
if (error) throw new Error(error)
|
|
126
|
-
const result = kbSearch(index, args.query, {
|
|
127
|
-
scope: args.scope === 'api' ? 'api' : 'all',
|
|
128
|
-
maxResults: args.maxResults,
|
|
129
|
-
})
|
|
130
|
-
const banner = versionBanner(index.kbDir, hostVersion)
|
|
131
|
-
return { kind: result.kind, text: banner ? `${banner}\n\n${renderResult(result)}` : renderResult(result) }
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
/** dsh_kb_read 工具定义(1.8.0):知识库局部阅读,带行号窗口 + 中英对照提示。 */
|
|
137
|
-
function kbReadTool(getIndex, hostVersion) {
|
|
138
|
-
return {
|
|
139
|
-
name: 'dsh_kb_read',
|
|
140
|
-
description:
|
|
141
|
-
'dsh 插件开发知识库局部阅读:按知识库相对路径 + 行号窗口读原文,输出带行号,' +
|
|
142
|
-
'自动防大文件整读(单次窗口有上限)、自动处理编码,并提示中英对照版本的对应路径。' +
|
|
143
|
-
'通常与 dsh_kb_search 配套:先 search 拿到 path + 命中行号,再用本工具按行号窗口读上下文。' +
|
|
144
|
-
'仅限知识库目录内的 .md 文件(路径越界会被拒绝)。' +
|
|
145
|
-
'仅限 dsh(DeepSeek Harness)插件开发知识库。',
|
|
146
|
-
parameters: {
|
|
147
|
-
type: 'object',
|
|
148
|
-
additionalProperties: false,
|
|
149
|
-
required: ['path'],
|
|
150
|
-
properties: {
|
|
151
|
-
path: {
|
|
152
|
-
type: 'string',
|
|
153
|
-
description: '知识库相对路径(正斜杠,如 "site/develop/basic/tool.md"、"extra/upgrade-guide/v0.2.0-rc.2/guide.zh.md")'
|
|
154
|
-
},
|
|
155
|
-
offset: {
|
|
156
|
-
type: 'integer',
|
|
157
|
-
description: '起始行(1 基,默认 1)'
|
|
158
|
-
},
|
|
159
|
-
limit: {
|
|
160
|
-
type: 'integer',
|
|
161
|
-
description: '读取行数(默认 120,最大 400)'
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
},
|
|
165
|
-
output: {
|
|
166
|
-
schema: {
|
|
167
|
-
type: 'object',
|
|
168
|
-
additionalProperties: false,
|
|
169
|
-
required: ['kind', 'text'],
|
|
170
|
-
properties: {
|
|
171
|
-
kind: { type: 'string', enum: ['text', 'error'] },
|
|
172
|
-
text: { type: 'string' }
|
|
173
|
-
}
|
|
174
|
-
},
|
|
175
|
-
render: (_args, value) => [{ type: 'text', text: value.text }]
|
|
176
|
-
},
|
|
177
|
-
async execute(args) {
|
|
178
|
-
const { index, error } = getIndex('zh')
|
|
179
|
-
if (error) throw new Error(error)
|
|
180
|
-
const rel = String(args.path ?? '').replaceAll('\\', '/').replace(/^\/+/, '')
|
|
181
|
-
if (!rel || !rel.endsWith('.md')) {
|
|
182
|
-
return { kind: 'error', text: `【拒绝】path 必须是知识库内的 .md 相对路径,收到:${rel || '(空)'}` }
|
|
183
|
-
}
|
|
184
|
-
const abs = resolve(index.kbDir, ...rel.split('/'))
|
|
185
|
-
const kbRootResolved = resolve(index.kbDir)
|
|
186
|
-
if (abs !== kbRootResolved && !abs.startsWith(kbRootResolved + sep)) {
|
|
187
|
-
return { kind: 'error', text: `【拒绝】路径越界:${rel}(只允许知识库目录内的文件)` }
|
|
188
|
-
}
|
|
189
|
-
let lines
|
|
190
|
-
try {
|
|
191
|
-
const st = statSync(abs)
|
|
192
|
-
if (st.size > 8_000_000) {
|
|
193
|
-
return { kind: 'error', text: `【拒绝】文件过大(${st.size} 字节):${rel}` }
|
|
194
|
-
}
|
|
195
|
-
lines = readFileSync(abs, 'utf8').split(/\r?\n/)
|
|
196
|
-
} catch (err) {
|
|
197
|
-
return { kind: 'error', text: `【读取失败】${rel}(${err?.message ?? err})。提示:先用 dsh_kb_search 确认路径是否在索引内。` }
|
|
198
|
-
}
|
|
199
|
-
const total = lines.length
|
|
200
|
-
const offset = Math.min(Math.max(Number(args.offset) > 0 ? Number(args.offset) : 1, 1), Math.max(total, 1))
|
|
201
|
-
const limit = Math.min(Math.max(Number(args.limit) > 0 ? Number(args.limit) : 120, 1), 400)
|
|
202
|
-
const end = Math.min(offset - 1 + limit, total)
|
|
203
|
-
const width = String(end).length
|
|
204
|
-
const body = lines.slice(offset - 1, end)
|
|
205
|
-
.map((l, i) => `${String(offset + i).padStart(width)}| ${l}`)
|
|
206
|
-
.join('\n')
|
|
207
|
-
const footer = []
|
|
208
|
-
if (end < total) footer.push(`(未完:共 ${total} 行,已读 L${offset}-${end};续读传 offset=${end + 1})`)
|
|
209
|
-
const alt = altLangPath(rel)
|
|
210
|
-
if (alt) footer.push(`中英对照:${alt.exists ? alt.rel : alt.rel + '(未收录)'}`)
|
|
211
|
-
const banner = versionBanner(index.kbDir, hostVersion)
|
|
212
|
-
const text = [
|
|
213
|
-
banner ?? '',
|
|
214
|
-
`【知识库阅读】${rel}(共 ${total} 行,窗口 L${offset}-${end})`,
|
|
215
|
-
body,
|
|
216
|
-
...footer,
|
|
217
|
-
].filter(Boolean).join('\n')
|
|
218
|
-
return { kind: 'text', text }
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
/** 中英对照路径推导:site/ ↔ site/en/,extra/*.zh.md ↔ extra/*.md。存在性现场检查。 */
|
|
224
|
-
function altLangPath(rel) {
|
|
225
|
-
let counter
|
|
226
|
-
if (rel.startsWith('site/en/')) counter = rel.replace('site/en/', 'site/')
|
|
227
|
-
else if (rel.startsWith('site/')) counter = rel.replace('site/', 'site/en/')
|
|
228
|
-
else if (rel.startsWith('extra/') && rel.endsWith('.zh.md')) counter = rel.slice(0, -'.zh.md'.length) + '.md'
|
|
229
|
-
else if (rel.startsWith('extra/') && rel.endsWith('.md') && !rel.endsWith('.zh.md')) counter = rel.slice(0, -'.md'.length) + '.zh.md'
|
|
230
|
-
else return null
|
|
231
|
-
const abs = join(KB_DIR, ...counter.split('/'))
|
|
232
|
-
return { rel: counter, exists: existsSync(abs) }
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
/** logger 拿不到时降级 console。 */
|
|
236
|
-
function makeLogger(ctx, tag) {
|
|
237
|
-
return {
|
|
238
|
-
info: (...args) => (ctx.logger?.info ? ctx.logger.info(...args) : console.log(tag, ...args)),
|
|
239
|
-
warn: (...args) => (ctx.logger?.warn ? ctx.logger.warn(...args) : console.warn(tag, ...args))
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
/** 极简 frontmatter 解析(name/description/whenToUse)。 */
|
|
244
|
-
function parseFrontmatter(md) {
|
|
245
|
-
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(md)
|
|
246
|
-
if (!match) return { name: undefined, description: undefined, whenToUse: undefined, body: md }
|
|
247
|
-
const meta = {}
|
|
248
|
-
for (const line of match[1].split(/\r?\n/)) {
|
|
249
|
-
const i = line.indexOf(':')
|
|
250
|
-
if (i <= 0) continue
|
|
251
|
-
const key = line.slice(0, i).trim()
|
|
252
|
-
if (key) meta[key] = line.slice(i + 1).trim().replace(/^["']|["']$/g, '')
|
|
253
|
-
}
|
|
254
|
-
return { ...meta, body: md.slice(match[0].length) }
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
/** 从 skills/ 目录加载技能定义(文件缺失/无 frontmatter 时跳过并警告,绝不让插件挂载失败)。 */
|
|
258
|
-
function loadSkills(log) {
|
|
259
|
-
const out = []
|
|
260
|
-
if (!existsSync(SKILLS_DIR)) {
|
|
261
|
-
log.warn('skills 目录缺失,技能未加载:' + SKILLS_DIR)
|
|
262
|
-
return out
|
|
263
|
-
}
|
|
264
|
-
for (const file of readdirSync(SKILLS_DIR)) {
|
|
265
|
-
if (!file.endsWith('.md')) continue
|
|
266
|
-
try {
|
|
267
|
-
const fm = parseFrontmatter(readFileSync(join(SKILLS_DIR, file), 'utf8'))
|
|
268
|
-
if (!fm.name || !fm.description) {
|
|
269
|
-
log.warn(`技能文件缺少 name/description frontmatter,已跳过:${file}`)
|
|
270
|
-
continue
|
|
271
|
-
}
|
|
272
|
-
out.push({
|
|
273
|
-
name: fm.name,
|
|
274
|
-
description: fm.description,
|
|
275
|
-
whenToUse: fm.whenToUse,
|
|
276
|
-
body: fm.body
|
|
277
|
-
})
|
|
278
|
-
} catch (err) {
|
|
279
|
-
log.warn(`技能文件读取失败,已跳过:${file}(${err?.message ?? err})`)
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
return out
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
export function apply(ctx, config) {
|
|
286
|
-
const log = makeLogger(ctx, '[dsh-plugin-dev-kb]')
|
|
287
|
-
const cfg = { ...DEFAULT_CONFIG, ...(config ?? {}) }
|
|
288
|
-
// 显式布尔归一(patch 配置可能是字符串)
|
|
289
|
-
for (const key of Object.keys(DEFAULT_CONFIG)) {
|
|
290
|
-
if (typeof cfg[key] === 'string') cfg[key] = cfg[key] === 'true'
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// 契约(dsh-skill validateDefinition):定义必须含 name/description/
|
|
294
|
-
// source/content 四个字符串;provider 缺省为 'runtime'。
|
|
295
|
-
//
|
|
296
|
-
// 路径注入:技能正文中的 {{KB_ROOT}} 占位符在此替换为插件实际的 kb/
|
|
297
|
-
// 绝对路径(正斜杠,Windows 反斜杠会破坏 Markdown 转义),并声明
|
|
298
|
-
// resourceBase 让 skill 工具渲染相对资源引导——知识库位置随插件安装
|
|
299
|
-
// 位置动态解析,不依赖任何写死的机器路径。
|
|
300
|
-
const kbRoot = KB_DIR.replaceAll('\\', '/')
|
|
301
|
-
for (const skill of loadSkills(log)) {
|
|
302
|
-
ctx.effect(() => ctx.skills.register({
|
|
303
|
-
name: skill.name,
|
|
304
|
-
description: skill.description,
|
|
305
|
-
...(skill.whenToUse ? { whenToUse: skill.whenToUse } : {}),
|
|
306
|
-
source: 'runtime',
|
|
307
|
-
content: skill.body.replaceAll('{{KB_ROOT}}', kbRoot),
|
|
308
|
-
resourceBase: { kind: 'directory', path: KB_DIR }
|
|
309
|
-
}), `dsh-plugin-dev-kb: register skill ${skill.name}`)
|
|
310
|
-
log.info(`技能已注册:${skill.name}`)
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
// B. 检索工具(1.6.0 起)+ 局部阅读工具(1.8.0 起):
|
|
314
|
-
// 索引惰性构建(首次调用时建,宿主启动零开销),zh/en 各缓存一份;
|
|
315
|
-
// 宿主版本探测一次性完成(拿不到 → 工具输出不带版本横幅);
|
|
316
|
-
// kb 目录缺失 → 工具执行时报错并提示退回文件工具检索。
|
|
317
|
-
const hostVersion = detectHostVersion(PKG_DIR)
|
|
318
|
-
if (hostVersion) {
|
|
319
|
-
log.info(`宿主 dsh 版本探测:${hostVersion.version}(${hostVersion.source})`)
|
|
320
|
-
} else {
|
|
321
|
-
log.warn('宿主 dsh 版本探测失败(工具输出将不带版本横幅)')
|
|
322
|
-
}
|
|
323
|
-
if (cfg.searchTool || cfg.readTool) {
|
|
324
|
-
if (typeof ctx.tools?.register === 'function') {
|
|
325
|
-
const indexCache = new Map()
|
|
326
|
-
const getIndex = (lang) => {
|
|
327
|
-
if (indexCache.has(lang)) return { index: indexCache.get(lang), error: undefined }
|
|
328
|
-
if (!existsSync(KB_DIR)) {
|
|
329
|
-
return {
|
|
330
|
-
index: undefined,
|
|
331
|
-
error: `知识库目录缺失:${KB_DIR.replaceAll('\\', '/')}(插件安装不完整?请改用文件工具 grep 检索,或重装插件)`
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
try {
|
|
335
|
-
const t0 = Date.now()
|
|
336
|
-
const index = buildIndex(KB_DIR, lang, log)
|
|
337
|
-
indexCache.set(lang, index)
|
|
338
|
-
log.info(`知识库${lang} 索引就绪:${index.files.length} 个文件(${Date.now() - t0}ms)`)
|
|
339
|
-
return { index, error: undefined }
|
|
340
|
-
} catch (err) {
|
|
341
|
-
return {
|
|
342
|
-
index: undefined,
|
|
343
|
-
error: `知识库索引构建失败:${err?.message ?? err}(请改用文件工具 grep 检索知识库目录)`
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
ctx.effect(() => ctx.tools.register(kbSearchTool(getIndex, hostVersion), 'dsh-plugin-dev-kb: register dsh_kb_search'), 'dsh-plugin-dev-kb: register dsh_kb_search')
|
|
348
|
-
log.info('工具已注册:dsh_kb_search')
|
|
349
|
-
if (cfg.readTool) {
|
|
350
|
-
ctx.effect(() => ctx.tools.register(kbReadTool(getIndex, hostVersion), 'dsh-plugin-dev-kb: register dsh_kb_read'), 'dsh-plugin-dev-kb: register dsh_kb_read')
|
|
351
|
-
log.info('工具已注册:dsh_kb_read')
|
|
352
|
-
}
|
|
353
|
-
} else {
|
|
354
|
-
log.warn('ctx.tools.register 不可用,dsh_kb_search 未注册(工具子系统的注入可能失败)')
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
log.info(`已挂载(知识库在 kb/ 目录)`)
|
|
359
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-plugin-dev-kb — 主机插件(技能载体 + 知识库检索工具)。
|
|
3
|
+
*
|
|
4
|
+
* 挂载于 profile 根层(全局层),两部分职责:
|
|
5
|
+
*
|
|
6
|
+
* A. 技能层:把 skills/ 目录下的 dsh-plugin-dev-kb / dsh-dev-sandbox 技能
|
|
7
|
+
* 注册为 runtime skill,让每个会话的 agent 在插件开发 / 查询 dsh 能力 /
|
|
8
|
+
* 写 Tool / 配置 cordis.yml 类任务时按技能 description 自动加载。
|
|
9
|
+
*
|
|
10
|
+
* B. 工具层(1.6.0 起):注册 dsh_kb_search 工具——知识库本地打分检索,
|
|
11
|
+
* 一次调用返回命中文件 + 行号 + 上下文片段,替代 agent 多轮 grep/read。
|
|
12
|
+
* 索引在内存(宿主启动时构建中文索引,英文索引按需惰性构建),只读不写。
|
|
13
|
+
*
|
|
14
|
+
* 知识库数据(kb/ 目录)是纯文档,模型仍可通过文件工具按技能指示直接读取。
|
|
15
|
+
*
|
|
16
|
+
* 依赖纪律:本模块不 import 任何 @deepseek-ai/* 运行时包(插件以
|
|
17
|
+
* link: 方式装入 profile,Node ESM 按 realpath 解析链接包,外部依赖
|
|
18
|
+
* 从插件目录解析不到);工具走 ctx.tools.register 的原始 definition
|
|
19
|
+
* 形状(标准 JSON Schema,参照 dsh-plugin-windows-guard 的先例)。
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs'
|
|
23
|
+
import { dirname, join, resolve, sep } from 'node:path'
|
|
24
|
+
import { fileURLToPath } from 'node:url'
|
|
25
|
+
import { buildIndex, search as kbSearch } from './kb-search.js'
|
|
26
|
+
import { detectHostVersion, versionBanner } from './kb-version.js'
|
|
27
|
+
|
|
28
|
+
export const name = 'dsh-plugin-dev-kb'
|
|
29
|
+
export const inject = ['skills', 'tools']
|
|
30
|
+
|
|
31
|
+
const PKG_DIR = dirname(fileURLToPath(import.meta.url))
|
|
32
|
+
const SKILLS_DIR = join(PKG_DIR, '..', 'skills')
|
|
33
|
+
const KB_DIR = join(PKG_DIR, '..', 'kb')
|
|
34
|
+
|
|
35
|
+
const DEFAULT_CONFIG = {
|
|
36
|
+
/** 注册 dsh_kb_search 知识库检索工具(false = 只保留技能,不注册工具)。 */
|
|
37
|
+
searchTool: true,
|
|
38
|
+
/** 注册 dsh_kb_read 知识库局部阅读工具(1.8.0 起,false = 只用 search)。 */
|
|
39
|
+
readTool: true,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
// dsh_kb_search 工具(原始 definition 形状,零依赖)
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
|
|
46
|
+
/** 把 search 结果渲染为模型可读 Markdown。 */
|
|
47
|
+
function renderResult(value) {
|
|
48
|
+
if (value.kind === 'topics') {
|
|
49
|
+
return [
|
|
50
|
+
`【知识库主题导航】${value.topicsPath ?? ''}`,
|
|
51
|
+
value.text ?? '',
|
|
52
|
+
value.hint ?? '',
|
|
53
|
+
].filter(Boolean).join('\n')
|
|
54
|
+
}
|
|
55
|
+
if (value.kind === 'empty') {
|
|
56
|
+
return `【无命中】query: ${value.query}\n${value.hint}`
|
|
57
|
+
}
|
|
58
|
+
const lines = [`【知识库检索】query: ${value.query}(scope=${value.scope},命中 ${value.files.length} 个文件${value.truncatedFiles ? `,另有 ${value.truncatedFiles} 个未展示` : ''})`]
|
|
59
|
+
for (const f of value.files) {
|
|
60
|
+
lines.push('')
|
|
61
|
+
lines.push(`### ${f.path}(score ${f.score}${f.title ? `,标题「${f.title}」` : ''})`)
|
|
62
|
+
lines.push(`绝对路径:${f.absPath}|命中行:${f.hitLines.join(', ')}`)
|
|
63
|
+
for (const s of f.snippets) {
|
|
64
|
+
lines.push('')
|
|
65
|
+
lines.push(`--- L${s.from} 起(命中行 L${s.line})---`)
|
|
66
|
+
lines.push(s.text)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
lines.push('')
|
|
70
|
+
lines.push(value.hint ?? '')
|
|
71
|
+
return lines.join('\n')
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** dsh_kb_search 工具定义。 */
|
|
75
|
+
function kbSearchTool(getIndex, hostVersion) {
|
|
76
|
+
return {
|
|
77
|
+
name: 'dsh_kb_search',
|
|
78
|
+
description:
|
|
79
|
+
'dsh 插件开发知识库本地打分检索(零 token、毫秒级):在官方文档完整镜像(站点 194 页 + 仓库补充文档)中' +
|
|
80
|
+
'按关键词检索,返回命中文件路径、行号与上下文片段,用 read 工具按行号局部读原文。' +
|
|
81
|
+
'query 为空或 "topics" 返回主题导航(任务场景 → 文档清单)。写 dsh 插件 / Tool / 配置 / 服务 / 事件 /' +
|
|
82
|
+
'查 ctx.* API 签名 / 打包发布 / LLM 适配器时先用本工具定位文档,找不到再用文件工具 grep。' +
|
|
83
|
+
'仅限 dsh(DeepSeek Harness)插件开发知识库。',
|
|
84
|
+
parameters: {
|
|
85
|
+
type: 'object',
|
|
86
|
+
additionalProperties: false,
|
|
87
|
+
required: ['query'],
|
|
88
|
+
properties: {
|
|
89
|
+
query: {
|
|
90
|
+
type: 'string',
|
|
91
|
+
description:
|
|
92
|
+
'检索关键词,空白分隔多词(如 "ctx.tools.register"、"插件配置 发布");空串或 "topics" 返回主题导航。'
|
|
93
|
+
},
|
|
94
|
+
scope: {
|
|
95
|
+
type: 'string',
|
|
96
|
+
enum: ['all', 'api'],
|
|
97
|
+
description:
|
|
98
|
+
'all = 全部文档(默认);api = 只搜 API 签名/目录类文档(tool-catalog、config-catalog、capability-seams、cordis-api、subsystems 等),适合查 ctx.* API。'
|
|
99
|
+
},
|
|
100
|
+
lang: {
|
|
101
|
+
type: 'string',
|
|
102
|
+
enum: ['zh', 'en'],
|
|
103
|
+
description: '文档语言(默认 zh;en 首次调用会现场建英文索引,稍慢)'
|
|
104
|
+
},
|
|
105
|
+
maxResults: {
|
|
106
|
+
type: 'integer',
|
|
107
|
+
description: '返回文件数上限(默认 8,最大 20)'
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
output: {
|
|
112
|
+
schema: {
|
|
113
|
+
type: 'object',
|
|
114
|
+
additionalProperties: false,
|
|
115
|
+
required: ['kind', 'text'],
|
|
116
|
+
properties: {
|
|
117
|
+
kind: { type: 'string', enum: ['hits', 'empty', 'topics'] },
|
|
118
|
+
text: { type: 'string' }
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
render: (_args, value) => [{ type: 'text', text: value.text }]
|
|
122
|
+
},
|
|
123
|
+
async execute(args) {
|
|
124
|
+
const { index, error } = getIndex(args.lang === 'en' ? 'en' : 'zh')
|
|
125
|
+
if (error) throw new Error(error)
|
|
126
|
+
const result = kbSearch(index, args.query, {
|
|
127
|
+
scope: args.scope === 'api' ? 'api' : 'all',
|
|
128
|
+
maxResults: args.maxResults,
|
|
129
|
+
})
|
|
130
|
+
const banner = versionBanner(index.kbDir, hostVersion)
|
|
131
|
+
return { kind: result.kind, text: banner ? `${banner}\n\n${renderResult(result)}` : renderResult(result) }
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** dsh_kb_read 工具定义(1.8.0):知识库局部阅读,带行号窗口 + 中英对照提示。 */
|
|
137
|
+
function kbReadTool(getIndex, hostVersion) {
|
|
138
|
+
return {
|
|
139
|
+
name: 'dsh_kb_read',
|
|
140
|
+
description:
|
|
141
|
+
'dsh 插件开发知识库局部阅读:按知识库相对路径 + 行号窗口读原文,输出带行号,' +
|
|
142
|
+
'自动防大文件整读(单次窗口有上限)、自动处理编码,并提示中英对照版本的对应路径。' +
|
|
143
|
+
'通常与 dsh_kb_search 配套:先 search 拿到 path + 命中行号,再用本工具按行号窗口读上下文。' +
|
|
144
|
+
'仅限知识库目录内的 .md 文件(路径越界会被拒绝)。' +
|
|
145
|
+
'仅限 dsh(DeepSeek Harness)插件开发知识库。',
|
|
146
|
+
parameters: {
|
|
147
|
+
type: 'object',
|
|
148
|
+
additionalProperties: false,
|
|
149
|
+
required: ['path'],
|
|
150
|
+
properties: {
|
|
151
|
+
path: {
|
|
152
|
+
type: 'string',
|
|
153
|
+
description: '知识库相对路径(正斜杠,如 "site/develop/basic/tool.md"、"extra/upgrade-guide/v0.2.0-rc.2/guide.zh.md")'
|
|
154
|
+
},
|
|
155
|
+
offset: {
|
|
156
|
+
type: 'integer',
|
|
157
|
+
description: '起始行(1 基,默认 1)'
|
|
158
|
+
},
|
|
159
|
+
limit: {
|
|
160
|
+
type: 'integer',
|
|
161
|
+
description: '读取行数(默认 120,最大 400)'
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
},
|
|
165
|
+
output: {
|
|
166
|
+
schema: {
|
|
167
|
+
type: 'object',
|
|
168
|
+
additionalProperties: false,
|
|
169
|
+
required: ['kind', 'text'],
|
|
170
|
+
properties: {
|
|
171
|
+
kind: { type: 'string', enum: ['text', 'error'] },
|
|
172
|
+
text: { type: 'string' }
|
|
173
|
+
}
|
|
174
|
+
},
|
|
175
|
+
render: (_args, value) => [{ type: 'text', text: value.text }]
|
|
176
|
+
},
|
|
177
|
+
async execute(args) {
|
|
178
|
+
const { index, error } = getIndex('zh')
|
|
179
|
+
if (error) throw new Error(error)
|
|
180
|
+
const rel = String(args.path ?? '').replaceAll('\\', '/').replace(/^\/+/, '')
|
|
181
|
+
if (!rel || !rel.endsWith('.md')) {
|
|
182
|
+
return { kind: 'error', text: `【拒绝】path 必须是知识库内的 .md 相对路径,收到:${rel || '(空)'}` }
|
|
183
|
+
}
|
|
184
|
+
const abs = resolve(index.kbDir, ...rel.split('/'))
|
|
185
|
+
const kbRootResolved = resolve(index.kbDir)
|
|
186
|
+
if (abs !== kbRootResolved && !abs.startsWith(kbRootResolved + sep)) {
|
|
187
|
+
return { kind: 'error', text: `【拒绝】路径越界:${rel}(只允许知识库目录内的文件)` }
|
|
188
|
+
}
|
|
189
|
+
let lines
|
|
190
|
+
try {
|
|
191
|
+
const st = statSync(abs)
|
|
192
|
+
if (st.size > 8_000_000) {
|
|
193
|
+
return { kind: 'error', text: `【拒绝】文件过大(${st.size} 字节):${rel}` }
|
|
194
|
+
}
|
|
195
|
+
lines = readFileSync(abs, 'utf8').split(/\r?\n/)
|
|
196
|
+
} catch (err) {
|
|
197
|
+
return { kind: 'error', text: `【读取失败】${rel}(${err?.message ?? err})。提示:先用 dsh_kb_search 确认路径是否在索引内。` }
|
|
198
|
+
}
|
|
199
|
+
const total = lines.length
|
|
200
|
+
const offset = Math.min(Math.max(Number(args.offset) > 0 ? Number(args.offset) : 1, 1), Math.max(total, 1))
|
|
201
|
+
const limit = Math.min(Math.max(Number(args.limit) > 0 ? Number(args.limit) : 120, 1), 400)
|
|
202
|
+
const end = Math.min(offset - 1 + limit, total)
|
|
203
|
+
const width = String(end).length
|
|
204
|
+
const body = lines.slice(offset - 1, end)
|
|
205
|
+
.map((l, i) => `${String(offset + i).padStart(width)}| ${l}`)
|
|
206
|
+
.join('\n')
|
|
207
|
+
const footer = []
|
|
208
|
+
if (end < total) footer.push(`(未完:共 ${total} 行,已读 L${offset}-${end};续读传 offset=${end + 1})`)
|
|
209
|
+
const alt = altLangPath(rel)
|
|
210
|
+
if (alt) footer.push(`中英对照:${alt.exists ? alt.rel : alt.rel + '(未收录)'}`)
|
|
211
|
+
const banner = versionBanner(index.kbDir, hostVersion)
|
|
212
|
+
const text = [
|
|
213
|
+
banner ?? '',
|
|
214
|
+
`【知识库阅读】${rel}(共 ${total} 行,窗口 L${offset}-${end})`,
|
|
215
|
+
body,
|
|
216
|
+
...footer,
|
|
217
|
+
].filter(Boolean).join('\n')
|
|
218
|
+
return { kind: 'text', text }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/** 中英对照路径推导:site/ ↔ site/en/,extra/*.zh.md ↔ extra/*.md。存在性现场检查。 */
|
|
224
|
+
function altLangPath(rel) {
|
|
225
|
+
let counter
|
|
226
|
+
if (rel.startsWith('site/en/')) counter = rel.replace('site/en/', 'site/')
|
|
227
|
+
else if (rel.startsWith('site/')) counter = rel.replace('site/', 'site/en/')
|
|
228
|
+
else if (rel.startsWith('extra/') && rel.endsWith('.zh.md')) counter = rel.slice(0, -'.zh.md'.length) + '.md'
|
|
229
|
+
else if (rel.startsWith('extra/') && rel.endsWith('.md') && !rel.endsWith('.zh.md')) counter = rel.slice(0, -'.md'.length) + '.zh.md'
|
|
230
|
+
else return null
|
|
231
|
+
const abs = join(KB_DIR, ...counter.split('/'))
|
|
232
|
+
return { rel: counter, exists: existsSync(abs) }
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** logger 拿不到时降级 console。 */
|
|
236
|
+
function makeLogger(ctx, tag) {
|
|
237
|
+
return {
|
|
238
|
+
info: (...args) => (ctx.logger?.info ? ctx.logger.info(...args) : console.log(tag, ...args)),
|
|
239
|
+
warn: (...args) => (ctx.logger?.warn ? ctx.logger.warn(...args) : console.warn(tag, ...args))
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/** 极简 frontmatter 解析(name/description/whenToUse)。 */
|
|
244
|
+
function parseFrontmatter(md) {
|
|
245
|
+
const match = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(md)
|
|
246
|
+
if (!match) return { name: undefined, description: undefined, whenToUse: undefined, body: md }
|
|
247
|
+
const meta = {}
|
|
248
|
+
for (const line of match[1].split(/\r?\n/)) {
|
|
249
|
+
const i = line.indexOf(':')
|
|
250
|
+
if (i <= 0) continue
|
|
251
|
+
const key = line.slice(0, i).trim()
|
|
252
|
+
if (key) meta[key] = line.slice(i + 1).trim().replace(/^["']|["']$/g, '')
|
|
253
|
+
}
|
|
254
|
+
return { ...meta, body: md.slice(match[0].length) }
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/** 从 skills/ 目录加载技能定义(文件缺失/无 frontmatter 时跳过并警告,绝不让插件挂载失败)。 */
|
|
258
|
+
function loadSkills(log) {
|
|
259
|
+
const out = []
|
|
260
|
+
if (!existsSync(SKILLS_DIR)) {
|
|
261
|
+
log.warn('skills 目录缺失,技能未加载:' + SKILLS_DIR)
|
|
262
|
+
return out
|
|
263
|
+
}
|
|
264
|
+
for (const file of readdirSync(SKILLS_DIR)) {
|
|
265
|
+
if (!file.endsWith('.md')) continue
|
|
266
|
+
try {
|
|
267
|
+
const fm = parseFrontmatter(readFileSync(join(SKILLS_DIR, file), 'utf8'))
|
|
268
|
+
if (!fm.name || !fm.description) {
|
|
269
|
+
log.warn(`技能文件缺少 name/description frontmatter,已跳过:${file}`)
|
|
270
|
+
continue
|
|
271
|
+
}
|
|
272
|
+
out.push({
|
|
273
|
+
name: fm.name,
|
|
274
|
+
description: fm.description,
|
|
275
|
+
whenToUse: fm.whenToUse,
|
|
276
|
+
body: fm.body
|
|
277
|
+
})
|
|
278
|
+
} catch (err) {
|
|
279
|
+
log.warn(`技能文件读取失败,已跳过:${file}(${err?.message ?? err})`)
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return out
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
export function apply(ctx, config) {
|
|
286
|
+
const log = makeLogger(ctx, '[dsh-plugin-dev-kb]')
|
|
287
|
+
const cfg = { ...DEFAULT_CONFIG, ...(config ?? {}) }
|
|
288
|
+
// 显式布尔归一(patch 配置可能是字符串)
|
|
289
|
+
for (const key of Object.keys(DEFAULT_CONFIG)) {
|
|
290
|
+
if (typeof cfg[key] === 'string') cfg[key] = cfg[key] === 'true'
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// 契约(dsh-skill validateDefinition):定义必须含 name/description/
|
|
294
|
+
// source/content 四个字符串;provider 缺省为 'runtime'。
|
|
295
|
+
//
|
|
296
|
+
// 路径注入:技能正文中的 {{KB_ROOT}} 占位符在此替换为插件实际的 kb/
|
|
297
|
+
// 绝对路径(正斜杠,Windows 反斜杠会破坏 Markdown 转义),并声明
|
|
298
|
+
// resourceBase 让 skill 工具渲染相对资源引导——知识库位置随插件安装
|
|
299
|
+
// 位置动态解析,不依赖任何写死的机器路径。
|
|
300
|
+
const kbRoot = KB_DIR.replaceAll('\\', '/')
|
|
301
|
+
for (const skill of loadSkills(log)) {
|
|
302
|
+
ctx.effect(() => ctx.skills.register({
|
|
303
|
+
name: skill.name,
|
|
304
|
+
description: skill.description,
|
|
305
|
+
...(skill.whenToUse ? { whenToUse: skill.whenToUse } : {}),
|
|
306
|
+
source: 'runtime',
|
|
307
|
+
content: skill.body.replaceAll('{{KB_ROOT}}', kbRoot),
|
|
308
|
+
resourceBase: { kind: 'directory', path: KB_DIR }
|
|
309
|
+
}), `dsh-plugin-dev-kb: register skill ${skill.name}`)
|
|
310
|
+
log.info(`技能已注册:${skill.name}`)
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// B. 检索工具(1.6.0 起)+ 局部阅读工具(1.8.0 起):
|
|
314
|
+
// 索引惰性构建(首次调用时建,宿主启动零开销),zh/en 各缓存一份;
|
|
315
|
+
// 宿主版本探测一次性完成(拿不到 → 工具输出不带版本横幅);
|
|
316
|
+
// kb 目录缺失 → 工具执行时报错并提示退回文件工具检索。
|
|
317
|
+
const hostVersion = detectHostVersion(PKG_DIR)
|
|
318
|
+
if (hostVersion) {
|
|
319
|
+
log.info(`宿主 dsh 版本探测:${hostVersion.version}(${hostVersion.source})`)
|
|
320
|
+
} else {
|
|
321
|
+
log.warn('宿主 dsh 版本探测失败(工具输出将不带版本横幅)')
|
|
322
|
+
}
|
|
323
|
+
if (cfg.searchTool || cfg.readTool) {
|
|
324
|
+
if (typeof ctx.tools?.register === 'function') {
|
|
325
|
+
const indexCache = new Map()
|
|
326
|
+
const getIndex = (lang) => {
|
|
327
|
+
if (indexCache.has(lang)) return { index: indexCache.get(lang), error: undefined }
|
|
328
|
+
if (!existsSync(KB_DIR)) {
|
|
329
|
+
return {
|
|
330
|
+
index: undefined,
|
|
331
|
+
error: `知识库目录缺失:${KB_DIR.replaceAll('\\', '/')}(插件安装不完整?请改用文件工具 grep 检索,或重装插件)`
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
try {
|
|
335
|
+
const t0 = Date.now()
|
|
336
|
+
const index = buildIndex(KB_DIR, lang, log)
|
|
337
|
+
indexCache.set(lang, index)
|
|
338
|
+
log.info(`知识库${lang} 索引就绪:${index.files.length} 个文件(${Date.now() - t0}ms)`)
|
|
339
|
+
return { index, error: undefined }
|
|
340
|
+
} catch (err) {
|
|
341
|
+
return {
|
|
342
|
+
index: undefined,
|
|
343
|
+
error: `知识库索引构建失败:${err?.message ?? err}(请改用文件工具 grep 检索知识库目录)`
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
ctx.effect(() => ctx.tools.register(kbSearchTool(getIndex, hostVersion), 'dsh-plugin-dev-kb: register dsh_kb_search'), 'dsh-plugin-dev-kb: register dsh_kb_search')
|
|
348
|
+
log.info('工具已注册:dsh_kb_search')
|
|
349
|
+
if (cfg.readTool) {
|
|
350
|
+
ctx.effect(() => ctx.tools.register(kbReadTool(getIndex, hostVersion), 'dsh-plugin-dev-kb: register dsh_kb_read'), 'dsh-plugin-dev-kb: register dsh_kb_read')
|
|
351
|
+
log.info('工具已注册:dsh_kb_read')
|
|
352
|
+
}
|
|
353
|
+
} else {
|
|
354
|
+
log.warn('ctx.tools.register 不可用,dsh_kb_search 未注册(工具子系统的注入可能失败)')
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
log.info(`已挂载(知识库在 kb/ 目录)`)
|
|
359
|
+
}
|