@dsh-workbench/core 0.1.10 → 0.1.12
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/bin/dsh-workbench +23 -0
- package/client.js +80 -23
- package/cordis.patch.yml +11 -3
- package/dist/client/client.body.js +80 -23
- package/dist/client/index.js +80 -23
- package/dist/host/index.js +150 -178
- package/dist/types/app/client.d.ts +9 -3
- package/dist/types/app/layouts/rightbar/BoardEmpty.d.ts +2 -1
- package/dist/types/app/layouts/rightbar/Rightbar.d.ts +1 -0
- package/dist/types/modules/panels/__tests__/approval.test.d.ts +1 -0
- package/dist/types/modules/panels/model/approval.d.ts +2 -0
- package/dist/types/shared/api/diy.d.ts +3 -0
- package/dist/types/shared/api/diy.test.d.ts +1 -0
- package/dist/types/shared/api/host/diy.d.ts +3 -0
- package/dist/types/shared/api/host/diy.test.d.ts +1 -0
- package/package.json +13 -5
- package/preset/diy.patch.yml +156 -0
- package/scripts/seed-profile.sh +116 -0
- package/skills/workbench-module-diy/BUILD.md +179 -0
- package/skills/workbench-module-diy/DATASOURCE.md +54 -0
- package/skills/workbench-module-diy/DESIGN.md +82 -0
- package/skills/workbench-module-diy/LINKAGE.md +113 -0
- package/skills/workbench-module-diy/PANEL.md +124 -0
- package/skills/workbench-module-diy/REVIEW.md +31 -0
- package/skills/workbench-module-diy/SKILL.md +57 -0
- package/skills/workbench-module-diy/STEPS.md +93 -0
- package/skills/workbench-module-diy/TOOLS.md +112 -0
|
@@ -21,6 +21,7 @@ export interface RightbarActions {
|
|
|
21
21
|
openPanel: (step: PanelStep) => void;
|
|
22
22
|
goToPanel: (index: number) => void;
|
|
23
23
|
openMarket: () => void;
|
|
24
|
+
buildModule: () => void;
|
|
24
25
|
units: ClientUnitRegistry;
|
|
25
26
|
}
|
|
26
27
|
export declare function Rightbar(props: RightbarProps): JSX.Element;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/core",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "个人工作台:Agent 工作台外壳(左栏 + 常驻对话 + 自研右侧数据面板)。",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"icon": "./icon.svg",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"bin": {
|
|
23
23
|
"dsh-workbench": "./bin/dsh-workbench"
|
|
24
24
|
},
|
|
25
|
-
"//files": "npm 的兜底是 .gitignore —— 而 dist/ 在 .gitignore 里,不显式列出就会**漏掉宿主入口**(exports['.'] 指向它)。发出去之前用 npm pack --dry-run 数一遍 dist/ 有几条。",
|
|
25
|
+
"//files": "npm 的兜底是 .gitignore —— 而 dist/ 在 .gitignore 里,不显式列出就会**漏掉宿主入口**(exports['.'] 指向它)。发出去之前用 npm pack --dry-run 数一遍 dist/ 有几条。skills/ 是 DIY 模式挂给 agent 的指引(经 skill-filesystem 的 customSkillDirs 挂载),漏了不报错、只是 DIY 的 agent 什么都不知道 —— selfcheck 有一条判据盯着它。",
|
|
26
26
|
"files": [
|
|
27
27
|
"dist",
|
|
28
28
|
"client.js",
|
|
@@ -30,6 +30,8 @@
|
|
|
30
30
|
"icon.svg",
|
|
31
31
|
"bin",
|
|
32
32
|
"profile",
|
|
33
|
+
"skills",
|
|
34
|
+
"preset",
|
|
33
35
|
"scripts/seed-profile.sh",
|
|
34
36
|
"README.md",
|
|
35
37
|
"docs/HANDOFF.md",
|
|
@@ -40,16 +42,22 @@
|
|
|
40
42
|
"description": "个人工作台"
|
|
41
43
|
},
|
|
42
44
|
"//scripts": "包内不跑 check:docs —— 那份文档管理块住在仓库根的 docs/ARCHITECTURE.md,由根 `pnpm run check` 用 --config 指到本包的 arch.config.mjs 校验。prepublishOnly 走 -w 跑根那一整套。",
|
|
45
|
+
"//dsh.bundle.patch": "一份 preset 就是一个补丁层:它 insert 一行 @deepseek-ai/dsh-agent-preset,config.plugins 是整表插件清单。diy.patch.yml 是**生成的**(scripts/gen-diy-preset.mjs,从 dsh 出厂那份 standard.patch.yml 派生),selfcheck 有一条判据盯着它有没有过期。",
|
|
43
46
|
"dsh": {
|
|
44
47
|
"bundle": {
|
|
45
|
-
"patch":
|
|
48
|
+
"patch": [
|
|
49
|
+
"./cordis.patch.yml",
|
|
50
|
+
"./preset/diy.patch.yml"
|
|
51
|
+
]
|
|
46
52
|
},
|
|
47
53
|
"client": {
|
|
48
54
|
"platform": "web",
|
|
49
55
|
"immediately": true,
|
|
50
56
|
"inject": [
|
|
51
57
|
"@deepseek-ai/dsh-client-ui-slots",
|
|
52
|
-
"@deepseek-ai/dsh-client-locale"
|
|
58
|
+
"@deepseek-ai/dsh-client-locale",
|
|
59
|
+
"@deepseek-ai/dsh-api-gateway",
|
|
60
|
+
"@deepseek-ai/dsh-api-session-controller"
|
|
53
61
|
]
|
|
54
62
|
}
|
|
55
63
|
},
|
|
@@ -78,7 +86,7 @@
|
|
|
78
86
|
"access": "public"
|
|
79
87
|
},
|
|
80
88
|
"dependencies": {
|
|
81
|
-
"@dsh-workbench/shared": "0.1.
|
|
89
|
+
"@dsh-workbench/shared": "0.1.12"
|
|
82
90
|
},
|
|
83
91
|
"scripts": {
|
|
84
92
|
"build": "node scripts/build.mjs && node scripts/build-types.mjs",
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
# Agent preset workbench-diy:DIY 模式。**这个文件是生成的**,别手改 ——
|
|
2
|
+
# 改 packages/workbench/scripts/gen-diy-preset.mjs 再重跑它。
|
|
3
|
+
#
|
|
4
|
+
# 它从 dsh 出厂那份 standard.patch.yml 派生(整表 plugins 归 dsh 管,手抄会漂),
|
|
5
|
+
# 只叠加三处差分:DIY skill 目录、tool-cordis、plugin-manager 打开。
|
|
6
|
+
# 派生逻辑与差分理由写在生成器开头。
|
|
7
|
+
- insert:
|
|
8
|
+
- id: preset-workbench-diy
|
|
9
|
+
name: '@deepseek-ai/dsh-agent-preset'
|
|
10
|
+
config:
|
|
11
|
+
id: workbench-diy
|
|
12
|
+
name: 工作台 DIY
|
|
13
|
+
description: 用大白话说你要在面板上看到什么、数据从哪来 —— agent 在工作台的工坊里把模块造出来,装上就上板。不用写代码。
|
|
14
|
+
order: 9
|
|
15
|
+
plugins:
|
|
16
|
+
- id: persona
|
|
17
|
+
name: '@deepseek-ai/dsh-persona'
|
|
18
|
+
config:
|
|
19
|
+
suffix: Your working directory is {{cwd}}.
|
|
20
|
+
prefix: You are a coding agent powered by the {{model}} model.
|
|
21
|
+
- id: agent-instructions
|
|
22
|
+
name: '@deepseek-ai/dsh-agent-instructions'
|
|
23
|
+
config:
|
|
24
|
+
maxBytes: 65536
|
|
25
|
+
- id: tool-bash
|
|
26
|
+
name: '@deepseek-ai/dsh-tool-bash'
|
|
27
|
+
disabled: !!js process.platform === 'win32'
|
|
28
|
+
- id: tool-pwsh
|
|
29
|
+
name: '@deepseek-ai/dsh-tool-pwsh'
|
|
30
|
+
disabled: !!js process.platform !== 'win32'
|
|
31
|
+
- id: tool-fs
|
|
32
|
+
name: '@deepseek-ai/dsh-tool-fs'
|
|
33
|
+
- id: tool-fs-search
|
|
34
|
+
name: '@deepseek-ai/dsh-tool-fs-search'
|
|
35
|
+
config:
|
|
36
|
+
sampleOverCapGlobResults: false
|
|
37
|
+
- id: tool-jobs
|
|
38
|
+
name: '@deepseek-ai/dsh-tool-jobs'
|
|
39
|
+
- id: command-goal
|
|
40
|
+
name: '@deepseek-ai/dsh-command-goal'
|
|
41
|
+
- id: tool-goal
|
|
42
|
+
name: '@deepseek-ai/dsh-tool-goal'
|
|
43
|
+
- id: planning
|
|
44
|
+
name: cordis:group
|
|
45
|
+
group: true
|
|
46
|
+
isolate:
|
|
47
|
+
planMode: true
|
|
48
|
+
config:
|
|
49
|
+
- id: plan-mode
|
|
50
|
+
name: '@deepseek-ai/dsh-plan-mode'
|
|
51
|
+
config:
|
|
52
|
+
section: |
|
|
53
|
+
You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user 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
|
+
|
|
55
|
+
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
|
+
|
|
57
|
+
The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode.
|
|
58
|
+
|
|
59
|
+
Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out.
|
|
60
|
+
|
|
61
|
+
Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions.
|
|
62
|
+
|
|
63
|
+
When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation.
|
|
64
|
+
- id: compaction
|
|
65
|
+
name: cordis:group
|
|
66
|
+
group: true
|
|
67
|
+
isolate:
|
|
68
|
+
compaction: true
|
|
69
|
+
toolResultPruner: true
|
|
70
|
+
config:
|
|
71
|
+
- id: compaction-basic
|
|
72
|
+
name: '@deepseek-ai/dsh-compaction-basic'
|
|
73
|
+
- id: command-compact
|
|
74
|
+
name: '@deepseek-ai/dsh-command-compact'
|
|
75
|
+
- id: tool-result-pruner
|
|
76
|
+
name: '@deepseek-ai/dsh-compaction-tool-result-pruner'
|
|
77
|
+
config:
|
|
78
|
+
thresholdChars: 8192
|
|
79
|
+
headChars: 4096
|
|
80
|
+
tailChars: 1024
|
|
81
|
+
- id: delegation
|
|
82
|
+
name: cordis:group
|
|
83
|
+
group: true
|
|
84
|
+
isolate:
|
|
85
|
+
workflowEngine: true
|
|
86
|
+
config:
|
|
87
|
+
- id: tool-subagent-control
|
|
88
|
+
name: '@deepseek-ai/dsh-tool-subagent-control'
|
|
89
|
+
- id: tool-subagent-list-agents
|
|
90
|
+
name: '@deepseek-ai/dsh-tool-subagent-control/list-agents'
|
|
91
|
+
- id: tool-subagent
|
|
92
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
93
|
+
config:
|
|
94
|
+
provider: spawn
|
|
95
|
+
toolName: subagent
|
|
96
|
+
modelSelectionSettings: true
|
|
97
|
+
backgroundMode: continuable
|
|
98
|
+
- id: tool-subagent-fork
|
|
99
|
+
name: '@deepseek-ai/dsh-tool-subagent'
|
|
100
|
+
config:
|
|
101
|
+
provider: fork
|
|
102
|
+
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
|
+
- id: workflow-ptc
|
|
121
|
+
name: '@deepseek-ai/dsh-workflow-ptc'
|
|
122
|
+
config:
|
|
123
|
+
provider: spawn
|
|
124
|
+
- id: tool-workflow
|
|
125
|
+
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
|
+
- id: tool-ask-user
|
|
133
|
+
name: '@deepseek-ai/dsh-tool-ask-user'
|
|
134
|
+
- id: tool-todo
|
|
135
|
+
name: '@deepseek-ai/dsh-tool-todo'
|
|
136
|
+
config:
|
|
137
|
+
allowParallelInProgress: true
|
|
138
|
+
- id: tool-web
|
|
139
|
+
name: '@deepseek-ai/dsh-tool-web'
|
|
140
|
+
config:
|
|
141
|
+
fetch: true
|
|
142
|
+
searchTimeoutMs: 60000
|
|
143
|
+
- id: present
|
|
144
|
+
name: '@deepseek-ai/dsh-tool-present'
|
|
145
|
+
- id: tool-plugin-manager
|
|
146
|
+
name: '@deepseek-ai/dsh-plugin-manager/tools'
|
|
147
|
+
disabled: !!js "!ctx.get('profileContext')"
|
|
148
|
+
- id: tool-cordis
|
|
149
|
+
name: '@deepseek-ai/dsh-tool-cordis'
|
|
150
|
+
- id: skill-filesystem
|
|
151
|
+
name: '@deepseek-ai/dsh-skill-filesystem'
|
|
152
|
+
config:
|
|
153
|
+
customSkillDirs:
|
|
154
|
+
- !!js process.getBuiltinModule('node:path').join(process.getBuiltinModule('node:path').dirname(process.getBuiltinModule('node:module').createRequire(baseUrl).resolve('@dsh-workbench/core/package.json')), 'skills')
|
|
155
|
+
- id: tool-skill
|
|
156
|
+
name: '@deepseek-ai/dsh-tool-skill'
|
package/scripts/seed-profile.sh
CHANGED
|
@@ -168,6 +168,122 @@ else
|
|
|
168
168
|
echo "→ 依赖已齐备,跳过安装"
|
|
169
169
|
fi
|
|
170
170
|
|
|
171
|
+
# ── 第 6 步:建 DIY 工坊 ────────────────────────────────────────────────
|
|
172
|
+
#
|
|
173
|
+
# 工坊是 DIY 模式的落点:agent 在那里造模块(一个模块一个目录),
|
|
174
|
+
# 而 **DIY 会话的 cwd 就是它** —— dsh 的沙箱把"会话 cwd"当作 workspace-write 的边界,
|
|
175
|
+
# 所以源码写在工坊里天然放行,不需要给用户开 danger-full-access。
|
|
176
|
+
#
|
|
177
|
+
# 为什么它是一个 pnpm workspace 而不是随便一个目录:模块要构建就得有
|
|
178
|
+
# module-tools / core / shared / react / typescript / vitest;把它做成 workspace 根,
|
|
179
|
+
# 依赖只装一次,每个模块只是 packages/ 下多一个目录。而且 module-tools 的发现逻辑
|
|
180
|
+
# 认 `pnpm-workspace.yaml`,于是模块单独跑 check 时能找到同仓的外壳(kit / standalone 两条判据
|
|
181
|
+
# 才不会静默跳过 —— 跳过等于没验)。
|
|
182
|
+
#
|
|
183
|
+
# 两个身份同 bin/dsh-workbench:**仓库副本**把三个 @dsh-workbench/* 用 link 指进本仓
|
|
184
|
+
# (改工具链立刻生效),**安装包副本**从源上按版本装。
|
|
185
|
+
DIY_DIR="$DSH_HOME/workbench/diy"
|
|
186
|
+
mkdir -p "$DIY_DIR/packages"
|
|
187
|
+
|
|
188
|
+
if [ -n "$GIT_TOP" ]; then
|
|
189
|
+
CORE_SPEC="link:$REPO_DIR"
|
|
190
|
+
TOOLS_SPEC="link:$(cd "$REPO_DIR/../module-tools" && pwd)"
|
|
191
|
+
SHARED_SPEC="link:$(cd "$REPO_DIR/../shared" && pwd)"
|
|
192
|
+
else
|
|
193
|
+
CORE_SPEC="@dsh-workbench/core@^$(node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo 0)"
|
|
194
|
+
TOOLS_SPEC="@dsh-workbench/module-tools@^$(node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo 0)"
|
|
195
|
+
SHARED_SPEC="@dsh-workbench/shared@^$(node -p "require('$REPO_DIR/package.json').version" 2>/dev/null || echo 0)"
|
|
196
|
+
fi
|
|
197
|
+
|
|
198
|
+
# 工坊必须**钉住自己那一代 pnpm**(与仓库根钉的同一个)。
|
|
199
|
+
#
|
|
200
|
+
# 不钉的话它用用户机器上那个 pnpm —— 实测这台是 11.21.0,而仓库钉的是 10.32.1,
|
|
201
|
+
# 而**两代的设置住址与键名都不一样**:`store-dir` 在 pnpm 10 从 `.npmrc` 读、
|
|
202
|
+
# 在 pnpm 11 只认 `pnpm-workspace.yaml` 里的 `storeDir`;放行依赖构建的键
|
|
203
|
+
# 在 pnpm 10 是 `onlyBuiltDependencies`、11 是 `allowBuilds`(11 还会往
|
|
204
|
+
# pnpm-workspace.yaml 里写一行 `esbuild: set this to true or false` 占位)。
|
|
205
|
+
# 结果是同一份工坊配置在这台机器上对、在另一台上错,而且错法都是静默或半静默的。
|
|
206
|
+
# 钉了之后两边一致,且这一整轮验证过的就是这一代。
|
|
207
|
+
PNPM_PIN=""
|
|
208
|
+
if [ -n "$GIT_TOP" ] && [ -f "$GIT_TOP/package.json" ]; then
|
|
209
|
+
PNPM_PIN="$(node -p "require('$GIT_TOP/package.json').packageManager || ''" 2>/dev/null || true)"
|
|
210
|
+
fi
|
|
211
|
+
PNPM_PIN="${PNPM_PIN:-pnpm@10.32.1}"
|
|
212
|
+
|
|
213
|
+
if [ -f "$DIY_DIR/package.json" ]; then
|
|
214
|
+
echo "→ DIY 工坊已存在,跳过建骨架(要重来就先删掉 $DIY_DIR)"
|
|
215
|
+
else
|
|
216
|
+
echo "→ 建 DIY 工坊 $DIY_DIR"
|
|
217
|
+
cat >"$DIY_DIR/package.json" <<EOF
|
|
218
|
+
{
|
|
219
|
+
"name": "dsh-workbench-diy",
|
|
220
|
+
"version": "0.0.0",
|
|
221
|
+
"private": true,
|
|
222
|
+
"description": "DIY 工坊:工作台模块在这里造。这个目录由 dsh-workbench 的 setup 生成,手改会被覆盖。",
|
|
223
|
+
"packageManager": "$PNPM_PIN",
|
|
224
|
+
"devDependencies": {
|
|
225
|
+
"@dsh-workbench/core": "$CORE_SPEC",
|
|
226
|
+
"@dsh-workbench/module-tools": "$TOOLS_SPEC",
|
|
227
|
+
"@dsh-workbench/shared": "$SHARED_SPEC",
|
|
228
|
+
"@types/node": "^24.19.0",
|
|
229
|
+
"@types/react": "^19.3.0",
|
|
230
|
+
"esbuild": "^0.28.2",
|
|
231
|
+
"react": "^19.3.0",
|
|
232
|
+
"typescript": "5.9",
|
|
233
|
+
"vitest": "^5.0.2"
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
EOF
|
|
237
|
+
cat >"$DIY_DIR/pnpm-workspace.yaml" <<'EOF'
|
|
238
|
+
packages:
|
|
239
|
+
- "packages/*"
|
|
240
|
+
|
|
241
|
+
# esbuild 的构建脚本要放行:pnpm 默认不跑依赖的 postinstall,而模块的构建就是 esbuild。
|
|
242
|
+
# 不放行的话每次 install 都会打一行
|
|
243
|
+
# `[ERR_PNPM_IGNORED_BUILDS] Ignored build scripts: esbuild@0.28.2` —— 看着像失败,
|
|
244
|
+
# 其实退出码是 0(实测踩过)。
|
|
245
|
+
# 钉在 10.32.1 之后这个键名是对的(同一代 pnpm 也从 package.json 的 `pnpm` 段读它,
|
|
246
|
+
# 仓库根就是这么放的);换 pnpm 11 会变成 `allowBuilds`,所以上面那句「钉住 pnpm」是前提。
|
|
247
|
+
onlyBuiltDependencies:
|
|
248
|
+
- esbuild
|
|
249
|
+
EOF
|
|
250
|
+
cat >"$DIY_DIR/.npmrc" <<EOF
|
|
251
|
+
# 工坊自己的 store 与 cache。
|
|
252
|
+
#
|
|
253
|
+
# 为什么必须指进来:agent 的 shell 跑在**会话沙箱**里,可写根就是会话 cwd(= 这个目录)。
|
|
254
|
+
# 而 pnpm 默认的 store(例如 ~/Library/pnpm/store/v10)与 npm 默认的 cache(~/.npm)
|
|
255
|
+
# 都在可写根**之外** —— 不指进来的话,agent 在会话里跑一次 pnpm install 会被沙箱拒(EPERM),
|
|
256
|
+
# 而"给模块加一个依赖"只有这一条路。setup 时的安装由人跑,不受沙箱限制,但用的也是这份配置,
|
|
257
|
+
# 于是装一次之后 agent 那边也能继续装。
|
|
258
|
+
store-dir=$DIY_DIR/.pnpm-store
|
|
259
|
+
cache-dir=$DIY_DIR/.npm-cache
|
|
260
|
+
EOF
|
|
261
|
+
cat >"$DIY_DIR/README.md" <<'EOF'
|
|
262
|
+
# DIY 工坊
|
|
263
|
+
|
|
264
|
+
工作台模块在这里造。**一个模块一个目录**,都住在 `packages/` 下。
|
|
265
|
+
|
|
266
|
+
- 这个目录是「DIY 会话」的工作目录 —— 在对话里让 agent 造模块,它就写在这里。
|
|
267
|
+
- 依赖已经装好(`@dsh-workbench/core` / `module-tools` / `shared` / react / typescript / vitest)。
|
|
268
|
+
- 造一个新模块:`dsh-wb-module init <名字> --dir packages/module-<名字>`
|
|
269
|
+
- 每个模块自己的四件事:`pnpm build` / `pnpm typecheck` / `pnpm test` / `pnpm selfcheck`,
|
|
270
|
+
外加 `dsh-wb-module check` 的十条判据。
|
|
271
|
+
|
|
272
|
+
不要手改这个目录下的 `package.json` / `pnpm-workspace.yaml` / `.npmrc`:
|
|
273
|
+
它们是 `dsh-workbench setup` 生成的,模块要加依赖就加在**模块自己**的 `package.json` 里。
|
|
274
|
+
EOF
|
|
275
|
+
fi
|
|
276
|
+
|
|
277
|
+
if [ -d "$DIY_DIR/node_modules" ]; then
|
|
278
|
+
echo "→ 工坊依赖已装,跳过"
|
|
279
|
+
elif ! command -v pnpm >/dev/null 2>&1; then
|
|
280
|
+
echo "→ ⚠️ PATH 上没有 pnpm,工坊依赖没装。装好 pnpm 后跑一次:" >&2
|
|
281
|
+
echo " cd $DIY_DIR && pnpm install" >&2
|
|
282
|
+
else
|
|
283
|
+
echo "→ 装工坊依赖(第一次会下载 react / typescript / vitest,几百 MB,之后不再下)"
|
|
284
|
+
(cd "$DIY_DIR" && pnpm install 2>&1 | tail -6)
|
|
285
|
+
fi
|
|
286
|
+
|
|
171
287
|
# 装 bundle 的目标:开发态是这个仓库(link),安装包态是源上那个包。
|
|
172
288
|
# 判据是「git 顶层在不在」—— npm pack 的产物里不含 .git(见 package.json 的 files)。
|
|
173
289
|
# ⚠️ 不能判 `$REPO_DIR/.git`:workspace 之后 .git 在仓库根(同 bin/dsh-workbench)。
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# 实现参考(DIY 域):把模块造成一个独立包,装进工坊
|
|
2
|
+
|
|
3
|
+
本文只管"怎么落"。设计口径看 [DESIGN.md](DESIGN.md) 与 [STEPS.md](STEPS.md);
|
|
4
|
+
门禁红了看 [REVIEW.md](REVIEW.md)。
|
|
5
|
+
|
|
6
|
+
## 0 一个模块就是一个独立的 dsh 插件包
|
|
7
|
+
|
|
8
|
+
它不是工作台源码的一部分,**不认识工作台源码**:它把自己插进 dsh 的 profile,
|
|
9
|
+
外壳通过服务名 `workbenchUnits` 收下它。所以"造模块"只发生在一个目录里,不动外壳一个字节。
|
|
10
|
+
|
|
11
|
+
它同时是两半:
|
|
12
|
+
|
|
13
|
+
| 半 | 文件 | 跑在哪 | 干什么 |
|
|
14
|
+
|---|---|---|---|
|
|
15
|
+
| 宿主半侧 | `src/host.ts` → `dist/host.js` | Node(dsh 宿主进程) | 注册服务、路由、agent 工具、长任务 |
|
|
16
|
+
| 浏览器半侧 | `src/client.tsx` → `client.js` | 页面里 | 画那张卡片、注册审批明细渲染器 |
|
|
17
|
+
|
|
18
|
+
两侧不许互相 import。它们只通过外壳交出来的服务名相接。
|
|
19
|
+
|
|
20
|
+
## 1 落点:工坊
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
$DSH_HOME/workbench/diy/ ← 工坊(一个 pnpm workspace,依赖已经装好)
|
|
24
|
+
package.json pnpm-workspace.yaml .npmrc node_modules/
|
|
25
|
+
packages/
|
|
26
|
+
module-<unit>/ ← 你要造的模块住这儿
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
一个模块一个目录,一个目录一个包。**不要**把源码写到别处,也不要去动工坊根上的配置。
|
|
30
|
+
|
|
31
|
+
## 2 从模板改,不从零写
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
dsh-wb-module init <unit> --dir <工坊>/packages/module-<unit> --description "<一句话>"
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`<unit>` 是小写短横线标识(如 `my-thing`),它同时是单元 id、包名后缀、卡片样式前缀。
|
|
38
|
+
**它必须全局唯一** —— 和工作台里其它模块、和外壳里已有的单元都不能撞;撞了装上去当场抛。
|
|
39
|
+
|
|
40
|
+
生成的骨架是**能直接跑的**(十条判据 + 三个脚本开箱全绿;门禁还在,模板改坏了当场报)。
|
|
41
|
+
|
|
42
|
+
**两个模板,按需要挑**(加 `--template <名字>`):
|
|
43
|
+
|
|
44
|
+
| 模板 | 是什么 | 什么时候用 |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| `minimal`(不写就是它) | 空壳:卡壳、文案、测试都接好了,但**没有数据源** | 你已经清楚要读什么、想从零写 |
|
|
47
|
+
| `diy` | 一张**读当前项目的样板卡**:宿主侧一条真读目录的只读路由、卡片三态、`?wbMock=1` 三档夹具 | 你要照着改出一个"真能出数据"的最小版本 —— **建议先用它** |
|
|
48
|
+
|
|
49
|
+
⚠️ `diy` 那份是**给别人照抄的**,所以它自己也必须开箱全绿 —— 门禁会**两个模板各 init 一遍、各跑一遍全套**。
|
|
50
|
+
|
|
51
|
+
十一个文件各有其职:
|
|
52
|
+
|
|
53
|
+
| 文件 | 职责 |
|
|
54
|
+
|---|---|
|
|
55
|
+
| `package.json` | 自述清单:包名、`dsh.bundle.patch`、`dsh.client`、`dsh.workbench.unit/faces`、四个脚本、peer 依赖 |
|
|
56
|
+
| `cordis.patch.yml` | 把自己插进 profile 的那一行 |
|
|
57
|
+
| `src/host.ts` | 宿主面:`manifest` + `mount()`(路由 / 工具) |
|
|
58
|
+
| `src/client.tsx` | 浏览器面:`manifest` + `card` |
|
|
59
|
+
| `src/messages.ts` | zh / en 文案;键必须带 `<unit>.` 前缀,两种语言同构 |
|
|
60
|
+
| `src/plugin.test.ts` | 单测:两侧都把自己的面注册进了外壳 |
|
|
61
|
+
| `vitest.config.ts` | 测试配置(`@/modules/<unit>/` 的别名) |
|
|
62
|
+
| `scripts/smoke.mjs` | `selfcheck`:把 `client.js` 当浏览器加载器跑一遍,看卡片真进没进外壳 |
|
|
63
|
+
| `tsconfig.json` | `paths` 里 `@/modules/<unit>/*` → `./src/*`,值必须是**相对路径** |
|
|
64
|
+
| `types/globals.d.ts` | 经典 JSX 运行时的全局类型垫片 |
|
|
65
|
+
| `README.md` | 这个模块自己的说明 |
|
|
66
|
+
|
|
67
|
+
改哪一个:**画什么改 `src/client.tsx`,文案改 `src/messages.ts`**,需要能力再动 `src/host.ts`。
|
|
68
|
+
模板已经把卡片套好了外壳的卡壳(`kit.components.Card`)、三态与测试;你只改里面。
|
|
69
|
+
|
|
70
|
+
## 3 三个命令 + 四个脚本
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
dsh-wb-module build # 出 client.js 与 dist/host.js
|
|
74
|
+
dsh-wb-module check # 十条判据,全跑
|
|
75
|
+
dsh-wb-module check --only cards # 单跑一条(contracts / bundles / source / primitives /
|
|
76
|
+
# kit / cards / standalone / markers / alias / style)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`package.json` 里那四个脚本都要留着(契约判据会查):
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
pnpm build # 构建
|
|
83
|
+
pnpm typecheck # tsc --noEmit
|
|
84
|
+
pnpm test # vitest
|
|
85
|
+
pnpm selfcheck # 构建 + 把 client.js 当加载器跑一遍
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**自己的门禁自己过,红了别抛给用户。** 十条判据在 [REVIEW.md](REVIEW.md) 里逐条翻了人话。
|
|
89
|
+
|
|
90
|
+
## 4 两个面的形状
|
|
91
|
+
|
|
92
|
+
宿主面(`src/host.ts`):
|
|
93
|
+
|
|
94
|
+
```ts
|
|
95
|
+
export const name = '<包名>';
|
|
96
|
+
export const inject = ['workbenchUnits'];
|
|
97
|
+
|
|
98
|
+
export function apply(ctx) {
|
|
99
|
+
const units = ctx.get('workbenchUnits');
|
|
100
|
+
if (units === undefined) return; // 外壳没交这个服务:报一声,别静默
|
|
101
|
+
ctx.effect(() => units.register({ manifest, mount }), '<unit>: 注册');
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
`mount()` 返回 `{ routes, registerTools, prefs? }`:
|
|
106
|
+
`routes` 是交给外壳去挂 `/workbench/api/<路由>` 的只读投影(只许 GET);
|
|
107
|
+
`registerTools` 交出这个模块的 agent 工具。**路由的键必须住自己那一格**(`<unit>` 或 `<unit>/…`),
|
|
108
|
+
否则装配期直接抛;只读路由要表达"出错"时回 `{ ok: false, error: { code, message, hint? } }`(HTTP 仍是 200,外壳原样转发)。
|
|
109
|
+
|
|
110
|
+
浏览器面(`src/client.tsx`):`units.registerUnit({ manifest, messages, card })`。
|
|
111
|
+
`card` 拿到 `{ t, world, refreshToken, onRefresh, onSummary }`(`world.current.workspace` 就是当前项目),
|
|
112
|
+
返回一棵元素树。**三态用外壳给的组件,别自己画**:
|
|
113
|
+
加载 → `kit.components.SkeletonRows`;空 → `kit.components.EmptyState`(不带按钮);
|
|
114
|
+
出错 → `kit.components.BlockerState` + `kit.components.blockerOfError(error, t)`。
|
|
115
|
+
⚠️ **`idle` 是独立的一格**(没工作区、或取数被 `enabled: false` 关掉时),不许画成"空" ——
|
|
116
|
+
"空"和"还没开始"是两件事,说错了他会去查他根本没配的东西。
|
|
117
|
+
|
|
118
|
+
**卡片必须在拿不到数据时也画得出来**(第一次读、卡在审批、平台没登录)。
|
|
119
|
+
空数据下抛异常的卡片 = 用户眼里的"卡片一直不出来"。这是 `check:cards` 守的那条。
|
|
120
|
+
|
|
121
|
+
## 5 依赖从哪拿
|
|
122
|
+
|
|
123
|
+
| 要什么 | 从哪拿 |
|
|
124
|
+
|---|---|
|
|
125
|
+
| 宿主侧要用的纯函数与常量(`param`、平台解析、工作区存储…) | `@dsh-workbench/shared/host`(写进 `dependencies`) |
|
|
126
|
+
| 浏览器侧要的一切(hooks、组件、图标、文案工具) | `@dsh-workbench/core/client` 的 `kit` |
|
|
127
|
+
| 类型 | `import type` 随便用(编译时擦除,不会进产物) |
|
|
128
|
+
| 外壳本体 | `@dsh-workbench/core`(peer 依赖,装的时候要比版本) |
|
|
129
|
+
|
|
130
|
+
⚠️ **浏览器侧不许 `import` `@dsh-workbench/shared` 的运行值**(两个入口都不行):
|
|
131
|
+
它**不在**客户端构建的 external 默认表里(`module-tools/src/build.mjs`),
|
|
132
|
+
import 运行值会被**打进你的包**(第二份实例),而且浏览器运行时也解析不到它。
|
|
133
|
+
宿主侧正相反:`@dsh-workbench/shared` 与 `/host` 都在 external 里,直接 import 才对。
|
|
134
|
+
|
|
135
|
+
**单例只能有一份**:kit 里的 hooks 与容器组件**不许被复制进模块包**
|
|
136
|
+
(各打一份的症状不是报错,而是"agent 改完面板不自己刷新")。它们只能从 `kit` 拿。
|
|
137
|
+
|
|
138
|
+
## 6 四条会被门禁拦住的写法
|
|
139
|
+
|
|
140
|
+
| 别这么写 | 为什么 |
|
|
141
|
+
|---|---|
|
|
142
|
+
| 从 DOM + CSS 手搓一个 dsh 已经有的东西(按钮、弹窗、输入框、图标) | 重画就是第二份材质与交互(悬停、禁用、焦点环、色板),迟早跟 dsh 漂。**先从 `kit` 拿**;kit 里没有再自己写,并在 spec 里记理由 |
|
|
143
|
+
| `import … from '@deepseek-ai/dsh-client-ui-primitives'` | 模块**不直接** import dsh 的包;dsh 原语由外壳经 `kit` 交给你。直接引会被判红(那是第二份实例) |
|
|
144
|
+
| `import … from '@/shared/…'`、`../../workbench/src/…` | 装你的人手上没有工作台的源码。`@/` 只许指**你自己**的 `src/` |
|
|
145
|
+
| 在公开包里写内网地址 / 内网域名 / 企业内部名 | 门禁会扫出来并指到行 |
|
|
146
|
+
|
|
147
|
+
另外两条不是"被拦",是纪律:**样式自带前缀 `wb-<unit>-`**(外壳的 `wb-*` 是全局类名,覆盖它等于两份规则打架);
|
|
148
|
+
**`src/**` 里零注释** —— 要说的写进 `README.md` 或 spec。
|
|
149
|
+
|
|
150
|
+
## 7 装上,看一眼
|
|
151
|
+
|
|
152
|
+
模块造好、门禁绿了,**不要自己往 profile 里装**:工作台的 profile 目录不在你的可写根里。
|
|
153
|
+
把这件事交给用户点一下「装上看看」—— 那一跳在宿主侧完成(装 + 顺手在面板里打开它)。
|
|
154
|
+
|
|
155
|
+
两个必须记住的后果:
|
|
156
|
+
|
|
157
|
+
- **那一跳一辈子只该走一次。** 包名一旦在 profile 里,再装一次只会得到"要重启"
|
|
158
|
+
(同名重装一定要求重启,而且那一支连重载都不做)。
|
|
159
|
+
- **装 ≠ 上板**:面板默认是空的,得在面板配置里把它打开 —— 这就是那一跳顺手做掉的第二件事。
|
|
160
|
+
|
|
161
|
+
## 8 改一行,看到它变
|
|
162
|
+
|
|
163
|
+
装完之后**不要再装一遍**。改源码 → `dsh-wb-module build` → 用户面板上那张卡自己变,
|
|
164
|
+
不重启、不刷新。为了让它成立:
|
|
165
|
+
|
|
166
|
+
- **构建必须原地覆写产物**(现在是)。别为了"原子写"改成临时文件 + rename ——
|
|
167
|
+
宿主的监听只认 `change`,不认 `rename`,改了会**静默**不热换。
|
|
168
|
+
- **改 `cordis.patch.yml`、改 `package.json` 里的 `dsh.client` / `exports["./client"]` 需要重启。**
|
|
169
|
+
这些是"模块怎么被装上"的声明,不是模块的内容。
|
|
170
|
+
- 重启操作只有在极少数情况下才需要用户做。真需要时,说清**为什么**、**要重启哪个**。
|
|
171
|
+
|
|
172
|
+
## 9 发布
|
|
173
|
+
|
|
174
|
+
**工坊里的模块一律 `private: true`,不发到任何源。** 模型写的代码在用户的机器上执行,
|
|
175
|
+
这是他自己要的东西、他自己的机器;把 `private` 摘掉是另一个决定,不属于 DIY。
|
|
176
|
+
|
|
177
|
+
真要给别人用:把 `private` 去掉,并**显式**写上发布目标
|
|
178
|
+
(`publishConfig.access: "public"`,或指向内网源的 `publishConfig.registry`)——
|
|
179
|
+
两样都不写等于打算发到公共源,门禁会拦。
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# 数据从哪来:干净机器上先用 opencli 探,再把它固化成一条读
|
|
2
|
+
|
|
3
|
+
用户往往只说"我要看发布情况""我想看缺陷看板" —— 他不知道那套系统叫什么、接口在哪、要什么参数。
|
|
4
|
+
干净机器上你也没有现成的 skill 可抄。所以这一步是**探查**,不是查手册。
|
|
5
|
+
|
|
6
|
+
## 先判:这是 DIY 域还是工程域
|
|
7
|
+
|
|
8
|
+
| 情况 | 判 |
|
|
9
|
+
|---|---|
|
|
10
|
+
| 他已经在浏览器里登录过那套系统 | **DIY 域** —— 用 opencli 复用他的登录去探 |
|
|
11
|
+
| 要**新凭据**(新账号 / 密钥 / token) | **工程域** —— 停下来,说清要谁去配什么 |
|
|
12
|
+
| 要起新服务 / 新进程 / 加新依赖 | **工程域** |
|
|
13
|
+
| 数据在本地文件 / 一条已有命令 / 一个不要凭据的 GET 上 | **DIY 域** —— 直接造 |
|
|
14
|
+
|
|
15
|
+
判的是「**要不要新凭据**」,不是「要不要接新平台」。复用已有登录的探查,正是把一大类需求留在 DIY 域的原因。
|
|
16
|
+
|
|
17
|
+
## 四步
|
|
18
|
+
|
|
19
|
+
1. **问出他在看什么(用他的词)** —— "你平时是在哪个网页 / 哪个软件里看这个的?" 记下他说的入口。
|
|
20
|
+
**不要**问"你们用 argocd 还是 jenkins" —— 他多半不知道,也答不上来。
|
|
21
|
+
2. **探** —— 用 `opencli`。**它随工作台预装**:隔离运行时在 `$DSH_HOME/workbench/opencli/`,
|
|
22
|
+
启动体检自己装,**不碰**用户本机的 `~/.opencli`(工作台已经把它挂成命令,`bin/opencli-isolated` 是那层壳)。
|
|
23
|
+
目标是找到**承载数据的那一处**:列表页本身,或它背后的那一个 XHR。记下:命令 / URL、需要的参数、凭据从哪来。
|
|
24
|
+
- 数据通常来自 **opencli 的某个插件**(`opencli <plugin> …`)—— 先看这台机器上哪些插件能用,
|
|
25
|
+
**别自己拼接口**。
|
|
26
|
+
- 隔离运行时没装好时它会自己说"opencli 的隔离运行时还没装好" —— 把体检那屏的原话转给用户,
|
|
27
|
+
别硬编 URL 猜(那正是"看起来能用、其实读不到"的来源)。
|
|
28
|
+
3. **试一条最小读** —— 先证明"能拿到",再写模块:
|
|
29
|
+
- 拿得到结构化输出 → 记下字段形状
|
|
30
|
+
- 要登录但已过期 → 记下"未登录"长什么样
|
|
31
|
+
- 超时 / 被墙 → 记下这两种失败的形状
|
|
32
|
+
这一步的产出是**一条可重复的命令或请求**,不是"我探过了"。
|
|
33
|
+
4. **固化进模块** —— 把它写进模块的**宿主半侧**(一条命令,或只读路由里的一个 HTTP GET)。
|
|
34
|
+
之后卡片只依赖**这一条**;渲染怎么改都不动它。
|
|
35
|
+
|
|
36
|
+
`opencli` 是**侦察兵,不是日常通道**:探明之后,日常读必须是一条固定的、便宜的、能解释的调用。
|
|
37
|
+
|
|
38
|
+
## 固化时一并写掉三态
|
|
39
|
+
|
|
40
|
+
拿不到数据是常态(第一次读、登录过期、站点改版、接口超时)。卡片**必须**画得出来:
|
|
41
|
+
|
|
42
|
+
| 情况 | 卡片说什么 |
|
|
43
|
+
|---|---|
|
|
44
|
+
| 没登录 / 凭据过期 | "去 XX 登录一下"(可点,指到他平时那个入口) |
|
|
45
|
+
| 超时 / 网络不通 | "这次没读到,稍后重试" + 重试 |
|
|
46
|
+
| 站点改版、字段没了 | "读到的形状和预期不一样"(别显示空白,也别显示 0) |
|
|
47
|
+
|
|
48
|
+
空数据下抛异常的卡片 = 用户眼里的"卡片一直不出来"。这条 `check:cards` 会拦(见 [REVIEW.md](REVIEW.md))。
|
|
49
|
+
|
|
50
|
+
## 你自己的门禁会查
|
|
51
|
+
|
|
52
|
+
- 数据源写在**宿主半侧**,卡片只消费它的返回;
|
|
53
|
+
- 只读路由出错时回 `{ ok: false, error: { code, message, hint? } }`(HTTP 仍是 200,外壳原样转发);
|
|
54
|
+
- `?wbMock=1` 下三档夹具都要能出(正常 / 空 / 出错)—— 这是你自己验收"三态"的快捷方式。
|