dsh-toolbox-ui 1.6.6 → 1.6.7
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/README.en.md +2 -2
- package/README.md +2 -2
- package/docs/installation.md +5 -5
- package/docs/skills.md +3 -2
- package/lib/client.js +123 -22
- package/lib/whats-new.js +18 -1
- package/package.json +1 -1
package/README.en.md
CHANGED
|
@@ -58,10 +58,10 @@ See every installed skill's **real scope** and cross-scope shadowing, enable/dis
|
|
|
58
58
|
|
|
59
59
|
```sh
|
|
60
60
|
# Recommended: from npm — `dsh plugin update` then upgrades within ^1.x
|
|
61
|
-
dsh plugin --profile web add dsh-toolbox-ui@^1.6.
|
|
61
|
+
dsh plugin --profile web add dsh-toolbox-ui@^1.6.7
|
|
62
62
|
|
|
63
63
|
# Or pin a GitHub release tag (no automatic upgrades; re-add with a new tag)
|
|
64
|
-
dsh plugin --profile web add github:Imzl-zl/dsh-toolbox-ui#v1.6.
|
|
64
|
+
dsh plugin --profile web add github:Imzl-zl/dsh-toolbox-ui#v1.6.7
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
Then **restart `dsh web`**. To uninstall (your existing MCP entries stay):
|
package/README.md
CHANGED
|
@@ -58,10 +58,10 @@
|
|
|
58
58
|
|
|
59
59
|
```sh
|
|
60
60
|
# 推荐:npm 安装 —— 之后 `dsh plugin update` 在 ^1.x 内自动升小版本
|
|
61
|
-
dsh plugin --profile web add dsh-toolbox-ui@^1.6.
|
|
61
|
+
dsh plugin --profile web add dsh-toolbox-ui@^1.6.7
|
|
62
62
|
|
|
63
63
|
# 或固定 GitHub release tag(不自动跨版本,升级要换 tag 重新 add)
|
|
64
|
-
dsh plugin --profile web add github:Imzl-zl/dsh-toolbox-ui#v1.6.
|
|
64
|
+
dsh plugin --profile web add github:Imzl-zl/dsh-toolbox-ui#v1.6.7
|
|
65
65
|
```
|
|
66
66
|
|
|
67
67
|
装完**重启 `dsh web`**。卸载(不会删除 profile 里已有的 MCP 条目):
|
package/docs/installation.md
CHANGED
|
@@ -15,14 +15,14 @@
|
|
|
15
15
|
推荐用 npm 安装:包里的 `^1.x` 范围让后续 `dsh plugin update` 能自动升级小版本。
|
|
16
16
|
|
|
17
17
|
```sh
|
|
18
|
-
dsh plugin --profile web add dsh-toolbox-ui@^1.6.
|
|
18
|
+
dsh plugin --profile web add dsh-toolbox-ui@^1.6.7
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
也可以固定 GitHub release tag(适合需要锁定某个具体版本时):
|
|
22
22
|
|
|
23
23
|
```sh
|
|
24
24
|
# 固定 release tag
|
|
25
|
-
dsh plugin --profile web add github:Imzl-zl/dsh-toolbox-ui#v1.6.
|
|
25
|
+
dsh plugin --profile web add github:Imzl-zl/dsh-toolbox-ui#v1.6.7
|
|
26
26
|
|
|
27
27
|
# 或固定某个 commit
|
|
28
28
|
dsh plugin --profile web add github:Imzl-zl/dsh-toolbox-ui#<commit>
|
|
@@ -73,7 +73,7 @@ dsh plugin --profile web update dsh-toolbox-ui
|
|
|
73
73
|
|
|
74
74
|
| 安装规格 | `update` 的效果 | 怎么跨版本 |
|
|
75
75
|
|---|---|---|
|
|
76
|
-
| `dsh-toolbox-ui@^1.6.
|
|
76
|
+
| `dsh-toolbox-ui@^1.6.7`(npm,推荐) | 在 `^1.x` 内自动升到最新小版本,不会跨到 2.0.0 | 已是自动;跨大版本显式安装 `@^2` |
|
|
77
77
|
| `github:...#v1.4.0`(tag) | 重新解析同一个 tag,**不会动** | 换成新 tag 重新 `add`:`... #<新 tag>` |
|
|
78
78
|
| `github:...#<commit>` | 同上,固定在 commit | 用新 commit/tag 重新 `add` |
|
|
79
79
|
| `github:...#main`(分支) | 拉取最新 main | 已是自动;但不推荐生产(无 release 门槛) |
|
|
@@ -113,7 +113,7 @@ dsh web # 打开面板点一遍
|
|
|
113
113
|
# 3) 提交并推 tag(CI 会校验 tag 与 version 一致,不一致就拒绝发布)
|
|
114
114
|
git add -A && git commit -m "..."
|
|
115
115
|
git push origin main
|
|
116
|
-
git tag -a v1.6.
|
|
116
|
+
git tag -a v1.6.7 -m "dsh-toolbox-ui 1.6.7" && git push origin v1.6.7
|
|
117
117
|
```
|
|
118
118
|
|
|
119
119
|
推 tag 后 CI 依次做:校验版本 → `pnpm install --frozen-lockfile` → `npm test` → `npm publish`(trusted publishing,无需 token)→ `gh release create`。
|
|
@@ -132,7 +132,7 @@ git tag -a v1.6.6 -m "dsh-toolbox-ui 1.6.6" && git push origin v1.6.6
|
|
|
132
132
|
|
|
133
133
|
```sh
|
|
134
134
|
npm publish # 会弹 2FA 挑战;若用 bypass-2FA 的 granular token 则非交互
|
|
135
|
-
gh release create v1.6.
|
|
135
|
+
gh release create v1.6.7 --title "dsh-toolbox-ui v1.6.7" --notes-file <notes>
|
|
136
136
|
```
|
|
137
137
|
|
|
138
138
|
`npm login` 的会话 token 只有 2 小时,超时需要重新登录;classic token 已被 npm 永久吊销,能非交互发布的只剩勾了 **Bypass 2FA** 的 granular access token——但它正在被 npm 收窄(已不能做账号/包管理类操作,且 2027-01 起将失去直接发布能力),所以长期方案是上面的 trusted publishing。
|
package/docs/skills.md
CHANGED
|
@@ -104,7 +104,7 @@ DSH 的技能没有 `disabled` 字段(MCP 有,技能没有)。**但宿主
|
|
|
104
104
|
边界确定,注释随行而去)、或文件里有宿主会拒绝的驼峰键(`disableModelInvocation`)时,
|
|
105
105
|
面板会拒绝并在界面上说明原因,让你手工改——猜一个改法等于改坏你的技能文件。
|
|
106
106
|
- **生效时机**:写的是文件,宿主发现这条技能变了之后才影响模型——**通常不到一秒**。
|
|
107
|
-
|
|
107
|
+
宿主用文件监听盯住技能根,改动会自动生效(实测约 0.25 秒:8 次采样的中位数是 246ms),面板显示的就是磁盘上的值,
|
|
108
108
|
**不必重启 DSH**。唯一例外:宿主启动时若被配置成不监听(`skill-filesystem` 的 `watch: false`),
|
|
109
109
|
那才需要重启——真机默认是监听的。详见下一节。
|
|
110
110
|
- 内置(bundled)技能只读,开关呈**禁用**(与启停/删除同一约定:渲染出来但点不动)。
|
|
@@ -112,7 +112,7 @@ DSH 的技能没有 `disabled` 字段(MCP 有,技能没有)。**但宿主
|
|
|
112
112
|
## 改了技能,多久生效
|
|
113
113
|
|
|
114
114
|
**宿主侧:自动、很快。** DSH 用文件监听盯住每个技能根,技能的新增、改名、删除与内容改动
|
|
115
|
-
|
|
115
|
+
都会让它作废自己的技能目录——**实测约 0.25 秒**(8 次采样中位数 246ms),无需重启。你在面板里启停/改权限之后,
|
|
116
116
|
模型下一次看技能列表时就是新的(本面板写的就是那个文件)。
|
|
117
117
|
|
|
118
118
|
**面板侧:切回标签页时重取。** 面板不能做到"被推送"——宿主有个 `skills/change` 事件,
|
|
@@ -173,6 +173,7 @@ DSH 的技能没有 `disabled` 字段(MCP 有,技能没有)。**但宿主
|
|
|
173
173
|
|
|
174
174
|
- 搜索由 **skillhub.cn** 提供(市场只有这一个来源),走它自己的搜索与分类接口;
|
|
175
175
|
- 搜索是**显式触发**的(回车 / 切分类),不是随打随搜;分类 key 来自上游,标签来自它的分类接口,取不到就显示原始 key;
|
|
176
|
+
- 结果是**分页**的:每页 100 条(上游上限),滚到底自动加载下一页,底部也有「加载更多」按钮;计数显示「已显示 X / 共 Y 条」,到头就不再发请求。换关键字/分类会重置回第 1 页并整体替换;
|
|
176
177
|
- 远端结果**不做**许可核实(上游条目没有许可字段),别把「上游收录」当成「已核过许可」;
|
|
177
178
|
- 失败不会让市场页打不开:宿主进程是旧的(wire 404)就提示重启宿主,上游真挂了就在结果区告警 + 重试。
|
|
178
179
|
|
package/lib/client.js
CHANGED
|
@@ -516,6 +516,8 @@ const SKILLS_CSS = `.dsh-skills-root{display:flex;flex-direction:column;min-heig
|
|
|
516
516
|
.dsh-skills-ack{display:flex;align-items:flex-start;gap:8px;font-size:12px;line-height:18px;padding:8px 0;color:var(--dsw-alias-label-primary);cursor:pointer}
|
|
517
517
|
.dsh-skills-ack input{margin:2px 0 0;flex:none;accent-color:var(--mcp-accent)}
|
|
518
518
|
.dsh-skills-note{padding:10px 12px;font-size:12px;line-height:18px;color:var(--mcp-mut);border-top:.5px solid var(--mcp-line);flex:none}
|
|
519
|
+
/* 市场翻页区:按钮居中。滚动到底的自动加载挂在 .dsh-mcp-market-view 的 onScroll 上。 */
|
|
520
|
+
.dsh-skills-hub-more{display:flex;justify-content:center;padding:4px 0 10px;flex:none}
|
|
519
521
|
/* 外部源的「上游页面」链接:与插件其余链接同一套(品牌色 + hover 下划线),
|
|
520
522
|
不在弹窗里引入第二种链接样式。 */
|
|
521
523
|
.dsh-skills-link{color:var(--mcp-accent);text-decoration:none;word-break:break-all}
|
|
@@ -4282,7 +4284,7 @@ const SkillDetail = ({ row, shadowedBy, body, busy, onRetry, onToggle, onRemove,
|
|
|
4282
4284
|
title: readonly
|
|
4283
4285
|
? '内置技能只读'
|
|
4284
4286
|
: invocationKnown
|
|
4285
|
-
? '写入 SKILL.md 的 disable-model-invocation
|
|
4287
|
+
? '写入 SKILL.md 的 disable-model-invocation。宿主用文件监听盯技能根,改动约 0.25 秒就生效(实测中位数 246ms),不必重启'
|
|
4286
4288
|
: invocationUnknownReason(row),
|
|
4287
4289
|
onChange: () => onModelInvocable(row, row.modelInvocable !== true),
|
|
4288
4290
|
}),
|
|
@@ -4507,6 +4509,37 @@ const HubCard = ({ entry, installed, onInstall }) => h(
|
|
|
4507
4509
|
),
|
|
4508
4510
|
);
|
|
4509
4511
|
|
|
4512
|
+
/**
|
|
4513
|
+
* 市场每页条数。上游 /api/skills 的 pageSize 上限就是 100(宿主侧会夹住),用满它:
|
|
4514
|
+
* 上游有 19 万条(实测 total=192936),按 48 一页要翻 4019 页,按 100 翻 1930 页。
|
|
4515
|
+
*/
|
|
4516
|
+
const HUB_PAGE_SIZE = 100;
|
|
4517
|
+
|
|
4518
|
+
/**
|
|
4519
|
+
* 市场条目的唯一性不变式:同一个 slug 只出现一次。
|
|
4520
|
+
*
|
|
4521
|
+
* 为什么必须全局去重(跨页 + 页内一起):上游**同一页内**就会返回重复 slug——
|
|
4522
|
+
* 实测第 1 页(pageSize=100, sortBy=downloads)100 条里只有 88 个不同 slug
|
|
4523
|
+
* (agent-browser / anti-fraud 各出现 3 次,同一技能被不同发布者重复发布)。
|
|
4524
|
+
* 只按「与已加载页」去重挡不住页内重复:重复 key 进 React 后 reconciliation
|
|
4525
|
+
* 错乱,真机表现是卡片越滚越多、计数与实物不符(297 张卡 vs 计数 285)、
|
|
4526
|
+
* 搜索像没生效。 HubCard 的 key 是 slug,所以这条不变式同时是渲染正确性的前提。
|
|
4527
|
+
*
|
|
4528
|
+
* @param {object[]} items - 上游条目(已规范化的形状)。
|
|
4529
|
+
* @returns {object[]} 去重后的新数组(保持原顺序,不修改入参)。
|
|
4530
|
+
*/
|
|
4531
|
+
function dedupeHubSkills(items) {
|
|
4532
|
+
const seen = new Set();
|
|
4533
|
+
const unique = [];
|
|
4534
|
+
for (const item of items ?? []) {
|
|
4535
|
+
const slug = String(item?.slug ?? '');
|
|
4536
|
+
if (seen.has(slug)) continue;
|
|
4537
|
+
seen.add(slug);
|
|
4538
|
+
unique.push(item);
|
|
4539
|
+
}
|
|
4540
|
+
return unique;
|
|
4541
|
+
}
|
|
4542
|
+
|
|
4510
4543
|
/**
|
|
4511
4544
|
* 技能能力面的入口组件:管理 + 市场。
|
|
4512
4545
|
*
|
|
@@ -4530,7 +4563,16 @@ const SkillsPanel = ({ call, workspaces, mode = 'manage', scopeFilter = 'all', o
|
|
|
4530
4563
|
// 宿主支持的**全部**落盘位置(含还没建的)。老宿主没有这个方法 → 空数组 → 退回发现集。
|
|
4531
4564
|
const [targets, setTargets] = useState([]);
|
|
4532
4565
|
// 市场只有**一个**来源:skillhub.cn(直连上游、本插件只做披露)。
|
|
4533
|
-
|
|
4566
|
+
// page/done 是翻页的真值来源:上游 total=192936(实测,深页有货),只取首页就会
|
|
4567
|
+
// 「计数写着 19 万、屏幕只有一页」——数字与实物对不上,用户以为下拉会刷新。
|
|
4568
|
+
const [hub, setHub] = useState({ items: [], total: 0, page: 1, done: false, loading: false, loadingMore: false, error: '', kind: '' });
|
|
4569
|
+
// 滚动回调与「加载更多」按钮都在渲染之外触发,那里读不到本次渲染的 hub——ref 拿最新的一份。
|
|
4570
|
+
const hubRef = useRef(hub);
|
|
4571
|
+
useEffect(() => { hubRef.current = hub; }, [hub]);
|
|
4572
|
+
// 同步的在途标志:state 要等下一次渲染+effect 才同步进 ref,那之间的重复触发
|
|
4573
|
+
// (双击按钮、滚动事件连续 fire)会并发打同一页——去重后第二次响应全是重复项,
|
|
4574
|
+
// 会被误判成 done,翻页提前终止。所以这道闸必须是同步的。
|
|
4575
|
+
const hubInFlight = useRef(false);
|
|
4534
4576
|
const [hubCategory, setHubCategory] = useState('');
|
|
4535
4577
|
const [hubCategoryLabels, setHubCategoryLabels] = useState({});
|
|
4536
4578
|
// 外部源条目被选中时的披露信息(点安装时按 slug 拉一次 detail)。
|
|
@@ -4611,15 +4653,44 @@ const SkillsPanel = ({ call, workspaces, mode = 'manage', scopeFilter = 'all', o
|
|
|
4611
4653
|
const searchHub = useCallback(async () => {
|
|
4612
4654
|
setHub((current) => ({ ...current, loading: true, error: '', kind: '' }));
|
|
4613
4655
|
try {
|
|
4614
|
-
|
|
4656
|
+
// page: 1 是**重置搜索**:换关键字/分类时整体替换,翻页态归零。
|
|
4657
|
+
const result = await call('skillHubSearch', { keyword: hubKeyword, category: hubCategory, page: 1, pageSize: HUB_PAGE_SIZE });
|
|
4658
|
+
if (!result.ok) throw new Error(result.error || '搜索失败');
|
|
4659
|
+
setHub({ items: dedupeHubSkills(result.skills), total: result.total || 0, page: 1, done: false, loading: false, loadingMore: false, error: '', kind: '' });
|
|
4660
|
+
} catch (error) {
|
|
4661
|
+
const message = String(error?.message || error);
|
|
4662
|
+
setHub({ items: [], total: 0, page: 1, done: false, loading: false, loadingMore: false, error: message, kind: classifyHubError(message) });
|
|
4663
|
+
}
|
|
4664
|
+
}, [call, hubCategory, hubKeyword]);
|
|
4665
|
+
|
|
4666
|
+
// 加载下一页:**追加**而不是替换(替换就成了「永远只有第 1 页」)。
|
|
4667
|
+
const loadMoreHub = useCallback(async () => {
|
|
4668
|
+
const current = hubRef.current;
|
|
4669
|
+
// 没有更多就不发:done(上游该给新条目的页全是重复项)或已显示满 total。
|
|
4670
|
+
if (hubInFlight.current || current.loading || current.loadingMore || current.done || current.items.length >= current.total) return;
|
|
4671
|
+
hubInFlight.current = true;
|
|
4672
|
+
setHub((prev) => ({ ...prev, loadingMore: true, error: '', kind: '' }));
|
|
4673
|
+
try {
|
|
4674
|
+
const result = await call('skillHubSearch', { keyword: hubKeyword, category: hubCategory, page: current.page + 1, pageSize: HUB_PAGE_SIZE });
|
|
4615
4675
|
if (!result.ok) throw new Error(result.error || '搜索失败');
|
|
4616
|
-
setHub(
|
|
4676
|
+
setHub((prev) => {
|
|
4677
|
+
// 一条不变式表达两种重复:拼接后整体去重,新增的尾部就是这一页的净增。
|
|
4678
|
+
// 跨页重复(排名漂移)与页内重复(多个发布者同一 slug)一次挡住;
|
|
4679
|
+
// 净增为 0 = 上游翻不出新东西了,再滚也是重复请求。
|
|
4680
|
+
const merged = dedupeHubSkills([...prev.items, ...(result.skills || [])]);
|
|
4681
|
+
return { ...prev, items: merged, page: prev.page + 1, loadingMore: false, done: merged.length === prev.items.length };
|
|
4682
|
+
});
|
|
4617
4683
|
} catch (error) {
|
|
4618
4684
|
const message = String(error?.message || error);
|
|
4619
|
-
setHub(
|
|
4685
|
+
setHub((prev) => ({ ...prev, loadingMore: false, error: message, kind: classifyHubError(message) }));
|
|
4686
|
+
} finally {
|
|
4687
|
+
hubInFlight.current = false;
|
|
4620
4688
|
}
|
|
4621
4689
|
}, [call, hubCategory, hubKeyword]);
|
|
4622
4690
|
|
|
4691
|
+
// 还有没有下一页:一条派生值,按钮显隐、滚动触发、停止条件都读它——不各写一份判断。
|
|
4692
|
+
const hubHasMore = hub.items.length < hub.total && hub.done !== true;
|
|
4693
|
+
|
|
4623
4694
|
// 分类只取一次:它不随关键字变,放进搜索的 effect 里会每搜一次多打一次分类接口。
|
|
4624
4695
|
useEffect(() => {
|
|
4625
4696
|
void (async () => {
|
|
@@ -4852,7 +4923,20 @@ const SkillsPanel = ({ call, workspaces, mode = 'manage', scopeFilter = 'all', o
|
|
|
4852
4923
|
// 市场与 MCP 面同一副骨架:分类药丸 + 搜索 | 说明 | 网格 + 卡片。
|
|
4853
4924
|
// 曾经这一面用的是 .dsh-skills-bar / .dsh-skills-tags / .dsh-skills-grid / .dsh-skills-card
|
|
4854
4925
|
// 一整套私有原语——同一个「发现市场」在两个能力面长得不一样,于是切换时像换了个产品。
|
|
4855
|
-
|
|
4926
|
+
// 滚动监听挂在滚动容器**自己**身上(React 对 scroll 是原生直挂,不靠事件委托),
|
|
4927
|
+
// 不在 effect 里挂:宿主外壳在持续高频重渲染本面板(真机 wire 上 list/skillTargets
|
|
4928
|
+
// 每秒一轮、连续上百次渲染),被动 effect 的重跑会被饿住——IntersectionObserver
|
|
4929
|
+
// 版的观察器在首屏搜索完成后迟迟创建不出来,实测「首次滚动不加载、交互过一次才加载」。
|
|
4930
|
+
// onScroll 的处理器每次渲染都是新闭包,读的也是当次渲染的 loadMoreHub,不过期。
|
|
4931
|
+
: h('div', {
|
|
4932
|
+
className: 'dsh-mcp-market-view', role: 'region', 'aria-label': '技能发现市场',
|
|
4933
|
+
onScroll: (event) => {
|
|
4934
|
+
const box = event.currentTarget;
|
|
4935
|
+
// 离底部还有 200px 以上就什么都不做——给一点提前量,用户看到的就是「到底就有」。
|
|
4936
|
+
if (box.scrollTop + box.clientHeight < box.scrollHeight - 200) return;
|
|
4937
|
+
void loadMoreHub();
|
|
4938
|
+
},
|
|
4939
|
+
},
|
|
4856
4940
|
h('div', { className: 'dsh-mcp-market-toolbar' },
|
|
4857
4941
|
h('div', { className: 'dsh-mcp-market-head' },
|
|
4858
4942
|
h('div', { className: 'dsh-mcp-search' },
|
|
@@ -4881,24 +4965,29 @@ const SkillsPanel = ({ call, workspaces, mode = 'manage', scopeFilter = 'all', o
|
|
|
4881
4965
|
),
|
|
4882
4966
|
h('div', { className: 'dsh-mcp-market-section' },
|
|
4883
4967
|
h('b', null, 'skillhub.cn(外部源)'),
|
|
4884
|
-
|
|
4968
|
+
// 计数说清「已显示 / 共」:只写总数就是 19 万条结果旁边只摆一页卡片——
|
|
4969
|
+
// 数字是真的,用户在面板里却到不了其余条目,看着就像没有。
|
|
4970
|
+
h('span', null, hub.loading ? '正在搜索…' : '已显示 ' + hub.items.length + ' / 共 ' + hub.total + ' 条 · 装入' + (scope === 'global' ? '全局' : '项目')),
|
|
4885
4971
|
),
|
|
4886
4972
|
// 两种失败**分开说**:宿主没这个接口 ≠ 上游挂了。混成一句会让用户去查网络,
|
|
4887
4973
|
// 而真正要做的是重启宿主加载新版插件。
|
|
4888
|
-
|
|
4889
|
-
|
|
4890
|
-
|
|
4891
|
-
|
|
4892
|
-
|
|
4893
|
-
|
|
4894
|
-
|
|
4895
|
-
|
|
4896
|
-
|
|
4897
|
-
|
|
4898
|
-
|
|
4899
|
-
|
|
4900
|
-
|
|
4901
|
-
|
|
4974
|
+
// 错误 Notice 只在**一页都没有**时替换整个结果区:首屏失败 = 市场打不开,这么摆;
|
|
4975
|
+
// 加载更多失败是另一回事——已加载的页面要留着,错误与重试放在结果区上方,
|
|
4976
|
+
// 否则上游抖一下,用户已翻的几百张卡就全没了(追加失败是翻页引入的新失败面)。
|
|
4977
|
+
hub.items.length === 0
|
|
4978
|
+
? (hub.error
|
|
4979
|
+
? (hub.kind === 'missing'
|
|
4980
|
+
? h(Notice, {
|
|
4981
|
+
tone: 'warn', icon: 'warn',
|
|
4982
|
+
title: '当前宿主进程还没有外部源接口',
|
|
4983
|
+
children: '面板已更新,但这个正在运行的宿主进程是在更新之前启动的,所以它不认识 skillHubSearch。重启宿主(dsh 进程)后即可使用。',
|
|
4984
|
+
})
|
|
4985
|
+
: h(Notice, {
|
|
4986
|
+
tone: 'warn', icon: 'warn', title: '连接 skillhub.cn 失败', children: hub.error,
|
|
4987
|
+
actions: [h(Btn, { key: 'retry', cls: 'sm', disabled: hub.loading, onClick: () => void searchHub(), children: '重试' })],
|
|
4988
|
+
}))
|
|
4989
|
+
: h(SkillsEmpty, { text: hub.loading ? '正在搜索…' : '没有匹配的外部技能。' }))
|
|
4990
|
+
: h('div', { className: 'dsh-mcp-grid' },
|
|
4902
4991
|
...hub.items.map((item) => h(HubCard, {
|
|
4903
4992
|
key: item.slug,
|
|
4904
4993
|
entry: item,
|
|
@@ -4913,7 +5002,19 @@ const SkillsPanel = ({ call, workspaces, mode = 'manage', scopeFilter = 'all', o
|
|
|
4913
5002
|
setHubDetail(result.ok ? result : { pageUrl: target.pageUrl, publisher: null, security: null, requiresApiKey: target.requiresApiKey });
|
|
4914
5003
|
})();
|
|
4915
5004
|
},
|
|
4916
|
-
})))
|
|
5005
|
+
}))),
|
|
5006
|
+
hub.error && hub.items.length > 0
|
|
5007
|
+
? h(Notice, {
|
|
5008
|
+
tone: 'warn', icon: 'warn', title: '加载更多失败', children: hub.error,
|
|
5009
|
+
actions: [h(Btn, { key: 'retry-more', cls: 'sm', disabled: hub.loadingMore, onClick: () => void loadMoreHub(), children: '重试' })],
|
|
5010
|
+
})
|
|
5011
|
+
: null,
|
|
5012
|
+
// 翻页区:显式按钮(兜底,键盘可达)。滚动到底的自动加载挂在上面那个滚动
|
|
5013
|
+
// 容器的 onScroll 上——没有更多时整块消失,摆一个点不动的按钮比不摆更糟。
|
|
5014
|
+
hubHasMore
|
|
5015
|
+
? h('div', { className: 'dsh-skills-hub-more' },
|
|
5016
|
+
h(Btn, { cls: 'sm', disabled: hub.loadingMore, onClick: () => void loadMoreHub(), children: hub.loadingMore ? '加载中…' : '加载更多' }))
|
|
5017
|
+
: null,
|
|
4917
5018
|
h('div', { className: 'dsh-skills-note' },
|
|
4918
5019
|
h('b', null, '外部源,本插件未核验。'),
|
|
4919
5020
|
'skillhub.cn 上的技能由任何人发布,条目里没有许可信息;安装前会把上游给的发布方认证与安全扫描结论摆出来,但那不等于本插件的审查结论。收录 ≠ 背书。'),
|
package/lib/whats-new.js
CHANGED
|
@@ -260,7 +260,7 @@ export const WHATS_NEW = [
|
|
|
260
260
|
where: '技能列表',
|
|
261
261
|
},
|
|
262
262
|
{
|
|
263
|
-
text: '
|
|
263
|
+
text: '**改完不用重启了(以前面板让你重启,其实是白重启)**:宿主用文件监听盯住技能根,你改完技能约 **0.25 秒**就生效(实测 8 次,中位数 246ms)——旧提示写「重启 DSH 最稳妥」会让你白重启一次。面板本身照旧在动作后与切回标签页时重取(宿主有个 `skills/change` 事件,但不在它转发的白名单里,第三方插件收不到,所以做不到实时推送)',
|
|
264
264
|
where: '技能详情的开关提示、docs/skills.md',
|
|
265
265
|
},
|
|
266
266
|
{
|
|
@@ -269,4 +269,21 @@ export const WHATS_NEW = [
|
|
|
269
269
|
},
|
|
270
270
|
],
|
|
271
271
|
},
|
|
272
|
+
{
|
|
273
|
+
version: '1.6.7',
|
|
274
|
+
items: [
|
|
275
|
+
{
|
|
276
|
+
text: '**技能市场终于能翻页了**:以前滚到没有任何反应——上游其实有 19 万个技能(实测 19.3 万),面板却只取第一页,计数还写着「19 万条结果」,数字和屏幕上的卡片永远对不上。现在**滚到底自动加载下一页**(底部也有「加载更多」按钮兜底),计数改成「已显示 X / 共 Y 条」;换关键字或分类会重置回第一页并整体替换',
|
|
277
|
+
where: '技能 →「发现市场」',
|
|
278
|
+
},
|
|
279
|
+
{
|
|
280
|
+
text: '**修掉市场卡片重复、越滚越乱**:上游同一页里就会返回重复条目——同一个技能被不同发布者多次发布(实测第一页 100 条只有 88 个不同技能),重复的卡片会让页面渲染错乱、搜索结果像叠在一起。现在按技能标识**全局去重**(跨页与页内一起去),屏幕上有的每一张都不一样,计数与实物一致',
|
|
281
|
+
where: '技能 →「发现市场」',
|
|
282
|
+
},
|
|
283
|
+
{
|
|
284
|
+
text: '**加载更多失败不再清空你已经翻到的卡片**:上游偶尔抖一下时,以前的摆法会把整个结果区换成一条错误提示,已加载的几百张卡片全部消失。现在错误与重试放在结果区上方,你翻到的东西原地留着',
|
|
285
|
+
where: '技能 →「发现市场」的结果区',
|
|
286
|
+
},
|
|
287
|
+
],
|
|
288
|
+
},
|
|
272
289
|
]
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-toolbox-ui",
|
|
3
|
-
"version": "1.6.
|
|
3
|
+
"version": "1.6.7",
|
|
4
4
|
"description": "Responsive MCP management UI and skills manager for DeepSeek Harness Web — floating panel, JSON import, profile-backed Host Remote management, and a skill market with a hard license gate.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|