dsh-plugin-dev-kb 1.5.1 → 1.6.0
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 +20 -0
- package/README.en.md +10 -5
- package/README.md +19 -11
- package/lib/index.js +151 -9
- package/lib/kb-search.js +261 -0
- package/package.json +2 -2
- package/scripts/selfcheck.mjs +10 -0
- package/skills/dsh-dev-sandbox.md +16 -13
- package/skills/dsh-plugin-dev-kb.md +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [1.6.0] - 2026-10-30
|
|
4
|
+
|
|
5
|
+
- **新增 `dsh_kb_search` 工具**:知识库本地打分检索(零 token、毫秒级、零运行时依赖)。
|
|
6
|
+
一次调用返回命中文件路径 + 行号 + 上下文片段(每文件最多 3 处,前后各 2 行),替代
|
|
7
|
+
agent 多轮 grep/read 检索 634 文件知识库。
|
|
8
|
+
- 参数:`query`(空白分词多词;空或 `"topics"` 返回主题导航)、`scope`(`api` 只搜
|
|
9
|
+
API 签名/目录类文档:tool-catalog / config-catalog / capability-seams / cordis-api /
|
|
10
|
+
subsystems 等)、`lang`(zh 默认;en 首次调用现场建索引)、`maxResults`(默认 8)。
|
|
11
|
+
- 打分:标题 ×8、路径 ×4、正文行命中平方和(多词同现加权)、路径子词弱匹配
|
|
12
|
+
(`ctx.tools.register` → tool.md)、`site/develop|guide/` 教程优先 +2。
|
|
13
|
+
- 索引惰性构建(首次调用时,~60ms / 314 个中文文件)、内存缓存、只读不写;
|
|
14
|
+
`searchTool: false` 可关闭。
|
|
15
|
+
- 技能 `dsh-plugin-dev-kb` 检索指南同步改为「工具优先,文件工具兜底」。
|
|
16
|
+
|
|
17
|
+
## [1.5.2] - 2026-10-21
|
|
18
|
+
|
|
19
|
+
- `dsh-dev-sandbox` 技能去掉固定 `dev` profile 约定:**沙箱 profile 名由 agent 按任务自定**
|
|
20
|
+
(建议 `sb-<插件名>`),建前检查不与现有 profile(尤其主力)重名,端口同样自选空闲高位端口;
|
|
21
|
+
新增沙箱「用完即删、不留残留」的收尾说明。
|
|
22
|
+
|
|
3
23
|
## [1.5.1] - 2026-10-21
|
|
4
24
|
|
|
5
25
|
- **知识库同步上游 master `687f282e`(2026-10-17)**:
|
package/README.en.md
CHANGED
|
@@ -17,6 +17,7 @@
|
|
|
17
17
|
**The official docs at your side whenever you write dsh plugins**: the entire content of the DeepSeek Harness official documentation site
|
|
18
18
|
<https://deepseek-harness.github.io/deepseek-harness/> is organized into a form natively usable by dsh —
|
|
19
19
|
once this plugin is installed, the agent automatically loads the knowledge base when writing plugins and locates docs by task scenario, so you never have to dig through web pages again.
|
|
20
|
+
It also ships the `dsh_kb_search` tool (since 1.6.0): local scored search over the knowledge base that returns matched files, line numbers, and context snippets in a single call.
|
|
20
21
|
|
|
21
22
|
## What is this
|
|
22
23
|
|
|
@@ -25,6 +26,9 @@ once this plugin is installed, the agent automatically loads the knowledge base
|
|
|
25
26
|
- **Supplementary docs**: 439 development references from the repo's `docs/` that were not published to the site (glossary, defensive patterns, module graph, testing strategy, incident postmortems, i18n specs, upgrade guides, persistence changes, etc.).
|
|
26
27
|
- **Agent-friendly**: the `skills/dsh-plugin-dev-kb.md` skill lets dsh auto-load it during plugin development tasks, providing the knowledge base location,
|
|
27
28
|
topic navigation, and search strategy; `kb/meta/topics.md` maps task scenarios to the files to read; `kb/meta/search-index.json` provides full search coverage.
|
|
29
|
+
- **Local search tool**: `dsh_kb_search` performs scored search over the knowledge base (title / path / body multi-term weighting) and returns
|
|
30
|
+
matched file paths, line numbers, and context snippets in a single call; supports `scope: api` (search only API-signature/catalog docs),
|
|
31
|
+
`lang: en`, and an empty query returning topic navigation. The index is built lazily in memory (~60ms / 314 files), read-only.
|
|
28
32
|
|
|
29
33
|
## Features
|
|
30
34
|
|
|
@@ -32,9 +36,9 @@ once this plugin is installed, the agent automatically loads the knowledge base
|
|
|
32
36
|
192 pages in both Chinese and English, links rewritten to site routes, verbatim identical to the live site;
|
|
33
37
|
- **Repo supplementary docs**: 439 development references not published to the site (glossary, defensive patterns, module graph, testing strategy, incident postmortems, i18n specs, upgrade guides, persistence changes, etc.);
|
|
34
38
|
- **Agent-friendly**: the `dsh-plugin-dev-kb` skill lets dsh auto-load the knowledge base during plugin development tasks;
|
|
35
|
-
`kb/meta/topics.md` maps task scenarios to the files to read, and `kb/meta/search-index.json` provides full search coverage;
|
|
39
|
+
the `dsh_kb_search` tool locates docs in a single call; `kb/meta/topics.md` maps task scenarios to the files to read, and `kb/meta/search-index.json` provides full search coverage;
|
|
36
40
|
- **Human-usable**: browse the `kb/` directory directly, or open `kb/INDEX.md` to look things up by URL mapping;
|
|
37
|
-
-
|
|
41
|
+
- Zero runtime dependencies; the single tool `dsh_kb_search` is a pure local Node implementation (no network, no file writes).
|
|
38
42
|
|
|
39
43
|
## Configuration
|
|
40
44
|
|
|
@@ -80,8 +84,8 @@ After restarting, open a new session and simply ask:
|
|
|
80
84
|
|
|
81
85
|
> Help me write a minimal dsh plugin
|
|
82
86
|
|
|
83
|
-
The agent will automatically load the `dsh-plugin-dev-kb` skill (visible in the skill catalog), locate the docs via `
|
|
84
|
-
and only then start working — if you see it referencing `kb/site/...` paths, the verification has passed.
|
|
87
|
+
The agent will automatically load the `dsh-plugin-dev-kb` skill (visible in the skill catalog), locate the docs via the `dsh_kb_search` tool
|
|
88
|
+
(or `kb/meta/topics.md`), and only then start working — if you see it referencing `kb/site/...` paths, the verification has passed.
|
|
85
89
|
|
|
86
90
|
## Directory structure
|
|
87
91
|
|
|
@@ -107,7 +111,8 @@ dsh-plugin-dev-kb/
|
|
|
107
111
|
## Usage
|
|
108
112
|
|
|
109
113
|
- **Agent side**: when writing plugins / Tools / config / services / events / packaging / LLM adapters, load
|
|
110
|
-
the `dsh-plugin-dev-kb` skill →
|
|
114
|
+
the `dsh-plugin-dev-kb` skill → prefer the `dsh_kb_search` tool (one call returns matched files + line numbers + snippets;
|
|
115
|
+
`scope: api` for `ctx.*` API signatures) → read the source locally by line number; fall back to grepping `kb/` if the tool is unavailable.
|
|
111
116
|
- **Human side**: browse the `kb/` directory directly, or open `kb/INDEX.md` to look things up by URL mapping.
|
|
112
117
|
|
|
113
118
|
## Self-check
|
package/README.md
CHANGED
|
@@ -17,8 +17,9 @@
|
|
|
17
17
|
**写 dsh 插件时的随身辅助**:① 把 DeepSeek Harness 官方文档站点
|
|
18
18
|
<https://deepseek-harness.github.io/deepseek-harness/> 的全部内容整理为 dsh 原生可用的形态——
|
|
19
19
|
装了这个插件,agent 写插件时自动加载知识库,按任务场景定位文档,不用再翻网页;
|
|
20
|
-
② 附带 `dsh-dev-sandbox`
|
|
21
|
-
agent 自主启动 / 重启 / 冒烟验证,插件崩了不影响主力 dsh
|
|
20
|
+
② 附带 `dsh-dev-sandbox` 技能:自建独立沙箱 profile 开发沙箱工作流(沙箱名由 agent 按任务自定),
|
|
21
|
+
开发中的插件隔离运行,agent 自主启动 / 重启 / 冒烟验证,插件崩了不影响主力 dsh;
|
|
22
|
+
③ `dsh_kb_search` 工具(1.6.0 起):知识库本地打分检索,一次调用返回命中文件 + 行号 + 上下文片段。
|
|
22
23
|
|
|
23
24
|
## 这是什么
|
|
24
25
|
|
|
@@ -27,9 +28,12 @@ agent 自主启动 / 重启 / 冒烟验证,插件崩了不影响主力 dsh。
|
|
|
27
28
|
- **补充文档**:仓库 `docs/` 中未发布到站点的 439 篇开发参考(术语表、防御模式、模块图、测试策略、事故复盘、i18n 规范、升级指南、持久化变更等)。
|
|
28
29
|
- **agent 友好**:`skills/dsh-plugin-dev-kb.md` 技能让 dsh 在插件开发任务中自动加载,获知知识库位置、
|
|
29
30
|
主题导航与检索策略;`kb/meta/topics.md` 按任务场景映射要读的文件;`kb/meta/search-index.json` 提供全量检索。
|
|
30
|
-
- **开发沙箱**:`skills/dsh-dev-sandbox.md`
|
|
31
|
-
|
|
31
|
+
- **开发沙箱**:`skills/dsh-dev-sandbox.md` 技能提供自建独立沙箱 profile 工作流(沙箱名 agent 按任务
|
|
32
|
+
自定,不与主力 profile 重名)——初始化(兼容闸门 / link: 挂载)、后台启动(自选空闲端口)、自主重启、headless 冒烟验证,
|
|
32
33
|
全程 agent 闭环,不要求用户重启。
|
|
34
|
+
- **本地检索工具**:`dsh_kb_search` 工具在知识库上做打分检索(标题 / 路径 / 正文多词加权),
|
|
35
|
+
一次调用返回命中文件路径、行号与上下文片段;支持 `scope: api`(只搜 API 签名/目录类文档)、
|
|
36
|
+
`lang: en`、空 query 返回主题导航。索引惰性构建于内存(约 60ms / 314 文件),只读不写。
|
|
33
37
|
|
|
34
38
|
## 功能
|
|
35
39
|
|
|
@@ -37,17 +41,19 @@ agent 自主启动 / 重启 / 冒烟验证,插件崩了不影响主力 dsh。
|
|
|
37
41
|
中英双语共 188 页,链接按站点路由重写,与线上逐字一致;
|
|
38
42
|
- **仓库补充文档**:未发布到站点的 182 篇开发参考(术语表、防御模式、模块图、测试策略、事故复盘、i18n 规范、升级指南、持久化变更等);
|
|
39
43
|
- **agent 友好**:`dsh-plugin-dev-kb` 技能让 dsh 在插件开发任务中自动加载知识库;
|
|
40
|
-
`kb/meta/topics.md` 按任务场景映射要读的文件,`kb/meta/search-index.json` 提供全量检索;
|
|
44
|
+
`dsh_kb_search` 工具一次调用完成检索定位;`kb/meta/topics.md` 按任务场景映射要读的文件,`kb/meta/search-index.json` 提供全量检索;
|
|
41
45
|
- **人侧可用**:直接浏览 `kb/` 目录,或打开 `kb/INDEX.md` 按 URL 对照查阅;
|
|
42
|
-
-
|
|
46
|
+
- 零运行时依赖;唯一的工具 `dsh_kb_search` 为纯 Node 本地实现(不联网、不写文件)。
|
|
43
47
|
|
|
44
48
|
## 配置
|
|
45
49
|
|
|
46
|
-
|
|
50
|
+
默认零配置,安装即用:
|
|
47
51
|
|
|
48
52
|
- 不读取环境变量,不需要 API Key / token,不写配置文件;
|
|
49
53
|
- 挂载后 `skills/dsh-plugin-dev-kb.md` 技能在会话启动时进入可用技能列表,自动生效;
|
|
50
|
-
-
|
|
54
|
+
- `dsh_kb_search` 工具随宿主启动注册,首次调用时构建索引(毫秒级);如需关闭,
|
|
55
|
+
在 profile 的插件配置里设 `searchTool: false`(只保留技能,不注册工具);
|
|
56
|
+
- 知识库更新走 `npm run rebuild-index`(见「更新知识库」)。
|
|
51
57
|
|
|
52
58
|
## 安装 / 挂载
|
|
53
59
|
|
|
@@ -85,8 +91,8 @@ npm install
|
|
|
85
91
|
|
|
86
92
|
> 帮我写一个最小的 dsh 插件
|
|
87
93
|
|
|
88
|
-
agent 会自动加载 `dsh-plugin-dev-kb`
|
|
89
|
-
|
|
94
|
+
agent 会自动加载 `dsh-plugin-dev-kb` 技能(技能清单里可见),调用 `dsh_kb_search`
|
|
95
|
+
(或读 `kb/meta/topics.md`)定位文档后再动手——看到它引用 `kb/site/...` 路径即验证通过。
|
|
90
96
|
|
|
91
97
|
## 目录结构
|
|
92
98
|
|
|
@@ -112,7 +118,9 @@ dsh-plugin-dev-kb/
|
|
|
112
118
|
## 使用
|
|
113
119
|
|
|
114
120
|
- **agent 侧**:写插件 / Tool / 配置 / 服务 / 事件 / 打包 / LLM 适配器时,加载
|
|
115
|
-
`dsh-plugin-dev-kb` 技能 →
|
|
121
|
+
`dsh-plugin-dev-kb` 技能 → 首选调 `dsh_kb_search` 工具检索(一次拿命中文件 + 行号 + 片段,
|
|
122
|
+
`scope: api` 查 `ctx.*` API 签名)→ 用 read 按行号局部读原文;工具不可用时退回
|
|
123
|
+
grep `kb/` 兜底。
|
|
116
124
|
- **人侧**:直接浏览 `kb/` 目录,或打开 `kb/INDEX.md` 按 URL 对照查阅。
|
|
117
125
|
|
|
118
126
|
## 自检
|
package/lib/index.js
CHANGED
|
@@ -1,30 +1,134 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* dsh-plugin-dev-kb —
|
|
2
|
+
* dsh-plugin-dev-kb — 主机插件(技能载体 + 知识库检索工具)。
|
|
3
3
|
*
|
|
4
|
-
* 挂载于 profile
|
|
5
|
-
* dsh-plugin-dev-kb 技能注册为 runtime skill,让每个会话的 agent
|
|
6
|
-
* 在插件开发 / 查询 dsh 能力 / 写 Tool / 配置 cordis.yml 类任务时,
|
|
7
|
-
* 能按技能 description 自动加载,从而获知知识库位置与阅读路径。
|
|
4
|
+
* 挂载于 profile 根层(全局层),两部分职责:
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
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/ 目录)是纯文档,模型仍可通过文件工具按技能指示直接读取。
|
|
11
15
|
*
|
|
12
16
|
* 依赖纪律:本模块不 import 任何 @deepseek-ai/* 运行时包(插件以
|
|
13
17
|
* link: 方式装入 profile,Node ESM 按 realpath 解析链接包,外部依赖
|
|
14
|
-
*
|
|
18
|
+
* 从插件目录解析不到);工具走 ctx.tools.register 的原始 definition
|
|
19
|
+
* 形状(标准 JSON Schema,参照 dsh-plugin-windows-guard 的先例)。
|
|
15
20
|
*/
|
|
16
21
|
|
|
17
22
|
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
18
23
|
import { dirname, join } from 'node:path'
|
|
19
24
|
import { fileURLToPath } from 'node:url'
|
|
25
|
+
import { buildIndex, search as kbSearch } from './kb-search.js'
|
|
20
26
|
|
|
21
27
|
export const name = 'dsh-plugin-dev-kb'
|
|
22
|
-
export const inject = ['skills']
|
|
28
|
+
export const inject = ['skills', 'tools']
|
|
23
29
|
|
|
24
30
|
const PKG_DIR = dirname(fileURLToPath(import.meta.url))
|
|
25
31
|
const SKILLS_DIR = join(PKG_DIR, '..', 'skills')
|
|
26
32
|
const KB_DIR = join(PKG_DIR, '..', 'kb')
|
|
27
33
|
|
|
34
|
+
const DEFAULT_CONFIG = {
|
|
35
|
+
/** 注册 dsh_kb_search 知识库检索工具(false = 只保留技能,不注册工具)。 */
|
|
36
|
+
searchTool: true,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ---------------------------------------------------------------------------
|
|
40
|
+
// dsh_kb_search 工具(原始 definition 形状,零依赖)
|
|
41
|
+
// ---------------------------------------------------------------------------
|
|
42
|
+
|
|
43
|
+
/** 把 search 结果渲染为模型可读 Markdown。 */
|
|
44
|
+
function renderResult(value) {
|
|
45
|
+
if (value.kind === 'topics') {
|
|
46
|
+
return [
|
|
47
|
+
`【知识库主题导航】${value.topicsPath ?? ''}`,
|
|
48
|
+
value.text ?? '',
|
|
49
|
+
value.hint ?? '',
|
|
50
|
+
].filter(Boolean).join('\n')
|
|
51
|
+
}
|
|
52
|
+
if (value.kind === 'empty') {
|
|
53
|
+
return `【无命中】query: ${value.query}\n${value.hint}`
|
|
54
|
+
}
|
|
55
|
+
const lines = [`【知识库检索】query: ${value.query}(scope=${value.scope},命中 ${value.files.length} 个文件${value.truncatedFiles ? `,另有 ${value.truncatedFiles} 个未展示` : ''})`]
|
|
56
|
+
for (const f of value.files) {
|
|
57
|
+
lines.push('')
|
|
58
|
+
lines.push(`### ${f.path}(score ${f.score}${f.title ? `,标题「${f.title}」` : ''})`)
|
|
59
|
+
lines.push(`绝对路径:${f.absPath}|命中行:${f.hitLines.join(', ')}`)
|
|
60
|
+
for (const s of f.snippets) {
|
|
61
|
+
lines.push('')
|
|
62
|
+
lines.push(`--- L${s.from} 起(命中行 L${s.line})---`)
|
|
63
|
+
lines.push(s.text)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
lines.push('')
|
|
67
|
+
lines.push(value.hint ?? '')
|
|
68
|
+
return lines.join('\n')
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** dsh_kb_search 工具定义。 */
|
|
72
|
+
function kbSearchTool(getIndex) {
|
|
73
|
+
return {
|
|
74
|
+
name: 'dsh_kb_search',
|
|
75
|
+
description:
|
|
76
|
+
'dsh 插件开发知识库本地打分检索(零 token、毫秒级):在官方文档完整镜像(站点 192 页 + 仓库补充文档)中' +
|
|
77
|
+
'按关键词检索,返回命中文件路径、行号与上下文片段,用 read 工具按行号局部读原文。' +
|
|
78
|
+
'query 为空或 "topics" 返回主题导航(任务场景 → 文档清单)。写 dsh 插件 / Tool / 配置 / 服务 / 事件 /' +
|
|
79
|
+
'查 ctx.* API 签名 / 打包发布 / LLM 适配器时先用本工具定位文档,找不到再用文件工具 grep。' +
|
|
80
|
+
'仅限 dsh(DeepSeek Harness)插件开发知识库。',
|
|
81
|
+
parameters: {
|
|
82
|
+
type: 'object',
|
|
83
|
+
additionalProperties: false,
|
|
84
|
+
required: ['query'],
|
|
85
|
+
properties: {
|
|
86
|
+
query: {
|
|
87
|
+
type: 'string',
|
|
88
|
+
description:
|
|
89
|
+
'检索关键词,空白分隔多词(如 "ctx.tools.register"、"插件配置 发布");空串或 "topics" 返回主题导航。'
|
|
90
|
+
},
|
|
91
|
+
scope: {
|
|
92
|
+
type: 'string',
|
|
93
|
+
enum: ['all', 'api'],
|
|
94
|
+
description:
|
|
95
|
+
'all = 全部文档(默认);api = 只搜 API 签名/目录类文档(tool-catalog、config-catalog、capability-seams、cordis-api、subsystems 等),适合查 ctx.* API。'
|
|
96
|
+
},
|
|
97
|
+
lang: {
|
|
98
|
+
type: 'string',
|
|
99
|
+
enum: ['zh', 'en'],
|
|
100
|
+
description: '文档语言(默认 zh;en 首次调用会现场建英文索引,稍慢)'
|
|
101
|
+
},
|
|
102
|
+
maxResults: {
|
|
103
|
+
type: 'integer',
|
|
104
|
+
description: '返回文件数上限(默认 8,最大 20)'
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
output: {
|
|
109
|
+
schema: {
|
|
110
|
+
type: 'object',
|
|
111
|
+
additionalProperties: false,
|
|
112
|
+
required: ['kind', 'text'],
|
|
113
|
+
properties: {
|
|
114
|
+
kind: { type: 'string', enum: ['hits', 'empty', 'topics'] },
|
|
115
|
+
text: { type: 'string' }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
render: (_args, value) => [{ type: 'text', text: value.text }]
|
|
119
|
+
},
|
|
120
|
+
async execute(args) {
|
|
121
|
+
const { index, error } = getIndex(args.lang === 'en' ? 'en' : 'zh')
|
|
122
|
+
if (error) throw new Error(error)
|
|
123
|
+
const result = kbSearch(index, args.query, {
|
|
124
|
+
scope: args.scope === 'api' ? 'api' : 'all',
|
|
125
|
+
maxResults: args.maxResults,
|
|
126
|
+
})
|
|
127
|
+
return { kind: result.kind, text: renderResult(result) }
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
28
132
|
/** logger 拿不到时降级 console。 */
|
|
29
133
|
function makeLogger(ctx, tag) {
|
|
30
134
|
return {
|
|
@@ -77,6 +181,11 @@ function loadSkills(log) {
|
|
|
77
181
|
|
|
78
182
|
export function apply(ctx, config) {
|
|
79
183
|
const log = makeLogger(ctx, '[dsh-plugin-dev-kb]')
|
|
184
|
+
const cfg = { ...DEFAULT_CONFIG, ...(config ?? {}) }
|
|
185
|
+
// 显式布尔归一(patch 配置可能是字符串)
|
|
186
|
+
for (const key of Object.keys(DEFAULT_CONFIG)) {
|
|
187
|
+
if (typeof cfg[key] === 'string') cfg[key] = cfg[key] === 'true'
|
|
188
|
+
}
|
|
80
189
|
|
|
81
190
|
// 契约(dsh-skill validateDefinition):定义必须含 name/description/
|
|
82
191
|
// source/content 四个字符串;provider 缺省为 'runtime'。
|
|
@@ -98,5 +207,38 @@ export function apply(ctx, config) {
|
|
|
98
207
|
log.info(`技能已注册:${skill.name}`)
|
|
99
208
|
}
|
|
100
209
|
|
|
210
|
+
// B. 检索工具(1.6.0 起):索引惰性构建(首次调用时建,宿主启动零开销),
|
|
211
|
+
// zh/en 各缓存一份;kb 目录缺失 → 工具执行时报错并提示退回文件工具检索。
|
|
212
|
+
if (cfg.searchTool) {
|
|
213
|
+
if (typeof ctx.tools?.register === 'function') {
|
|
214
|
+
const indexCache = new Map()
|
|
215
|
+
const getIndex = (lang) => {
|
|
216
|
+
if (indexCache.has(lang)) return { index: indexCache.get(lang), error: undefined }
|
|
217
|
+
if (!existsSync(KB_DIR)) {
|
|
218
|
+
return {
|
|
219
|
+
index: undefined,
|
|
220
|
+
error: `知识库目录缺失:${KB_DIR.replaceAll('\\', '/')}(插件安装不完整?请改用文件工具 grep 检索,或重装插件)`
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const t0 = Date.now()
|
|
225
|
+
const index = buildIndex(KB_DIR, lang, log)
|
|
226
|
+
indexCache.set(lang, index)
|
|
227
|
+
log.info(`知识库${lang} 索引就绪:${index.files.length} 个文件(${Date.now() - t0}ms)`)
|
|
228
|
+
return { index, error: undefined }
|
|
229
|
+
} catch (err) {
|
|
230
|
+
return {
|
|
231
|
+
index: undefined,
|
|
232
|
+
error: `知识库索引构建失败:${err?.message ?? err}(请改用文件工具 grep 检索知识库目录)`
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
ctx.effect(() => ctx.tools.register(kbSearchTool(getIndex), 'dsh-plugin-dev-kb: register dsh_kb_search'), 'dsh-plugin-dev-kb: register dsh_kb_search')
|
|
237
|
+
log.info('工具已注册:dsh_kb_search')
|
|
238
|
+
} else {
|
|
239
|
+
log.warn('ctx.tools.register 不可用,dsh_kb_search 未注册(工具子系统的注入可能失败)')
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
|
|
101
243
|
log.info(`已挂载(知识库在 kb/ 目录)`)
|
|
102
244
|
}
|
package/lib/kb-search.js
ADDED
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* kb-search — 知识库本地打分检索(零依赖,纯函数)。
|
|
3
|
+
*
|
|
4
|
+
* 为 dsh_kb_search 工具提供两段式能力:
|
|
5
|
+
* buildIndex(kbDir, log) 扫描 kb/ 构建 内存索引(每文件:相对路径、标题、行数组)。
|
|
6
|
+
* 中文索引默认扫 site/(跳过 site/en/)+ extra/(跳过 *.zh.md
|
|
7
|
+
* 的英文原文对?不——extra 中英成对,跳过无 .zh 后缀的英文版)
|
|
8
|
+
* + meta/topics.md;英文索引按需构建(lang=en)。
|
|
9
|
+
* search(index, query, opts) 按空白分词打分:标题命中 ×8、路径命中 ×4、
|
|
10
|
+
* 正文行命中 ×1(多词同现加权),返回 top N 文件,
|
|
11
|
+
* 每文件附命中行号与上下文片段(每文件最多 maxSnippets 处)。
|
|
12
|
+
*
|
|
13
|
+
* 设计约束:
|
|
14
|
+
* - 只读 kb/,绝不写任何文件;索引在内存,宿主重启重建(构建毫秒级)。
|
|
15
|
+
* - 大文件不整读返回:只回片段 + 行号,模型再按行号 read 局部。
|
|
16
|
+
* - 输出总量截断保护(renderChars),超出提示用文件工具局部读。
|
|
17
|
+
* - 失败安全:任何文件读不了就跳过并计数警告,绝不让索引构建抛错崩掉宿主。
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { readdirSync, readFileSync, statSync } from 'node:fs'
|
|
21
|
+
import { join, relative, sep } from 'node:path'
|
|
22
|
+
|
|
23
|
+
/** 单文件大小上限(1.5MB):超过则跳过正文索引,只保留标题/路径(防止异常大文件拖慢启动)。 */
|
|
24
|
+
const MAX_FILE_BYTES = 1_500_000
|
|
25
|
+
|
|
26
|
+
/** 递归收集目录下全部 .md 文件(相对路径,正斜杠)。 */
|
|
27
|
+
function listMarkdownFiles(dir, base) {
|
|
28
|
+
const out = []
|
|
29
|
+
let entries
|
|
30
|
+
try {
|
|
31
|
+
entries = readdirSync(dir, { withFileTypes: true })
|
|
32
|
+
} catch {
|
|
33
|
+
return out
|
|
34
|
+
}
|
|
35
|
+
for (const e of entries) {
|
|
36
|
+
const full = join(dir, e.name)
|
|
37
|
+
if (e.isDirectory()) {
|
|
38
|
+
out.push(...listMarkdownFiles(full, base))
|
|
39
|
+
} else if (e.isFile() && e.name.endsWith('.md')) {
|
|
40
|
+
out.push(relative(base, full).split(sep).join('/'))
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** 判断相对路径是否纳入指定语言的索引。 */
|
|
47
|
+
function includeForLang(rel, lang) {
|
|
48
|
+
if (lang === 'en') {
|
|
49
|
+
// 英文:site/en/** 或 extra 下无 .zh.md 对应的英文原文;topics 导航保留(中文但导航有效)
|
|
50
|
+
if (rel.startsWith('site/en/')) return true
|
|
51
|
+
if (rel.startsWith('extra/')) return !isZhPair(rel)
|
|
52
|
+
if (rel === 'meta/topics.md') return true
|
|
53
|
+
return false
|
|
54
|
+
}
|
|
55
|
+
// 中文:site/ 下除 en/ 外全部;extra/ 下仅 *.zh.md;meta/topics.md
|
|
56
|
+
if (rel.startsWith('site/en/')) return false
|
|
57
|
+
if (rel.startsWith('site/')) return true
|
|
58
|
+
if (rel.startsWith('extra/')) return rel.endsWith('.zh.md')
|
|
59
|
+
if (rel === 'meta/topics.md') return true
|
|
60
|
+
return false
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** rel 是否为 extra 下成对的中文版(*.zh.md)——中文索引收 .zh.md,英文索引收无后缀原文。 */
|
|
64
|
+
function isZhPair(rel) {
|
|
65
|
+
return rel.endsWith('.zh.md')
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** 读文件并切行;读失败返回 null。 */
|
|
69
|
+
function readLines(absPath) {
|
|
70
|
+
try {
|
|
71
|
+
const st = statSync(absPath)
|
|
72
|
+
if (st.size > MAX_FILE_BYTES) return null
|
|
73
|
+
return readFileSync(absPath, 'utf8').split(/\r?\n/)
|
|
74
|
+
} catch {
|
|
75
|
+
return null
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 取文件首个 ATX 标题作为 title。 */
|
|
80
|
+
function firstHeading(lines) {
|
|
81
|
+
for (const line of lines) {
|
|
82
|
+
const m = /^#{1,6}\s+(.+)$/.exec(line)
|
|
83
|
+
if (m) return m[1].trim()
|
|
84
|
+
}
|
|
85
|
+
return ''
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* 构建索引。
|
|
90
|
+
* @param {string} kbDir 知识库根(插件内 kb/ 绝对路径)
|
|
91
|
+
* @param {'zh'|'en'} [lang] 中文索引(默认):site/ 非 en + extra/*.zh.md + topics;
|
|
92
|
+
* 英文索引:site/en/** + extra/ 无 .zh 后缀原文。
|
|
93
|
+
* @param {{warn:(...a:any[])=>void}} [log]
|
|
94
|
+
* @returns {{ kbDir: string, lang: string, files: Array<{rel:string, abs:string, title:string, lines:string[]}> }}
|
|
95
|
+
*/
|
|
96
|
+
export function buildIndex(kbDir, lang = 'zh', log) {
|
|
97
|
+
const warn = log?.warn ?? (() => {})
|
|
98
|
+
const files = []
|
|
99
|
+
const rels = listMarkdownFiles(kbDir, kbDir)
|
|
100
|
+
for (const rel of rels) {
|
|
101
|
+
if (!includeForLang(rel, lang)) continue
|
|
102
|
+
const lines = readLines(join(kbDir, ...rel.split('/')))
|
|
103
|
+
if (!lines) {
|
|
104
|
+
warn(`索引跳过(读取失败或过大):${rel}`)
|
|
105
|
+
continue
|
|
106
|
+
}
|
|
107
|
+
files.push({ rel, abs: join(kbDir, ...rel.split('/')), title: firstHeading(lines), lines })
|
|
108
|
+
}
|
|
109
|
+
// topics.md 永远排最前候选位(搜索无结果时兜底展示)
|
|
110
|
+
files.sort((a, b) => (a.rel === 'meta/topics.md' ? -1 : b.rel === 'meta/topics.md' ? 1 : a.rel < b.rel ? -1 : 1))
|
|
111
|
+
return { kbDir, lang, files }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** query 按空白分词 + 小写化;空结果返回 []。 */
|
|
115
|
+
function tokenize(query) {
|
|
116
|
+
return String(query ?? '')
|
|
117
|
+
.toLowerCase()
|
|
118
|
+
.split(/\s+/)
|
|
119
|
+
.map((t) => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')) // 转成正则安全串
|
|
120
|
+
.filter((t) => t.length > 0)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** 从主词派生标题/路径子词(ctx.tools.register → ctx/tools/register),用于路径与标题的弱匹配。 */
|
|
124
|
+
function derivePathTerms(terms) {
|
|
125
|
+
const out = []
|
|
126
|
+
for (const t of terms) {
|
|
127
|
+
for (const seg of t.split(/[._/\\-]+/)) {
|
|
128
|
+
if (seg.length >= 3 && !out.includes(seg)) out.push(seg)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
return out
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/** API 类文件(scope='api' 时只搜这些):签名/目录/缝合点文档。 */
|
|
135
|
+
const API_PATH_RE = /(tool-catalog|config-catalog|capability-seams|cordis-api\/|subsystems\/|api-gateway|agent-lifecycle|persistence-catalog)/
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 检索。
|
|
139
|
+
* @param {{kbDir:string, files:Array}} index buildIndex 产物
|
|
140
|
+
* @param {string} query 关键词(空白分隔多词);空或 'topics' 返回主题导航
|
|
141
|
+
* @param {{scope?:'all'|'api', maxResults?:number, maxSnippets?:number, renderChars?:number}} [opts]
|
|
142
|
+
*/
|
|
143
|
+
export function search(index, query, opts = {}) {
|
|
144
|
+
const maxResults = Math.min(Math.max(opts.maxResults ?? 8, 1), 20)
|
|
145
|
+
const maxSnippets = opts.maxSnippets ?? 3
|
|
146
|
+
const renderChars = opts.renderChars ?? 12000
|
|
147
|
+
const scope = opts.scope === 'api' ? 'api' : 'all'
|
|
148
|
+
|
|
149
|
+
const raw = String(query ?? '').trim()
|
|
150
|
+
if (!raw || raw.toLowerCase() === 'topics') {
|
|
151
|
+
return topicsDigest(index, renderChars)
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const terms = tokenize(raw)
|
|
155
|
+
if (!terms.length) return topicsDigest(index, renderChars)
|
|
156
|
+
const termRes = terms.map((t) => new RegExp(t, 'i'))
|
|
157
|
+
const pathTermRes = derivePathTerms(terms).map((t) => new RegExp(t, 'i'))
|
|
158
|
+
|
|
159
|
+
const scored = []
|
|
160
|
+
for (const f of index.files) {
|
|
161
|
+
if (scope === 'api' && !API_PATH_RE.test(f.rel)) continue
|
|
162
|
+
let score = 0
|
|
163
|
+
const hitLineNums = []
|
|
164
|
+
for (let i = 0; i < f.lines.length; i++) {
|
|
165
|
+
let lineHit = 0
|
|
166
|
+
for (let k = 0; k < termRes.length; k++) {
|
|
167
|
+
if (termRes[k].test(f.lines[i])) lineHit++
|
|
168
|
+
}
|
|
169
|
+
if (lineHit > 0) {
|
|
170
|
+
// 多词同现的行更强:词数平方和近似
|
|
171
|
+
score += lineHit * lineHit
|
|
172
|
+
if (hitLineNums.length < 50) hitLineNums.push(i + 1)
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (score === 0) continue
|
|
176
|
+
// 教程优先:入门/开发/指南路径下的文档在同等得分时更靠前
|
|
177
|
+
if (/^site\/(develop|guide)\//.test(f.rel)) score += 2
|
|
178
|
+
const titleLower = f.title.toLowerCase()
|
|
179
|
+
const relLower = f.rel.toLowerCase()
|
|
180
|
+
for (let k = 0; k < termRes.length; k++) {
|
|
181
|
+
if (termRes[k].test(titleLower)) score += 8
|
|
182
|
+
if (termRes[k].test(relLower)) score += 4
|
|
183
|
+
}
|
|
184
|
+
for (let k = 0; k < pathTermRes.length; k++) {
|
|
185
|
+
if (pathTermRes[k].test(titleLower)) score += 2
|
|
186
|
+
if (pathTermRes[k].test(relLower)) score += 3
|
|
187
|
+
}
|
|
188
|
+
scored.push({ file: f, score, hitLineNums })
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
scored.sort((a, b) => b.score - a.score || (a.file.rel < b.file.rel ? -1 : 1))
|
|
192
|
+
|
|
193
|
+
const files = []
|
|
194
|
+
let used = 0
|
|
195
|
+
for (const s of scored.slice(0, maxResults)) {
|
|
196
|
+
const f = s.file
|
|
197
|
+
const snippets = []
|
|
198
|
+
// 挑前 maxSnippets 个命中行,每个给前后各 2 行上下文
|
|
199
|
+
for (const ln of s.hitLineNums.slice(0, maxSnippets)) {
|
|
200
|
+
const from = Math.max(1, ln - 2)
|
|
201
|
+
const to = Math.min(f.lines.length, ln + 2)
|
|
202
|
+
const text = f.lines.slice(from - 1, to).join('\n')
|
|
203
|
+
snippets.push({ line: ln, from, text })
|
|
204
|
+
}
|
|
205
|
+
const entry = {
|
|
206
|
+
path: f.rel,
|
|
207
|
+
absPath: f.abs.replaceAll('\\', '/'),
|
|
208
|
+
title: f.title,
|
|
209
|
+
score: s.score,
|
|
210
|
+
hitLines: s.hitLineNums.slice(0, 12),
|
|
211
|
+
snippets,
|
|
212
|
+
}
|
|
213
|
+
// 预算控制:超过 renderChars 就不再多塞文件
|
|
214
|
+
const est = JSON.stringify(entry).length
|
|
215
|
+
if (used + est > renderChars && files.length > 0) break
|
|
216
|
+
used += est
|
|
217
|
+
files.push(entry)
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (!files.length) {
|
|
221
|
+
return {
|
|
222
|
+
kind: 'empty',
|
|
223
|
+
query: raw,
|
|
224
|
+
scope,
|
|
225
|
+
hint: `无命中。建议:① 换更短的关键词(如 "tool" "config" "event");② query 传 "topics" 看主题导航;③ 用文件工具 grep 知识库目录。`,
|
|
226
|
+
topicsPath: absOf(index, 'meta/topics.md'),
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return {
|
|
231
|
+
kind: 'hits',
|
|
232
|
+
query: raw,
|
|
233
|
+
scope,
|
|
234
|
+
truncatedFiles: scored.length > files.length ? scored.length - files.length : 0,
|
|
235
|
+
files,
|
|
236
|
+
hint: '用 read 工具按 absPath + 行号局部读取原文;大文件禁止整读。',
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** 主题导航摘要(query 空 / 'topics' / 无命中兜底)。 */
|
|
241
|
+
function topicsDigest(index, renderChars) {
|
|
242
|
+
const f = index.files.find((x) => x.rel === 'meta/topics.md')
|
|
243
|
+
if (!f) {
|
|
244
|
+
return {
|
|
245
|
+
kind: 'topics',
|
|
246
|
+
hint: '知识库缺 meta/topics.md;请直接用文件工具 grep 知识库目录。',
|
|
247
|
+
kbRoot: index.kbDir.replaceAll('\\', '/'),
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
return {
|
|
251
|
+
kind: 'topics',
|
|
252
|
+
topicsPath: f.abs.replaceAll('\\', '/'),
|
|
253
|
+
text: f.lines.join('\n').slice(0, renderChars),
|
|
254
|
+
hint: '按主题里的文件清单直接 read 对应文档。',
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function absOf(index, rel) {
|
|
259
|
+
const f = index.files.find((x) => x.rel === rel)
|
|
260
|
+
return f ? f.abs.replaceAll('\\', '/') : `${index.kbDir.replaceAll('\\', '/')}/${rel}`
|
|
261
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-dev-kb",
|
|
3
|
-
"description": "dsh 插件辅助:① dsh-plugin-dev-kb 技能 = DeepSeek Harness 官方文档站完整 Markdown 镜像(中英双语
|
|
4
|
-
"version": "1.
|
|
3
|
+
"description": "dsh 插件辅助:① dsh-plugin-dev-kb 技能 = DeepSeek Harness 官方文档站完整 Markdown 镜像(中英双语 192 页)+ 439 篇仓库补充开发参考 + 主题导航与 634 文件全量搜索索引;② dsh-dev-sandbox 技能 = 自建独立沙箱 profile 开发沙箱工作流(沙箱名由 agent 按任务自定),开发中的插件隔离运行,agent 自主启动/重启/冒烟验证,插件崩了不影响主力 dsh;③ dsh_kb_search 工具 = 知识库本地打分检索,一次调用返回命中文件 + 行号 + 上下文片段。零运行时依赖。",
|
|
4
|
+
"version": "1.6.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
package/scripts/selfcheck.mjs
CHANGED
|
@@ -22,6 +22,16 @@ for (const f of ["README.md", "LICENSE"]) {
|
|
|
22
22
|
if (!existsSync(join(root, f))) failures.push(`缺 ${f}`);
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
// 1.6.0:检索工具完整性
|
|
26
|
+
if (!existsSync(join(root, "lib", "kb-search.js"))) {
|
|
27
|
+
failures.push("缺 lib/kb-search.js(dsh_kb_search 工具实现)");
|
|
28
|
+
} else {
|
|
29
|
+
const idxMod = readFileSync(join(root, "lib", "index.js"), "utf8");
|
|
30
|
+
if (!idxMod.includes("dsh_kb_search")) failures.push("lib/index.js 未注册 dsh_kb_search");
|
|
31
|
+
const skill = readFileSync(join(root, "skills", "dsh-plugin-dev-kb.md"), "utf8");
|
|
32
|
+
if (!skill.includes("dsh_kb_search")) failures.push("技能正文未提及 dsh_kb_search");
|
|
33
|
+
}
|
|
34
|
+
|
|
25
35
|
if (failures.length) {
|
|
26
36
|
for (const f of failures) console.error(`[FAIL] ${f}`);
|
|
27
37
|
console.error(`${failures.length} 项失败`);
|
|
@@ -1,24 +1,25 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: dsh-dev-sandbox
|
|
3
|
-
description: dsh
|
|
3
|
+
description: dsh 插件开发沙箱:agent 自建独立沙箱 profile 隔离开发中的插件,插件崩了只崩沙箱、绝不影响主力 dsh。所有启动 / 重启 / 看日志 / 冒烟验证都由 agent 自己完成,不要求用户重启。开发或调试 dsh 插件、需要验证插件能否加载、插件改完要重启验证时加载本技能。
|
|
4
4
|
whenToUse: 开发/调试 dsh 插件需要启动、重启、验证插件加载,或插件疑似把 dsh 搞挂时
|
|
5
5
|
---
|
|
6
6
|
# dsh 插件开发沙箱(dsh-dev-sandbox)
|
|
7
7
|
|
|
8
8
|
核心原则:**开发中的插件永远装在独立沙箱 profile 里,不进主力 profile**。
|
|
9
|
-
插件写崩 → 只有沙箱起不来,主力 dsh
|
|
9
|
+
插件写崩 → 只有沙箱起不来,主力 dsh 照常运行,agent 还活着就能自己看日志、改代码、重启沙箱、再验证——闭环不需要用户参与。
|
|
10
10
|
|
|
11
|
-
## 1. profile
|
|
11
|
+
## 1. 沙箱 profile:名字由 agent 自定
|
|
12
12
|
|
|
13
|
-
-
|
|
14
|
-
-
|
|
13
|
+
- **不要使用固定名字**。每次建沙箱时按任务起一个可辨识的名字,如 `sb-<插件名或简称>`(`sb-image-tools`、`sb-table-zoom`),目录为 `$DSH_HOME/profiles/<名字>/`(Windows 默认 `%USERPROFILE%\.dsh\profiles\<名字>\`)。
|
|
14
|
+
- 建之前先列 `~/.dsh/profiles/`:**不得与现有 profile 重名**(尤其 `web` / `desktop` 这类主力 profile,绝不复用、绝不写入);重名就换一个名字。
|
|
15
|
+
- 一次性验证可用完即删(删 profile 目录即可,沙箱与主力完全隔离);跨天持续开发的插件保留同名沙箱复用。
|
|
15
16
|
- 沙箱与主力 profile 完全隔离:插件、patch、会话、设置各一份;provider/模型配置可从主力 profile 的 `cordis.patch.yml` 拷贝 provider 段过来(用编辑工具,UTF-8 无 BOM,勿用 PowerShell 重定向写)。
|
|
16
17
|
|
|
17
18
|
## 2. 沙箱不存在时:初始化(一次)
|
|
18
19
|
|
|
19
20
|
```powershell
|
|
20
21
|
# 版本与目标 dsh 对齐:先用主力安装的实际版本号替换下面的 <ver>(插件自身 peerDependencies 也要覆盖它)
|
|
21
|
-
npx -y "@deepseek-ai/dsh@<ver>" plugin --profile
|
|
22
|
+
npx -y "@deepseek-ai/dsh@<ver>" plugin --profile <沙箱名> add file:/绝对路径/插件目录
|
|
22
23
|
```
|
|
23
24
|
|
|
24
25
|
- `add` 走兼容闸门:peerDependencies 不覆盖当前 dsh 版本会整体拒绝并回滚——这不是网络问题,改 peer 或按提示 `allow-version` 豁免。
|
|
@@ -29,30 +30,32 @@ npx -y "@deepseek-ai/dsh@<ver>" plugin --profile dev add file:/绝对路径/插
|
|
|
29
30
|
|
|
30
31
|
```powershell
|
|
31
32
|
# 后台启动(run_in_background),端口避开主力;日志里找 token 行与报错
|
|
32
|
-
npx -y "@deepseek-ai/dsh@<ver>" web --profile
|
|
33
|
+
npx -y "@deepseek-ai/dsh@<ver>" web --profile <沙箱名> --port <空闲端口>
|
|
33
34
|
```
|
|
34
35
|
|
|
35
|
-
-
|
|
36
|
+
- 端口自选一个空闲高位端口(如 3081 起,被占就换下一个);记住本次用的端口,后续重启/探活都用它。
|
|
37
|
+
- 启动日志含 `dsh web: http://127.0.0.1:<port>/?token=…`(token 每次启动轮换,用它开 GUI 验证 UI 类插件)。
|
|
36
38
|
- **逐条看 stderr 的 `dsh: skipping profile bundle "X"`**——被跳过 = 插件静默失效(不崩但功能没了),立即修,别当没看见。
|
|
37
39
|
- **自主重启循环**(改代码 / 改配置后):
|
|
38
|
-
1. 找进程:`Get-NetTCPConnection -LocalPort
|
|
40
|
+
1. 找进程:`Get-NetTCPConnection -LocalPort <port> -State Listen | Select OwningProcess`;
|
|
39
41
|
2. 核对 PID 后 `Stop-Process -Id <pid> -Force`;
|
|
40
42
|
3. 重新后台启动,读日志验证(无 skipping 警告、无崩溃栈、端口起来)。
|
|
41
|
-
- **红线:沙箱操作只允许碰沙箱端口/profile;绝不 Stop 主力 dsh
|
|
43
|
+
- **红线:沙箱操作只允许碰沙箱端口/profile;绝不 Stop 主力 dsh 进程、绝不改主力 profile 下的东西。**
|
|
42
44
|
|
|
43
45
|
## 4. 冒烟验证(不起 GUI)
|
|
44
46
|
|
|
45
47
|
一次性 headless 跑同 profile,验证插件能加载、工具能注册:
|
|
46
48
|
|
|
47
49
|
```powershell
|
|
48
|
-
npx -y "@deepseek-ai/dsh@<ver>" --profile
|
|
50
|
+
npx -y "@deepseek-ai/dsh@<ver>" --profile <沙箱名> "列出你当前可用的工具,并调用 <插件工具名> 做一次最小调用"
|
|
49
51
|
```
|
|
50
52
|
|
|
51
53
|
启动即崩(exit 非 0 / 打印异常栈)= 插件破坏了 profile 启动,直接读栈修代码,主 dsh 无恙。
|
|
52
54
|
|
|
53
|
-
## 5.
|
|
55
|
+
## 5. 收尾
|
|
54
56
|
|
|
55
|
-
插件在沙箱里稳定后,才走正常流程挪进主力 profile:同步版本与 peerDependencies → 改主力 profile → `pnpm install` → 自检 → 由用户重启主力 dsh(主力重启永远由用户决定)。发布流程遵循该插件仓库自己的发布规范/CI。
|
|
57
|
+
- 插件在沙箱里稳定后,才走正常流程挪进主力 profile:同步版本与 peerDependencies → 改主力 profile → `pnpm install` → 自检 → 由用户重启主力 dsh(主力重启永远由用户决定)。发布流程遵循该插件仓库自己的发布规范/CI。
|
|
58
|
+
- 沙箱功成身退:删除对应 profile 目录即完全清理,不留残留。
|
|
56
59
|
|
|
57
60
|
## 6. 常见坑速查
|
|
58
61
|
|
|
@@ -35,11 +35,11 @@ kb/
|
|
|
35
35
|
└── README.md 知识库总览与更新方法
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
## 2.
|
|
38
|
+
## 2. 使用流程(从快到慢,优先用工具)
|
|
39
39
|
|
|
40
|
-
1.
|
|
41
|
-
2.
|
|
42
|
-
3. **按 URL 反查**:已知线上页面 URL 时,在 `
|
|
40
|
+
1. **dsh_kb_search 工具检索(首选)**:调用本插件的 `dsh_kb_search` 工具,一次拿到命中文件路径 + 行号 + 上下文片段,再用 read 工具按行号局部读原文。参数:`query`(关键词,空白分词;空或 `"topics"` 返回主题导航)、`scope`(`api` = 只搜 API 签名/目录类文档,查 `ctx.*` API 用)、`lang`(默认 zh)、`maxResults`。
|
|
41
|
+
2. **文件工具兜底**(工具不可用时):读 `{{KB_ROOT}}/meta/topics.md` 按主题找文件;或对 `{{KB_ROOT}}/meta/search-index.json` / `{{KB_ROOT}}/site/` 用 grep 定位。
|
|
42
|
+
3. **按 URL 反查**:已知线上页面 URL 时,在 `{{KB_ROOT}}/INDEX.md` 查对应本地文件。
|
|
43
43
|
|
|
44
44
|
## 3. 阅读原则
|
|
45
45
|
|