@dsh-workbench/core 0.1.18 → 0.2.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/client.js +2241 -955
- package/dist/client/client.body.js +2241 -955
- package/dist/client/index.js +2242 -956
- package/dist/host/index.js +1840 -332
- package/dist/types/app/client.d.ts +225 -81
- package/dist/types/app/layouts/__tests__/async-feedback.test.d.ts +1 -0
- package/dist/types/app/layouts/market/__tests__/add-module-stages.test.d.ts +1 -0
- package/dist/types/app/layouts/market/add-module-text.d.ts +16 -1
- package/dist/types/app/layouts/market/use-add-module.d.ts +3 -8
- package/dist/types/app/layouts/market/use-module-versions.d.ts +2 -1
- package/dist/types/app/layouts/rightbar/MarketView.d.ts +7 -1
- package/dist/types/app/layouts/rightbar/ModulesPage.d.ts +4 -2
- package/dist/types/app/layouts/rightbar/PanelGroupRows.d.ts +10 -0
- package/dist/types/app/layouts/rightbar/PanelModules.d.ts +2 -2
- package/dist/types/app/layouts/rightbar/PanelRowControls.d.ts +13 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-catalog.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-dots.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-row-icon.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-section-icon.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-state-rules.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/market-workbench.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-dismiss.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-drag-target.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-plugin-rows.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-scope-ui.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/__tests__/panel-write.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/board-selection.d.ts +20 -0
- package/dist/types/app/layouts/rightbar/dsh-borrow.d.ts +15 -0
- package/dist/types/app/layouts/rightbar/dsh-borrow.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/dsh-surface.d.ts +11 -0
- package/dist/types/app/layouts/rightbar/dsh-surface.test.d.ts +1 -0
- package/dist/types/app/layouts/rightbar/market-act.d.ts +21 -0
- package/dist/types/app/layouts/rightbar/market-installed-mock.d.ts +9 -0
- package/dist/types/app/layouts/rightbar/market-mock-types.d.ts +58 -0
- package/dist/types/app/layouts/rightbar/market-mock.d.ts +2 -43
- package/dist/types/app/layouts/rightbar/market-plugin-rows.d.ts +3 -1
- package/dist/types/app/layouts/rightbar/market-row-icon.d.ts +7 -0
- package/dist/types/app/layouts/rightbar/market-row-types.d.ts +37 -0
- package/dist/types/app/layouts/rightbar/market-rows.d.ts +13 -53
- package/dist/types/app/layouts/rightbar/market-section-icon.d.ts +3 -0
- package/dist/types/app/layouts/rightbar/market-section.d.ts +18 -0
- package/dist/types/app/layouts/rightbar/market-shared-rows.d.ts +21 -0
- package/dist/types/app/layouts/rightbar/market-view-text.d.ts +7 -1
- package/dist/types/app/layouts/rightbar/market-workbench-row.d.ts +5 -0
- package/dist/types/app/layouts/rightbar/panel-dismiss.d.ts +2 -0
- package/dist/types/app/layouts/rightbar/panel-mock.d.ts +3 -2
- package/dist/types/app/layouts/rightbar/panel-write.d.ts +15 -0
- package/dist/types/app/layouts/rightbar/use-drag-listeners.d.ts +6 -0
- package/dist/types/app/layouts/rightbar/use-panels.d.ts +15 -1
- package/dist/types/app/layouts/sidebar/__tests__/session-row-ui.test.d.ts +1 -0
- package/dist/types/app/units/registry.d.ts +5 -2
- package/dist/types/modules/panels/__tests__/diy-harness.d.ts +19 -0
- package/dist/types/modules/panels/__tests__/diy-report.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/diy-tools-health.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/diy-tools-panel.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-panel-market-parity.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-panel.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/plugin-tool-units.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/scope-machine.test.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/scope-support.d.ts +51 -0
- package/dist/types/modules/panels/model/config.d.ts +23 -3
- package/dist/types/modules/panels/model/diy-report.d.ts +63 -0
- package/dist/types/modules/panels/model/market-dispatch.d.ts +4 -0
- package/dist/types/modules/panels/model/market-registry.d.ts +12 -0
- package/dist/types/modules/panels/model/market-registry.test.d.ts +1 -0
- package/dist/types/modules/panels/model/market-types.d.ts +1 -0
- package/dist/types/modules/panels/model/market.d.ts +3 -2
- package/dist/types/modules/panels/model/plugin-panel.d.ts +3 -0
- package/dist/types/modules/panels/model/prefs.d.ts +3 -1
- package/dist/types/modules/panels/model/service.d.ts +5 -1
- package/dist/types/shared/api/bundle-install.d.ts +15 -1
- package/dist/types/shared/api/host/__tests__/approval-note-injection.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/panel-state-route.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-catalog.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-login-hint.test.d.ts +1 -0
- package/dist/types/shared/api/host/__tests__/plugin-tools.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-inspect-kind.test.d.ts +1 -0
- package/dist/types/shared/api/host/bundle-routes-self.test.d.ts +1 -0
- package/dist/types/shared/api/host/installed-module.d.ts +12 -0
- package/dist/types/shared/api/host/mount-units.d.ts +1 -0
- package/dist/types/shared/api/host/plugin-catalog.d.ts +60 -0
- package/dist/types/shared/api/host/plugin-login-hint.d.ts +2 -0
- package/dist/types/shared/api/host/plugin-tools.d.ts +10 -0
- package/dist/types/shared/api/host/plugin-units.d.ts +47 -0
- package/dist/types/shared/api/host/unit-registry.d.ts +9 -0
- package/dist/types/shared/api/unit-manifests.d.ts +1 -0
- package/dist/types/shared/components/PanelFoot.d.ts +1 -1
- package/dist/types/shared/hooks/panel-state-report.d.ts +13 -0
- package/dist/types/shared/hooks/panel-state-report.test.d.ts +1 -0
- package/docs/HANDOFF.md +7 -0
- package/package.json +2 -2
- package/preset/diy.patch.yml +5 -26
- package/skills/workbench-module-diy/REVIEW.md +22 -0
- package/skills/workbench-module-diy/SKILL.md +3 -1
- package/skills/workbench-module-diy/TOOLING.md +2 -0
|
@@ -10,6 +10,8 @@ export type UnitSource = {
|
|
|
10
10
|
readonly pkg: string;
|
|
11
11
|
} | {
|
|
12
12
|
readonly kind: 'owned';
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'plugin';
|
|
13
15
|
};
|
|
14
16
|
export interface HostUnit {
|
|
15
17
|
readonly unit: string;
|
|
@@ -21,14 +23,21 @@ export interface UnitDeclarationSource {
|
|
|
21
23
|
readonly pkg: string;
|
|
22
24
|
readonly declaration: unknown;
|
|
23
25
|
}
|
|
26
|
+
export declare function installableUnitsOf(registry: {
|
|
27
|
+
all(): readonly HostUnit[];
|
|
28
|
+
}): readonly string[];
|
|
24
29
|
export interface UnitRegistry {
|
|
25
30
|
register(face: UnitModuleFace): () => void;
|
|
26
31
|
registerOwned(face: UnitOwnedFace): () => void;
|
|
32
|
+
registerPlugin(face: UnitOwnedFace): () => void;
|
|
27
33
|
all(): readonly HostUnit[];
|
|
28
34
|
owned(): readonly string[];
|
|
29
35
|
refusals(): readonly UnitRefusal[];
|
|
30
36
|
manifests(): readonly UnitManifest[];
|
|
31
37
|
subscribe(listener: () => void): () => void;
|
|
38
|
+
fail(unit: string, reason: string): void;
|
|
39
|
+
recovered(unit: string): void;
|
|
40
|
+
failureOf(unit: string): string | undefined;
|
|
32
41
|
}
|
|
33
42
|
export declare function createUnitRegistry(options: {
|
|
34
43
|
declarationOf: (unit: string) => UnitDeclarationSource | undefined;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { PanelCardError, PanelCardPhase } from '@dsh-workbench/shared';
|
|
2
|
+
export declare function unitOfRoute(route: string): string | null;
|
|
3
|
+
export declare function setPanelWorkspace(path: string | null): void;
|
|
4
|
+
export declare function reportPanelRead(input: {
|
|
5
|
+
readonly unit: string;
|
|
6
|
+
readonly route: string;
|
|
7
|
+
readonly phase: PanelCardPhase;
|
|
8
|
+
readonly error?: PanelCardError | undefined;
|
|
9
|
+
}): void;
|
|
10
|
+
export declare function reportPanelCrash(input: {
|
|
11
|
+
readonly unit: string;
|
|
12
|
+
readonly reason: string;
|
|
13
|
+
}): void;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/docs/HANDOFF.md
CHANGED
|
@@ -256,6 +256,13 @@ pnpm release:dry # 同上,但 publish 只 dry-run:零上传,结束
|
|
|
256
256
|
| `shared` ↔ 外壳/模块 | 依赖写的是 `workspace:*`,发布时会被改写成**当时那个版本号** → `shared` 必须先发;`shared` 升版后,用到它的包要重新发一次才会带上新范围 |
|
|
257
257
|
| `module-tools` ↔ `init` 模板 | 模板里 `@dsh-workbench/core` / `module-tools` 的范围要跟着本仓版本走,否则 `init` 生成的是过期范围 —— **现在没有任何判据检查它**(上一版 release 脚本查过,换成现在这份之后这条没了),升外壳次版本时记得一起改 |
|
|
258
258
|
|
|
259
|
+
> **`0.2.0` 给模块作者的一处变更**(很小,但旧写法会红):`check:cards` 现在要求"清单说没有卡片"
|
|
260
|
+
> 与"产物里登记没登记卡片"**两处说法一致** —— 声明写 `"card": null` 的模块**不要再挂占位卡**
|
|
261
|
+
> (`UnitClientFace.card` 自 `0.2.0` 起可选,见 [ADR-0038 §6.20](../docs/adr/0038-module-declaration-protocol.md))。
|
|
262
|
+
> 有卡片的模块零迁移。反过来,`card: null` 的模块在**旧外壳/旧工具链**上仍按旧口径被要求交卡片 ⇒
|
|
263
|
+
> 模块仓把依赖提到 `^0.2.0` 要与外壳发版**同一个窗口**做(先发 `0.2.0`,模块仓再删那张卡片函数、
|
|
264
|
+
> 提范围、跑模块门禁)。
|
|
265
|
+
|
|
259
266
|
### 中断了怎么续跑(发布是可中断的)
|
|
260
267
|
|
|
261
268
|
脚本把现场写进 `.release-state.json`(已进 `.gitignore`)。断了就:
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"access": "public"
|
|
88
88
|
},
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"@dsh-workbench/shared": "0.1
|
|
90
|
+
"@dsh-workbench/shared": "0.2.1"
|
|
91
91
|
},
|
|
92
92
|
"scripts": {
|
|
93
93
|
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
package/preset/diy.patch.yml
CHANGED
|
@@ -16,12 +16,13 @@
|
|
|
16
16
|
- id: persona
|
|
17
17
|
name: '@deepseek-ai/dsh-persona'
|
|
18
18
|
config:
|
|
19
|
-
suffix: Your working directory is {{cwd}}.
|
|
20
19
|
prefix: You are a coding agent powered by the {{model}} model.
|
|
21
20
|
- id: agent-instructions
|
|
22
21
|
name: '@deepseek-ai/dsh-agent-instructions'
|
|
23
22
|
config:
|
|
24
23
|
maxBytes: 65536
|
|
24
|
+
- id: time-context
|
|
25
|
+
name: '@deepseek-ai/dsh-time-context'
|
|
25
26
|
- id: tool-bash
|
|
26
27
|
name: '@deepseek-ai/dsh-tool-bash'
|
|
27
28
|
disabled: !!js process.platform === 'win32'
|
|
@@ -36,6 +37,8 @@
|
|
|
36
37
|
sampleOverCapGlobResults: false
|
|
37
38
|
- id: tool-jobs
|
|
38
39
|
name: '@deepseek-ai/dsh-tool-jobs'
|
|
40
|
+
- id: tool-schedule
|
|
41
|
+
name: '@deepseek-ai/dsh-tool-schedule'
|
|
39
42
|
- id: command-goal
|
|
40
43
|
name: '@deepseek-ai/dsh-command-goal'
|
|
41
44
|
- id: tool-goal
|
|
@@ -50,7 +53,7 @@
|
|
|
50
53
|
name: '@deepseek-ai/dsh-plan-mode'
|
|
51
54
|
config:
|
|
52
55
|
section: |
|
|
53
|
-
You are in plan mode. Stay in plan mode until
|
|
56
|
+
You are in plan mode. Stay in plan mode until the user approves your plan through exit_plan_mode or switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode.
|
|
54
57
|
|
|
55
58
|
Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery.
|
|
56
59
|
|
|
@@ -94,41 +97,17 @@
|
|
|
94
97
|
provider: spawn
|
|
95
98
|
toolName: subagent
|
|
96
99
|
modelSelectionSettings: true
|
|
97
|
-
backgroundMode: continuable
|
|
98
100
|
- id: tool-subagent-fork
|
|
99
101
|
name: '@deepseek-ai/dsh-tool-subagent'
|
|
100
102
|
config:
|
|
101
103
|
provider: fork
|
|
102
104
|
toolName: subagent_fork
|
|
103
|
-
backgroundMode: continuable
|
|
104
|
-
- id: tool-subagent-codex
|
|
105
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
106
|
-
disabled: true
|
|
107
|
-
config:
|
|
108
|
-
provider: codex
|
|
109
|
-
toolName: subagent_codex
|
|
110
|
-
backgroundMode: one-shot
|
|
111
|
-
maxDepth: provider-managed
|
|
112
|
-
- id: tool-subagent-claude-code
|
|
113
|
-
name: '@deepseek-ai/dsh-tool-subagent'
|
|
114
|
-
disabled: true
|
|
115
|
-
config:
|
|
116
|
-
provider: claude-code
|
|
117
|
-
toolName: subagent_claude_code
|
|
118
|
-
backgroundMode: one-shot
|
|
119
|
-
maxDepth: provider-managed
|
|
120
105
|
- id: workflow-ptc
|
|
121
106
|
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
122
107
|
config:
|
|
123
108
|
provider: spawn
|
|
124
109
|
- id: tool-workflow
|
|
125
110
|
name: '@deepseek-ai/dsh-tool-workflow'
|
|
126
|
-
- id: tool-ralph
|
|
127
|
-
name: '@deepseek-ai/dsh-tool-ralph'
|
|
128
|
-
disabled: true
|
|
129
|
-
config:
|
|
130
|
-
subagentProvider: spawn
|
|
131
|
-
maxRounds: 64
|
|
132
111
|
- id: tool-ask-user
|
|
133
112
|
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
134
113
|
- id: tool-todo
|
|
@@ -31,3 +31,25 @@
|
|
|
31
31
|
1. **装上去没反应** → 先问 `workbench_diy`(`action=status, unit=…`),别自己拼。[WIRING.md](WIRING.md) §3
|
|
32
32
|
2. **改完没热换** → 宿主侧是秒级热换的;客户端侧刷新一下页面。[WIRING.md](WIRING.md) §4
|
|
33
33
|
3. **"再装一遍"** → 日常迭代只重建、不重装。[WIRING.md](WIRING.md) §3
|
|
34
|
+
|
|
35
|
+
## 用户说"卡片不对"时:四步取证
|
|
36
|
+
|
|
37
|
+
他说"一片空白 / 一直转圈 / 数字不对"时,**先取证再改代码**,四步按顺序走;前一步有结论就停在那儿修:
|
|
38
|
+
|
|
39
|
+
1. **`workbench_diy`(`action=panel`)** —— 这个工作区面板上现在是什么样:哪几张卡、每张最后一次取数
|
|
40
|
+
是读到了还是读不到(带宿主回的码与原文)、有没有崩过(原因 + 次数)。有答案就别往下走。
|
|
41
|
+
2. **`workbench_diy`(`action=status, unit=…`)** —— 看"起得来:不是 —— …"那一行:模块起不来时
|
|
42
|
+
原因就在那里(装配抛的错 / 声明被拒的原文)。这一行说"都在生效"才继续第 3 步。
|
|
43
|
+
3. **自己把那条读跑一遍**:卡片画的就是模块自己那条宿主路由的返回值 ——
|
|
44
|
+
`curl '<工作台地址>/workbench/api/<unit>/<route>'`(地址就是你起工作台那个,默认 `http://127.0.0.1:3081`)。
|
|
45
|
+
接口回对了而卡片不对,问题在渲染那一侧;接口就回错,问题在宿主半侧(回头照着 [DATASOURCE.md](DATASOURCE.md) 重探一次)。
|
|
46
|
+
4. **最后才看用户那一页**:`opencli browser <会话> console`(**不带任何过滤** —— 第一次查用过滤会得到
|
|
47
|
+
假的"没有消息",本仓实测踩过)、`state`(这一页是什么、有哪些可点的)、`screenshot <路径>`(截一张自己看)。
|
|
48
|
+
宿主侧热换 / 装载的报错在**起工作台那个终端**里,不在浏览器里。
|
|
49
|
+
|
|
50
|
+
两条纪律:**别让用户截图或替你转述报错**(第 1 步就是干这个的);**截图与 console 只用来定位,不用来验收** ——
|
|
51
|
+
验收仍是他对着三张表走「打开哪 → 看什么 → 点什么 → 应该看到什么」。
|
|
52
|
+
|
|
53
|
+
面板运行态是**用户那一页自己报上来的**(页面关了就停在最后一次,带时间与新鲜度),所以第 1 步没有内容时,
|
|
54
|
+
先让他把工作台切到工坊那个工作区、刷新一次页面再看。
|
|
55
|
+
|
|
@@ -30,7 +30,8 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
30
30
|
就问工具:`workbench_diy`(`action=status, unit=<单元 id>`)会把这几件事一次说清并给出下一步 ——
|
|
31
31
|
**别自己拼,也别再装一遍**;已经装过只是想上板,用 `action=open`。
|
|
32
32
|
7. **验收** —— 逐条对着三张表走:「打开哪 → 看什么 → 点什么 → 应该看到什么」。
|
|
33
|
-
|
|
33
|
+
他说哪不对,**先取证再改**:`workbench_diy`(`action=panel`)一次看清面板上每张卡是读到了、读不到还是崩了
|
|
34
|
+
(四步取证见 [REVIEW.md](REVIEW.md));改完让他看**同一处**;不给他看 diff、日志、代码。
|
|
34
35
|
逐条过他都说对了,再 `dsh-wb-module design accept` 把验收打勾(**没验完不许打**)。
|
|
35
36
|
|
|
36
37
|
## 每个问题都带推荐答案
|
|
@@ -52,6 +53,7 @@ description: 在工作台里凭需求造一个模块(面板上的一块内容
|
|
|
52
53
|
| 落点、文件形状、命令、两个面 | [BUILD.md](BUILD.md) |
|
|
53
54
|
| 依赖、会被拦的写法、装、热换、发布 | [WIRING.md](WIRING.md) |
|
|
54
55
|
| 门禁红了、装上去没反应、改完没热换 | [REVIEW.md](REVIEW.md) |
|
|
56
|
+
| 用户说"卡片不对 / 一片空白 / 一直转圈"(四步取证) | [REVIEW.md](REVIEW.md) 那一节 · `workbench_diy`(`action=panel`) |
|
|
55
57
|
| 哪条纪律已经固化成工具、哪些还留给 agent | [TOOLING.md](TOOLING.md) |
|
|
56
58
|
|
|
57
59
|
## 找不到一件事的时候,按这个顺序找
|
|
@@ -15,6 +15,8 @@
|
|
|
15
15
|
| 十一条判据([REVIEW.md](REVIEW.md)) | `dsh-wb-module check`(`--only <判据>` 单跑;`--only kit` 列这份外壳的 kit 成员) |
|
|
16
16
|
| 落点、文件形状、四个脚本([BUILD.md](BUILD.md) §1–§2) | `dsh-wb-module init --template diy`(骨架带一条真读盘的数据源)+ `scripts/smoke.mjs` |
|
|
17
17
|
| 装、上板、不重装([WIRING.md](WIRING.md) §3) | `workbench_diy`(`install` / `open` / `status`)+ `workbench_panels` |
|
|
18
|
+
| 它到底起得来没(装配抛的错 / 声明被拒的原文) | `workbench_diy`(`status` 的"起得来"那一行,见 [REVIEW.md](REVIEW.md) 四步取证的第二步) |
|
|
19
|
+
| 面板上现在是什么样(每张卡读到没 / 崩没崩) | `workbench_diy`(`panel`:浏览器半侧上报,宿主按 unit 留最新一条;见 [ADR-0042](../../../../docs/adr/0042-panel-runtime-state-is-reported-to-the-host.md)) |
|
|
18
20
|
| 空着的格就是还没定的事([DESIGN.md](DESIGN.md) §5) | `design.json` + `design check`(点名叫出空格)/ `render` / `accept` |
|
|
19
21
|
| 发货前的清单([WIRING.md](WIRING.md) §5) | `dsh-wb-module publish`(门禁 + 打包清单 `files` 漏项,只 dry run) |
|
|
20
22
|
| 干净机器上的第一脚([SKILL.md](SKILL.md) 第 4 步) | 启动器自愈骨架 + PATH 上的 `dsh-wb-module` |
|