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.
Files changed (229) hide show
  1. package/CHANGELOG.md +186 -178
  2. package/README.en.md +172 -172
  3. package/README.md +183 -183
  4. package/kb/INDEX.md +236 -234
  5. package/kb/README.md +88 -88
  6. package/kb/extra/AGENTS.md +77 -77
  7. package/kb/extra/deepseek-llm-api-wire-extensions.md +162 -162
  8. package/kb/extra/deepseek-llm-api-wire-extensions.zh.md +162 -162
  9. package/kb/extra/defensive-patterns/package-surface.md +233 -232
  10. package/kb/extra/defensive-patterns/package-surface.zh.md +233 -232
  11. package/kb/extra/defensive-patterns/remote-and-api.md +117 -117
  12. package/kb/extra/defensive-patterns/remote-and-api.zh.md +117 -117
  13. package/kb/extra/development.md +221 -221
  14. package/kb/extra/development.zh.md +228 -228
  15. package/kb/extra/event-producer-consumer.md +133 -123
  16. package/kb/extra/event-producer-consumer.zh.md +135 -125
  17. package/kb/extra/module-graph.md +1961 -1888
  18. package/kb/extra/module-graph.zh.md +1963 -1890
  19. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.md +60 -0
  20. package/kb/extra/persistence-changes/2026-11-05-timeline-priority-notice.zh.md +60 -0
  21. package/kb/extra/persistence-changes/README.md +92 -92
  22. package/kb/extra/persistence-changes/README.zh.md +92 -92
  23. package/kb/extra/persistence-changes/historical-formats/README.md +75 -75
  24. package/kb/extra/persistence-changes/historical-formats/README.zh.md +75 -75
  25. package/kb/extra/persistence-changes/releases/README.md +103 -103
  26. package/kb/extra/persistence-changes/releases/README.zh.md +103 -103
  27. package/kb/extra/rescope.md +55 -55
  28. package/kb/extra/rescope.zh.md +55 -55
  29. package/kb/extra/subsystems/boot.md +631 -621
  30. package/kb/extra/subsystems/boot.zh.md +631 -621
  31. package/kb/extra/subsystems/computer-use.md +179 -137
  32. package/kb/extra/subsystems/computer-use.zh.md +179 -137
  33. package/kb/extra/subsystems/extensions.md +387 -384
  34. package/kb/extra/subsystems/extensions.zh.md +387 -384
  35. package/kb/extra/subsystems/feedback.md +418 -400
  36. package/kb/extra/subsystems/feedback.zh.md +418 -400
  37. package/kb/extra/subsystems/forge.md +2169 -1226
  38. package/kb/extra/subsystems/forge.zh.md +2171 -1226
  39. package/kb/extra/subsystems/liveshare.md +167 -158
  40. package/kb/extra/subsystems/liveshare.zh.md +167 -158
  41. package/kb/extra/subsystems/mcp.md +237 -234
  42. package/kb/extra/subsystems/mcp.zh.md +237 -234
  43. package/kb/extra/subsystems/office-kit.md +122 -0
  44. package/kb/extra/subsystems/office-kit.zh.md +122 -0
  45. package/kb/extra/subsystems/working-directory.md +95 -64
  46. package/kb/extra/subsystems/working-directory.zh.md +95 -64
  47. package/kb/extra/subsystems/worktrees.md +187 -127
  48. package/kb/extra/subsystems/worktrees.zh.md +187 -127
  49. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.md +21 -21
  50. package/kb/extra/upgrade-guide/v0.2.1-alpha.1/python-ptc-sandbox/guide.zh.md +21 -21
  51. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.md +18 -0
  52. package/kb/extra/upgrade-guide/v0.2.1-rc.4/sandbox-unsupported-policy-error/guide.zh.md +18 -0
  53. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.md +18 -0
  54. package/kb/extra/upgrade-guide/v0.2.2-alpha.2/llm-error-builtin-codes/guide.zh.md +18 -0
  55. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.md +34 -0
  56. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-kit-package/guide.zh.md +34 -0
  57. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.md +49 -0
  58. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-operation-tools/guide.zh.md +49 -0
  59. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.md +36 -0
  60. package/kb/extra/upgrade-guide/v0.2.2-alpha.3/office-skill-resources/guide.zh.md +36 -0
  61. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.md +26 -0
  62. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-notification-fields/guide.zh.md +26 -0
  63. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.md +22 -0
  64. package/kb/extra/upgrade-guide/v0.2.2-rc.1/account-remote-provider/guide.zh.md +22 -0
  65. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.md +21 -0
  66. package/kb/extra/upgrade-guide/v0.2.2-rc.1/archive-worktrees/guide.zh.md +21 -0
  67. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.md +23 -0
  68. package/kb/extra/upgrade-guide/v0.2.2-rc.1/attachment-error-codes/guide.zh.md +23 -0
  69. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.md +20 -0
  70. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-ax-read-limits/guide.zh.md +20 -0
  71. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.md +18 -0
  72. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-hover-ax-only/guide.zh.md +18 -0
  73. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.md +23 -0
  74. package/kb/extra/upgrade-guide/v0.2.2-rc.1/cua-native-platform/guide.zh.md +23 -0
  75. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.md +35 -0
  76. package/kb/extra/upgrade-guide/v0.2.2-rc.1/dsh-doc-skill/guide.zh.md +35 -0
  77. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.md +26 -0
  78. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-change-request-overview/guide.zh.md +26 -0
  79. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.md +26 -0
  80. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-pinned-diff/guide.zh.md +26 -0
  81. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.md +19 -0
  82. package/kb/extra/upgrade-guide/v0.2.2-rc.1/forge-review-reads/guide.zh.md +19 -0
  83. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.md +26 -0
  84. package/kb/extra/upgrade-guide/v0.2.2-rc.1/package-export-cleanup/guide.zh.md +26 -0
  85. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.md +22 -0
  86. package/kb/extra/upgrade-guide/v0.2.2-rc.1/plugin-update-method/guide.zh.md +22 -0
  87. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.md +25 -0
  88. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-bootstrap-protocol/guide.zh.md +25 -0
  89. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.md +48 -0
  90. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-inspect-output/guide.zh.md +48 -0
  91. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.md +45 -0
  92. package/kb/extra/upgrade-guide/v0.2.2-rc.1/ptc-scoped-repl-default/guide.zh.md +45 -0
  93. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.md +19 -0
  94. package/kb/extra/upgrade-guide/v0.2.2-rc.1/runtime-context-reminders/guide.zh.md +19 -0
  95. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.md +17 -0
  96. package/kb/extra/upgrade-guide/v0.2.2-rc.1/session-append-overloads/guide.zh.md +17 -0
  97. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.md +27 -0
  98. package/kb/extra/upgrade-guide/v0.2.2-rc.1/subagent-interrupt-default/guide.zh.md +27 -0
  99. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.md +20 -0
  100. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-backend-cleanup-error/guide.zh.md +20 -0
  101. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.md +19 -0
  102. package/kb/extra/upgrade-guide/v0.2.2-rc.1/terminal-bundle-sidebar-backend/guide.zh.md +19 -0
  103. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.md +18 -0
  104. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-fetch-transport-error-codes/guide.zh.md +18 -0
  105. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.md +41 -0
  106. package/kb/extra/upgrade-guide/v0.2.2-rc.1/web-search-billing/guide.zh.md +41 -0
  107. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.md +21 -0
  108. package/kb/extra/upgrade-guide/v0.2.2-rc.1/window-drag-regions/guide.zh.md +21 -0
  109. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.md +29 -0
  110. package/kb/extra/upgrade-guide/v0.2.2-rc.2/client-module-metadata/guide.zh.md +29 -0
  111. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.md +25 -0
  112. package/kb/extra/upgrade-guide/v0.2.2-rc.2/connection-page-peer/guide.zh.md +25 -0
  113. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.md +31 -0
  114. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cordis-root-and-intercept-metadata/guide.zh.md +31 -0
  115. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.md +24 -0
  116. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-agents-without-preset/guide.zh.md +24 -0
  117. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.md +24 -0
  118. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-macos-provider/guide.zh.md +24 -0
  119. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.md +19 -0
  120. package/kb/extra/upgrade-guide/v0.2.2-rc.2/cua-partial-observations/guide.zh.md +19 -0
  121. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.md +15 -0
  122. package/kb/extra/upgrade-guide/v0.2.2-rc.2/deepseek-files-timeout/guide.zh.md +15 -0
  123. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.md +28 -0
  124. package/kb/extra/upgrade-guide/v0.2.2-rc.2/experimental-tool-error-codes/guide.zh.md +28 -0
  125. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.md +36 -0
  126. package/kb/extra/upgrade-guide/v0.2.2-rc.2/package-export-cleanup/guide.zh.md +36 -0
  127. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.md +22 -0
  128. package/kb/extra/upgrade-guide/v0.2.2-rc.2/python-ptc-removal/guide.zh.md +22 -0
  129. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.md +31 -0
  130. package/kb/extra/upgrade-guide/v0.2.2-rc.2/session-status-package/guide.zh.md +31 -0
  131. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.md +18 -0
  132. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shared-search-filter/guide.zh.md +18 -0
  133. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.md +30 -0
  134. package/kb/extra/upgrade-guide/v0.2.2-rc.2/shell-fs-tool-error-codes/guide.zh.md +30 -0
  135. package/kb/extra/web-styling.md +57 -57
  136. package/kb/extra/web-styling.zh.md +57 -57
  137. package/kb/meta/search-index.json +4508 -3980
  138. package/kb/meta/source.json +18 -18
  139. package/kb/site/develop/practice/llm-adapter.md +192 -192
  140. package/kb/site/en/develop/practice/llm-adapter.md +192 -192
  141. package/kb/site/en/guide/desktop-uninstall-cleanup.md +63 -0
  142. package/kb/site/en/guide/plugin-market.md +153 -151
  143. package/kb/site/en/guide/providers.md +234 -234
  144. package/kb/site/en/guide/quickstart.md +35 -34
  145. package/kb/site/en/reference/capability-seams.md +789 -767
  146. package/kb/site/en/reference/config-catalog.md +5432 -5128
  147. package/kb/site/en/reference/cookbook/adding-a-package.md +175 -175
  148. package/kb/site/en/reference/cookbook/adding-a-tool.md +105 -104
  149. package/kb/site/en/reference/cookbook/extension-cookbook.md +178 -135
  150. package/kb/site/en/reference/cordis-api/context.md +373 -369
  151. package/kb/site/en/reference/cordis-api/fiber.md +457 -433
  152. package/kb/site/en/reference/cordis-api/inherited.md +44 -44
  153. package/kb/site/en/reference/cordis-api/service.md +118 -107
  154. package/kb/site/en/reference/experimental-persistence-catalog.md +36 -23
  155. package/kb/site/en/reference/index.md +174 -174
  156. package/kb/site/en/reference/persistence-catalog.md +9453 -9432
  157. package/kb/site/en/reference/subsystems/compaction.md +288 -288
  158. package/kb/site/en/reference/subsystems/core.md +1172 -1171
  159. package/kb/site/en/reference/subsystems/credentials.md +526 -497
  160. package/kb/site/en/reference/subsystems/filesystem.md +708 -708
  161. package/kb/site/en/reference/subsystems/index.md +82 -82
  162. package/kb/site/en/reference/subsystems/llm-streaming.md +1290 -1288
  163. package/kb/site/en/reference/subsystems/lsp.md +230 -206
  164. package/kb/site/en/reference/subsystems/persistence.md +451 -432
  165. package/kb/site/en/reference/subsystems/plan.md +91 -91
  166. package/kb/site/en/reference/subsystems/ptc-runtime.md +295 -261
  167. package/kb/site/en/reference/subsystems/sandbox.md +250 -232
  168. package/kb/site/en/reference/subsystems/session-query.md +525 -515
  169. package/kb/site/en/reference/subsystems/session-reference.md +231 -229
  170. package/kb/site/en/reference/subsystems/session.md +1478 -1431
  171. package/kb/site/en/reference/subsystems/sidebar-right.md +165 -165
  172. package/kb/site/en/reference/subsystems/skills.md +356 -356
  173. package/kb/site/en/reference/subsystems/slots.md +235 -231
  174. package/kb/site/en/reference/subsystems/terminal.md +188 -188
  175. package/kb/site/en/reference/subsystems/tools.md +902 -891
  176. package/kb/site/en/reference/subsystems/user-questions.md +330 -327
  177. package/kb/site/en/reference/subsystems/web-client.md +161 -104
  178. package/kb/site/en/reference/subsystems/web-server.md +434 -351
  179. package/kb/site/en/reference/subsystems/web.md +268 -268
  180. package/kb/site/en/reference/subsystems/workflow.md +287 -282
  181. package/kb/site/en/reference/subsystems/workspace.md +743 -651
  182. package/kb/site/en/reference/tool-catalog.md +2867 -2704
  183. package/kb/site/guide/desktop-uninstall-cleanup.md +63 -0
  184. package/kb/site/guide/plugin-market.md +153 -151
  185. package/kb/site/guide/providers.md +234 -234
  186. package/kb/site/guide/quickstart.md +35 -34
  187. package/kb/site/reference/capability-seams.md +789 -767
  188. package/kb/site/reference/config-catalog.md +5432 -5128
  189. package/kb/site/reference/cookbook/adding-a-package.md +175 -175
  190. package/kb/site/reference/cookbook/adding-a-tool.md +107 -106
  191. package/kb/site/reference/cookbook/extension-cookbook.md +182 -139
  192. package/kb/site/reference/cordis-api/context.md +373 -369
  193. package/kb/site/reference/cordis-api/fiber.md +459 -435
  194. package/kb/site/reference/cordis-api/inherited.md +44 -44
  195. package/kb/site/reference/cordis-api/service.md +118 -107
  196. package/kb/site/reference/experimental-persistence-catalog.md +36 -23
  197. package/kb/site/reference/index.md +178 -178
  198. package/kb/site/reference/persistence-catalog.md +9453 -9432
  199. package/kb/site/reference/subsystems/compaction.md +288 -288
  200. package/kb/site/reference/subsystems/core.md +1184 -1183
  201. package/kb/site/reference/subsystems/credentials.md +526 -497
  202. package/kb/site/reference/subsystems/filesystem.md +708 -708
  203. package/kb/site/reference/subsystems/index.md +82 -82
  204. package/kb/site/reference/subsystems/llm-streaming.md +1296 -1294
  205. package/kb/site/reference/subsystems/lsp.md +230 -206
  206. package/kb/site/reference/subsystems/persistence.md +451 -432
  207. package/kb/site/reference/subsystems/plan.md +91 -91
  208. package/kb/site/reference/subsystems/ptc-runtime.md +295 -261
  209. package/kb/site/reference/subsystems/sandbox.md +250 -232
  210. package/kb/site/reference/subsystems/session-query.md +525 -515
  211. package/kb/site/reference/subsystems/session-reference.md +231 -229
  212. package/kb/site/reference/subsystems/session.md +1482 -1435
  213. package/kb/site/reference/subsystems/sidebar-right.md +165 -165
  214. package/kb/site/reference/subsystems/skills.md +356 -356
  215. package/kb/site/reference/subsystems/slots.md +239 -235
  216. package/kb/site/reference/subsystems/terminal.md +188 -188
  217. package/kb/site/reference/subsystems/tools.md +902 -891
  218. package/kb/site/reference/subsystems/user-questions.md +330 -327
  219. package/kb/site/reference/subsystems/web-client.md +161 -104
  220. package/kb/site/reference/subsystems/web-server.md +434 -351
  221. package/kb/site/reference/subsystems/web.md +268 -268
  222. package/kb/site/reference/subsystems/workflow.md +287 -282
  223. package/kb/site/reference/subsystems/workspace.md +743 -651
  224. package/kb/site/reference/tool-catalog.md +2872 -2709
  225. package/lib/index.js +359 -359
  226. package/package.json +68 -68
  227. package/skills/dsh-plugin-dev-kb.md +71 -70
  228. package/kb/extra/subsystems/office-to-pdf.md +0 -87
  229. 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、毫秒级):在官方文档完整镜像(站点 192 页 + 仓库补充文档)中' +
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
+ }