dsh-plugin-toolkit 0.9.6 → 0.10.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 +30 -0
- package/README.md +8 -0
- package/README.zh.md +4 -0
- package/client.js +235 -31
- package/package.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,33 @@
|
|
|
1
|
+
## 0.10.0
|
|
2
|
+
|
|
3
|
+
- **The checker is docked under the conversation.** It used to live only in the
|
|
4
|
+
right sidebar, behind a guide list nobody opens; now it sits above the input
|
|
5
|
+
bar as one collapsed row, and opens into the full guide: who it is for, how to
|
|
6
|
+
use it, what each check catches, and the caveats (read-only, folder not
|
|
7
|
+
profile, advisory vs hard, runtime behaviour is not provable).
|
|
8
|
+
- The dock and the sidebar tab share one `useChecker()` hook and one `Form` /
|
|
9
|
+
`Result` pair, so the two cannot drift apart.
|
|
10
|
+
- `dsh.client.inject` now names `@deepseek-ai/dsh-client-ui-conversation`, the
|
|
11
|
+
package that owns the dock seat.
|
|
12
|
+
- Three tests: the dock registers under `conversation.input.dock`, it opens onto
|
|
13
|
+
the guide (and stays collapsed before that), and it runs the same request as
|
|
14
|
+
the tab.
|
|
15
|
+
|
|
16
|
+
## 0.9.7
|
|
17
|
+
|
|
18
|
+
- **The tab type is now reachable.** Registering a tab *type* is not enough: the
|
|
19
|
+
right sidebar's guide list is built from `tabs.guide()`, so a type with no
|
|
20
|
+
`guide` entry is registered, has a body slot, and is still impossible to open —
|
|
21
|
+
no tab appears anywhere. The type now contributes one guide entry
|
|
22
|
+
(`id: 'doctor'`, `order: 60`, localized title and description), which is what
|
|
23
|
+
puts "Plugin doctor" in the sidebar's guide.
|
|
24
|
+
- The title and description come from the panel's own dictionary through a
|
|
25
|
+
module-level `translate()`, because the sidebar calls them from its guide list,
|
|
26
|
+
outside this component.
|
|
27
|
+
- Regression test: mounting the bundle with a recording `sidebarRightTabs` now
|
|
28
|
+
asserts the registered definition carries a guide entry with a title and an
|
|
29
|
+
order.
|
|
30
|
+
|
|
1
31
|
## 0.9.6
|
|
2
32
|
|
|
3
33
|
- **The host half now survives a context it does not own.** `apply()` ended with
|
package/README.md
CHANGED
|
@@ -90,6 +90,14 @@ next: npm install && npm test && npm run doctor
|
|
|
90
90
|
|
|
91
91
|
What you get is not a snippet: it is a plugin whose `doctor` already passes (`engines.dsh`, the patch file, the `files` list), with a client bundle that registers a sidebar tab, a testkit test for it, and a CI workflow that runs both. `init` refuses a directory that is not empty unless you pass `--force`, and refuses a name npm would refuse — those are the two mistakes that cost the most time later.
|
|
92
92
|
|
|
93
|
+
## Where it lives
|
|
94
|
+
|
|
95
|
+
The checker is docked **under the conversation**, above the input bar: a single
|
|
96
|
+
row, collapsed by default. Open it and it carries the whole guide — who it is
|
|
97
|
+
for, how to use it, what each check means, and what it cannot tell you — plus
|
|
98
|
+
the folder field and the results. The same panel is also a **Plugin doctor** tab
|
|
99
|
+
in the right sidebar, for when the conversation is busy.
|
|
100
|
+
|
|
93
101
|
## Test a panel without a browser
|
|
94
102
|
|
|
95
103
|
A plugin's client code is one file that calls `window.__ModuleLoader__.load({factory})` — there is no module to import, so the usual React test setup does not apply. `testkit` supplies the missing half: a DOM, the real React, and a host context that records what the panel registers.
|
package/README.zh.md
CHANGED
|
@@ -53,6 +53,10 @@ cd my-panel && npm install && npm test
|
|
|
53
53
|
|
|
54
54
|
版本号取自当前运行的 CLI 自身,所以脚手架不会落后于你正在读的文档。
|
|
55
55
|
|
|
56
|
+
## 它在哪里
|
|
57
|
+
|
|
58
|
+
检查器固定在**对话栏下方、输入框上方**:默认收起成一行,点开就是完整指南——这是给谁用的、怎么用、每一项检查拦什么、以及它证明不了什么——下面才是文件夹输入框和结果。同一个面板也在右侧栏有一个 **Plugin doctor** 标签页,对话区忙的时候用那个。
|
|
59
|
+
|
|
56
60
|
## 不用浏览器测面板
|
|
57
61
|
|
|
58
62
|
```js
|
package/client.js
CHANGED
|
@@ -20,6 +20,9 @@ window.__ModuleLoader__.load({
|
|
|
20
20
|
fail: 'This would not install cleanly.',
|
|
21
21
|
noDir: 'Name the folder that holds the plugin\'s package.json.',
|
|
22
22
|
failed: 'Could not run the checks',
|
|
23
|
+
guideDescription: 'Run the install checks on a plugin folder',
|
|
24
|
+
dockTitle: 'Plugin doctor',
|
|
25
|
+
dockSub: 'Will this plugin survive an install? Check a folder before you publish it.',
|
|
23
26
|
},
|
|
24
27
|
zh: {
|
|
25
28
|
tabTitle: '插件体检',
|
|
@@ -32,9 +35,108 @@ window.__ModuleLoader__.load({
|
|
|
32
35
|
fail: '这样装上去会出问题。',
|
|
33
36
|
noDir: '请填写放着 package.json 的插件文件夹。',
|
|
34
37
|
failed: '检查没能跑起来',
|
|
38
|
+
guideDescription: '对任意插件文件夹跑一遍安装前的检查',
|
|
39
|
+
dockTitle: '插件体检',
|
|
40
|
+
dockSub: '这个插件装上去还能用吗?发布前先对着文件夹查一遍。',
|
|
35
41
|
},
|
|
36
42
|
};
|
|
37
43
|
|
|
44
|
+
/**
|
|
45
|
+
* The full guide, rendered in the dock. It is deliberately long: the panel
|
|
46
|
+
* used to be a bare input with no explanation of what it was for, which is
|
|
47
|
+
* the first question anyone asks.
|
|
48
|
+
*/
|
|
49
|
+
const GUIDE = {
|
|
50
|
+
en: {
|
|
51
|
+
who: {
|
|
52
|
+
title: 'Who this is for',
|
|
53
|
+
body: 'People who write DSH plugins. It is not a daily tool — if you never publish a plugin, it has nothing for you.',
|
|
54
|
+
},
|
|
55
|
+
steps: {
|
|
56
|
+
title: 'How to use it',
|
|
57
|
+
items: [
|
|
58
|
+
'Paste the folder that holds the plugin\'s package.json (a folder, not a file).',
|
|
59
|
+
'Press Check. Nothing is installed and none of your files are touched.',
|
|
60
|
+
'Read the list: ✓ passes, ✗ would break the install, with one line saying why.',
|
|
61
|
+
],
|
|
62
|
+
},
|
|
63
|
+
checks: {
|
|
64
|
+
title: 'What it checks',
|
|
65
|
+
rows: [
|
|
66
|
+
['manifest', 'name, version, engines.dsh'],
|
|
67
|
+
['root export', 'the package exports apply — the loader mounts nothing without it'],
|
|
68
|
+
['client declaration', 'dsh.client agrees with exports["./client"], which is what makes the host serve the panel'],
|
|
69
|
+
['files list', 'npm pack would actually ship every path package.json points at'],
|
|
70
|
+
['patch', 'cordis.patch.yml is exactly one - insert: row'],
|
|
71
|
+
['readme', 'the README has a Requirements section (the market index asks for it)'],
|
|
72
|
+
['tarball', 'the packed tarball installs and its root export imports'],
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
notes: {
|
|
76
|
+
title: 'Before you trust it',
|
|
77
|
+
items: [
|
|
78
|
+
'Read-only: it does not install anything, write files, or make network calls beyond this one request.',
|
|
79
|
+
'It looks at a folder, not at what you have installed — use dsh-plugin audit --host … for that.',
|
|
80
|
+
'engines.dsh and README are market-index requirements; a plugin missing them still runs.',
|
|
81
|
+
'It cannot prove runtime behaviour. A panel can still fail inside the real app when a service arrives late or a registration throws; those failures are appended to ~/.dsh/toolkit-client-report.ndjson.',
|
|
82
|
+
],
|
|
83
|
+
},
|
|
84
|
+
cli: {
|
|
85
|
+
title: 'The same checks in a terminal',
|
|
86
|
+
body: 'dsh-plugin verify . · dsh-plugin doctor . · dsh-plugin audit --host http://127.0.0.1:PORT',
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
zh: {
|
|
90
|
+
who: {
|
|
91
|
+
title: '这是给谁用的',
|
|
92
|
+
body: '写 DSH 插件的人。它不是日常工具——如果你不发布插件,它对你没有用处。',
|
|
93
|
+
},
|
|
94
|
+
steps: {
|
|
95
|
+
title: '怎么用',
|
|
96
|
+
items: [
|
|
97
|
+
'粘贴放着 package.json 的那个文件夹(是文件夹,不是文件)。',
|
|
98
|
+
'按「检查」。不会安装任何东西,也不会改你的文件。',
|
|
99
|
+
'看列表:✓ 通过,✗ 是装上去会坏的地方,右边一行说明原因。',
|
|
100
|
+
],
|
|
101
|
+
},
|
|
102
|
+
checks: {
|
|
103
|
+
title: '检查什么',
|
|
104
|
+
rows: [
|
|
105
|
+
['manifest', 'name、version、engines.dsh'],
|
|
106
|
+
['root export', '包导出的东西里有没有 apply——没有它加载器什么都挂不上'],
|
|
107
|
+
['client declaration', 'dsh.client 与 exports["./client"] 是否一致,这决定宿主会不会加载面板'],
|
|
108
|
+
['files list', 'npm pack 是否真的会带上 package.json 指到的每个文件'],
|
|
109
|
+
['patch', 'cordis.patch.yml 是不是恰好一条 - insert:'],
|
|
110
|
+
['readme', 'README 有没有 Requirements 段(市场索引会看)'],
|
|
111
|
+
['tarball', '打包后的 tarball 能不能装上、根导出能不能 import'],
|
|
112
|
+
],
|
|
113
|
+
},
|
|
114
|
+
notes: {
|
|
115
|
+
title: '用之前要知道的',
|
|
116
|
+
items: [
|
|
117
|
+
'只读:不安装、不写文件、除这一次请求外不联网。',
|
|
118
|
+
'它看的是文件夹,不是你已装的插件——查已装的用 dsh-plugin audit --host …。',
|
|
119
|
+
'engines.dsh 与 README 是市场索引的要求,缺了插件照样能跑。',
|
|
120
|
+
'它证明不了运行期行为。面板在真实应用里仍可能因为服务来晚了、注册抛错而失败;那些失败会写进 ~/.dsh/toolkit-client-report.ndjson。',
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
cli: {
|
|
124
|
+
title: '同样的检查,命令行里',
|
|
125
|
+
body: 'dsh-plugin verify . · dsh-plugin doctor . · dsh-plugin audit --host http://127.0.0.1:PORT',
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* The panel's copy, by the document language. Module scope because the tab
|
|
132
|
+
* type's `title`/`description` are called by the sidebar's guide list, not
|
|
133
|
+
* by this component, and they need the same strings.
|
|
134
|
+
*/
|
|
135
|
+
const translate = (key) => {
|
|
136
|
+
const lang = typeof document === 'undefined' ? 'en' : (document.documentElement.lang === 'zh' ? 'zh' : 'en');
|
|
137
|
+
return COPY[lang][key] ?? key;
|
|
138
|
+
};
|
|
139
|
+
|
|
38
140
|
const h = React.createElement;
|
|
39
141
|
|
|
40
142
|
const CSS = [
|
|
@@ -63,6 +165,28 @@ window.__ModuleLoader__.load({
|
|
|
63
165
|
'.tk-check{font-size:11px;text-transform:uppercase;letter-spacing:.04em;',
|
|
64
166
|
'color:var(--dsw-alias-label-tertiary,#888)}',
|
|
65
167
|
'.tk-message{font-size:12px;overflow-wrap:anywhere}',
|
|
168
|
+
'.tk-dock{border-top:0.5px solid var(--dsw-alias-border-l2,#e5e5e5);margin:0 12px}',
|
|
169
|
+
'.tk-dock-head{display:flex}',
|
|
170
|
+
'.tk-dock-toggle{display:flex;align-items:center;gap:8px;width:100%;padding:7px 2px;',
|
|
171
|
+
'border:0;background:none;font:inherit;font-size:12px;color:inherit;cursor:pointer;text-align:left}',
|
|
172
|
+
'.tk-dock-toggle:focus-visible{outline:1px solid var(--dsw-alias-state-business-primary,#4176e6);',
|
|
173
|
+
'outline-offset:-1px;border-radius:4px}',
|
|
174
|
+
'.tk-dock-title{font-weight:600}',
|
|
175
|
+
'.tk-dock-sub{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;',
|
|
176
|
+
'color:var(--dsw-alias-label-tertiary,#888)}',
|
|
177
|
+
'.tk-dock-chevron{flex:none;color:var(--dsw-alias-label-tertiary,#888)}',
|
|
178
|
+
'.tk-dock-body{display:flex;flex-direction:column;gap:12px;padding:2px 2px 14px;',
|
|
179
|
+
'max-height:46vh;overflow:auto;font-size:12px}',
|
|
180
|
+
'.tk-guide{margin:0;display:flex;flex-direction:column;gap:5px}',
|
|
181
|
+
'.tk-guide h3{margin:0;font-size:12px;font-weight:600}',
|
|
182
|
+
'.tk-guide p{margin:0;color:var(--dsw-alias-label-secondary,#555)}',
|
|
183
|
+
'.tk-guide ol,.tk-guide ul{margin:0;padding-left:18px;display:flex;flex-direction:column;gap:3px;',
|
|
184
|
+
'color:var(--dsw-alias-label-secondary,#555)}',
|
|
185
|
+
'.tk-guide-rows{display:grid;grid-template-columns:auto 1fr;gap:3px 10px;margin:0}',
|
|
186
|
+
'.tk-guide-rows dt{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px;',
|
|
187
|
+
'color:var(--dsw-alias-label-primary,#222)}',
|
|
188
|
+
'.tk-guide-rows dd{margin:0;color:var(--dsw-alias-label-secondary,#555)}',
|
|
189
|
+
'.tk-guide code{font-family:ui-monospace,SFMono-Regular,Menlo,monospace;font-size:11px}',
|
|
66
190
|
].join('');
|
|
67
191
|
|
|
68
192
|
function injectStyles(document) {
|
|
@@ -84,19 +208,19 @@ window.__ModuleLoader__.load({
|
|
|
84
208
|
);
|
|
85
209
|
}
|
|
86
210
|
|
|
87
|
-
|
|
211
|
+
/**
|
|
212
|
+
* The check itself, shared by the dock and the sidebar tab so the two can
|
|
213
|
+
* never drift apart.
|
|
214
|
+
*/
|
|
215
|
+
function useChecker() {
|
|
88
216
|
const [dir, setDir] = useState('');
|
|
89
217
|
const [result, setResult] = useState(null);
|
|
90
218
|
const [busy, setBusy] = useState(false);
|
|
91
219
|
const [failed, setFailed] = useState(null);
|
|
92
|
-
const tr = (key) => {
|
|
93
|
-
const language = COPY[document.documentElement.lang === 'zh' ? 'zh' : 'en'];
|
|
94
|
-
return language[key] ?? key;
|
|
95
|
-
};
|
|
96
220
|
|
|
97
221
|
const check = useCallback(() => {
|
|
98
222
|
if (dir.trim().length === 0) {
|
|
99
|
-
setFailed(
|
|
223
|
+
setFailed(translate('noDir'));
|
|
100
224
|
return;
|
|
101
225
|
}
|
|
102
226
|
setBusy(true);
|
|
@@ -104,35 +228,96 @@ window.__ModuleLoader__.load({
|
|
|
104
228
|
fetch(`/toolkit/check?dir=${encodeURIComponent(dir.trim())}`)
|
|
105
229
|
.then((response) => (response.ok === true ? response.json() : Promise.reject(new Error(String(response.status)))))
|
|
106
230
|
.then((payload) => setResult(payload))
|
|
107
|
-
.catch((error) => setFailed(`${
|
|
231
|
+
.catch((error) => setFailed(`${translate('failed')} · ${error.message}`))
|
|
108
232
|
.finally(() => setBusy(false));
|
|
109
233
|
}, [dir]);
|
|
110
234
|
|
|
235
|
+
return { dir, setDir, result, busy, failed, check };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function Form({ checker }) {
|
|
239
|
+
return h('div', { className: 'tk-form' },
|
|
240
|
+
h('input', {
|
|
241
|
+
className: 'tk-input',
|
|
242
|
+
type: 'text',
|
|
243
|
+
value: checker.dir,
|
|
244
|
+
placeholder: translate('placeholder'),
|
|
245
|
+
'aria-label': translate('placeholder'),
|
|
246
|
+
onChange: (event) => checker.setDir(event.target.value),
|
|
247
|
+
onKeyDown: (event) => { if (event.key === 'Enter') checker.check(); },
|
|
248
|
+
}),
|
|
249
|
+
h('button', {
|
|
250
|
+
className: 'tk-button',
|
|
251
|
+
type: 'button',
|
|
252
|
+
disabled: checker.busy === true,
|
|
253
|
+
onClick: checker.check,
|
|
254
|
+
}, checker.busy === true ? translate('checking') : translate('check')));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function Result({ checker }) {
|
|
258
|
+
return h('div', { className: 'tk-result' },
|
|
259
|
+
checker.failed !== null ? h('p', { className: 'tk-failed', role: 'status' }, checker.failed) : null,
|
|
260
|
+
checker.result !== null
|
|
261
|
+
? h('div', { className: 'tk-result-body' },
|
|
262
|
+
h('p', { className: 'tk-verdict', 'data-ok': checker.result.ok === true ? 'true' : 'false', role: 'status' },
|
|
263
|
+
checker.result.ok === true ? translate('pass') : translate('fail')),
|
|
264
|
+
h('ul', { className: 'tk-rows' }, checker.result.checks.map((entry) => h(Row, { key: entry.check, entry }))))
|
|
265
|
+
: null);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function Panel() {
|
|
269
|
+
const checker = useChecker();
|
|
111
270
|
return h('div', { className: 'tk-panel' },
|
|
112
|
-
h('h2', { className: 'tk-title' },
|
|
113
|
-
h('p', { className: 'tk-hint' },
|
|
114
|
-
h(
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
271
|
+
h('h2', { className: 'tk-title' }, translate('title')),
|
|
272
|
+
h('p', { className: 'tk-hint' }, translate('hint')),
|
|
273
|
+
h(Form, { checker }),
|
|
274
|
+
h(Result, { checker }));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* The same checker, docked under the conversation where people actually
|
|
279
|
+
* look. Collapsed it is one row; opened it carries the whole guide, because
|
|
280
|
+
* "what is this for" is the question the sidebar tab never answered.
|
|
281
|
+
*/
|
|
282
|
+
function Dock() {
|
|
283
|
+
const [open, setOpen] = useState(false);
|
|
284
|
+
const checker = useChecker();
|
|
285
|
+
const guide = GUIDE[typeof document === 'undefined' || document.documentElement.lang !== 'zh' ? 'en' : 'zh'];
|
|
286
|
+
|
|
287
|
+
return h('section', { className: 'tk-dock', 'data-open': open === true ? 'true' : 'false' },
|
|
288
|
+
h('div', { className: 'tk-dock-head' },
|
|
124
289
|
h('button', {
|
|
125
|
-
className: 'tk-
|
|
290
|
+
className: 'tk-dock-toggle',
|
|
126
291
|
type: 'button',
|
|
127
|
-
|
|
128
|
-
onClick:
|
|
129
|
-
},
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
h('
|
|
292
|
+
'aria-expanded': open === true ? 'true' : 'false',
|
|
293
|
+
onClick: () => setOpen(open !== true),
|
|
294
|
+
},
|
|
295
|
+
h('span', { className: 'tk-dock-title' }, translate('dockTitle')),
|
|
296
|
+
h('span', { className: 'tk-dock-sub' }, translate('dockSub')),
|
|
297
|
+
h('span', { className: 'tk-dock-chevron', 'aria-hidden': 'true' }, open === true ? '▾' : '▸'))),
|
|
298
|
+
open === true
|
|
299
|
+
? h('div', { className: 'tk-dock-body' },
|
|
300
|
+
h('div', { className: 'tk-guide' },
|
|
301
|
+
h('h3', {}, guide.who.title),
|
|
302
|
+
h('p', {}, guide.who.body)),
|
|
303
|
+
h('div', { className: 'tk-guide' },
|
|
304
|
+
h('h3', {}, guide.steps.title),
|
|
305
|
+
h('ol', {}, guide.steps.items.map((item) => h('li', { key: item }, item)))),
|
|
306
|
+
h('div', { className: 'tk-guide' },
|
|
307
|
+
h('h3', {}, guide.checks.title),
|
|
308
|
+
h('dl', { className: 'tk-guide-rows' },
|
|
309
|
+
guide.checks.rows.map(([name, what]) => [
|
|
310
|
+
h('dt', { key: `${name}-k` }, name),
|
|
311
|
+
h('dd', { key: `${name}-v` }, what),
|
|
312
|
+
]))),
|
|
313
|
+
h('div', { className: 'tk-guide' },
|
|
314
|
+
h('h3', {}, guide.notes.title),
|
|
315
|
+
h('ul', {}, guide.notes.items.map((item) => h('li', { key: item }, item)))),
|
|
316
|
+
h('div', { className: 'tk-guide' },
|
|
317
|
+
h('h3', {}, guide.cli.title),
|
|
318
|
+
h('p', {}, h('code', {}, guide.cli.body))),
|
|
319
|
+
h(Form, { checker }),
|
|
320
|
+
h(Result, { checker }))
|
|
136
321
|
: null);
|
|
137
322
|
}
|
|
138
323
|
|
|
@@ -195,11 +380,21 @@ window.__ModuleLoader__.load({
|
|
|
195
380
|
report('no-inject', 'ctx.inject is not a function');
|
|
196
381
|
return;
|
|
197
382
|
}
|
|
198
|
-
ctx.inject(['sidebarRightTabs', 'sidebarRight'], (tabCtx) => {
|
|
383
|
+
ctx.inject(['slots', 'sidebarRightTabs', 'sidebarRight'], (tabCtx) => {
|
|
199
384
|
// Failures inside this callback happen after apply() returned, so the
|
|
200
385
|
// guards above do not cover them — this try/catch is the one that
|
|
201
386
|
// decides whether the row lands in "failed".
|
|
202
387
|
try {
|
|
388
|
+
// The dock lives under the conversation and needs nothing but
|
|
389
|
+
// `slots`, so it is registered before the sidebar is required.
|
|
390
|
+
if (tabCtx !== undefined && tabCtx.slots !== undefined) {
|
|
391
|
+
tabCtx.effect(() => tabCtx.slots.inject('conversation.input.dock', () => tabCtx.slots.register({
|
|
392
|
+
name: 'conversation.input.dock',
|
|
393
|
+
id: NS,
|
|
394
|
+
order: 30,
|
|
395
|
+
locale: NS,
|
|
396
|
+
}, Dock)));
|
|
397
|
+
}
|
|
203
398
|
if (tabCtx === undefined || tabCtx.slots === undefined || tabCtx.sidebarRightTabs === undefined) {
|
|
204
399
|
report('no-sidebar', `tabCtx slots=${String(tabCtx !== undefined && tabCtx.slots !== undefined)} sidebarRightTabs=${String(tabCtx !== undefined && tabCtx.sidebarRightTabs !== undefined)}`);
|
|
205
400
|
return;
|
|
@@ -209,11 +404,20 @@ window.__ModuleLoader__.load({
|
|
|
209
404
|
key: NS,
|
|
210
405
|
locale: NS,
|
|
211
406
|
}, Panel)));
|
|
407
|
+
// A tab *type* with no guide entry is registered but unreachable:
|
|
408
|
+
// the sidebar's guide list is built from `tabs.guide()`, so the
|
|
409
|
+
// type would never be offered and nothing would open it.
|
|
212
410
|
tabCtx.effect(() => tabCtx.sidebarRightTabs.register({
|
|
213
411
|
id: NS,
|
|
214
412
|
kind: KIND,
|
|
215
413
|
priority: 'extension',
|
|
216
|
-
title: () => '
|
|
414
|
+
title: () => translate('tabTitle'),
|
|
415
|
+
guide: [{
|
|
416
|
+
id: 'doctor',
|
|
417
|
+
order: 60,
|
|
418
|
+
title: () => translate('tabTitle'),
|
|
419
|
+
description: () => translate('guideDescription'),
|
|
420
|
+
}],
|
|
217
421
|
}));
|
|
218
422
|
report('tab-registered', `kind=${KIND}`);
|
|
219
423
|
} catch (error) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-plugin-toolkit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Preflight, release and test DeepSeek Harness plugins: checks what the host needs, waits for npm to actually serve the version, and keeps the market index honest.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -81,6 +81,7 @@
|
|
|
81
81
|
"platform": "web",
|
|
82
82
|
"immediately": true,
|
|
83
83
|
"inject": [
|
|
84
|
+
"@deepseek-ai/dsh-client-ui-conversation",
|
|
84
85
|
"@deepseek-ai/dsh-client-ui-sidebar-right"
|
|
85
86
|
]
|
|
86
87
|
}
|