dsh-skill-hub 0.3.13 → 0.3.15

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.
Files changed (60) hide show
  1. package/CONTRIBUTING.md +6 -4
  2. package/README.md +9 -5
  3. package/README.zh.md +9 -5
  4. package/lib/client.js +55 -58
  5. package/lib/client.js.map +1 -1
  6. package/lib/index.js +529 -315
  7. package/lib/types/client/SkillHubSettingsCard.d.ts +5 -3
  8. package/lib/types/client/icons.d.ts +4 -5
  9. package/lib/types/client/index.d.ts +13 -12
  10. package/lib/types/client/locales/market.d.ts +1 -0
  11. package/lib/types/client/locales.d.ts +1 -0
  12. package/lib/types/client/settings-form.d.ts +9 -3
  13. package/lib/types/client/slash-dots.d.ts +20 -11
  14. package/lib/types/index.d.ts +59 -24
  15. package/lib/types/protocol/api.d.ts +22 -2
  16. package/lib/types/protocol/config.d.ts +30 -4
  17. package/lib/types/protocol/market.d.ts +1 -1
  18. package/lib/types/protocol/repo.d.ts +18 -1
  19. package/lib/types/protocol.d.ts +3 -3
  20. package/lib/types/repo/discovery.d.ts +59 -3
  21. package/lib/types/repo/github-client.d.ts +16 -0
  22. package/lib/types/repo.d.ts +6 -3
  23. package/lib/types/routes/helpers.d.ts +8 -1
  24. package/lib/types/routes/market.d.ts +2 -2
  25. package/lib/types/routes.d.ts +1 -1
  26. package/lib/types/store/store.d.ts +4 -0
  27. package/package.json +31 -41
  28. package/src/client/SkillHubSettingsCard.tsx +5 -3
  29. package/src/client/icons.tsx +4 -5
  30. package/src/client/index.tsx +33 -32
  31. package/src/client/locales/market.ts +2 -0
  32. package/src/client/panel/RepoScanCard.tsx +6 -3
  33. package/src/client/panel/SkillHubPanel.tsx +1 -1
  34. package/src/client/panel/SourcesView.tsx +1 -1
  35. package/src/client/panel/hooks/useGroupFlow.ts +1 -8
  36. package/src/client/settings-form.ts +9 -4
  37. package/src/client/slash-dots.test.ts +19 -36
  38. package/src/client/slash-dots.tsx +30 -60
  39. package/src/index.ts +145 -82
  40. package/src/protocol/api.ts +24 -2
  41. package/src/protocol/config.ts +35 -4
  42. package/src/protocol/market.ts +1 -1
  43. package/src/protocol/repo.ts +22 -1
  44. package/src/protocol.ts +3 -3
  45. package/src/repo/discovery.ts +120 -33
  46. package/src/repo/github-client.ts +18 -1
  47. package/src/repo/install.ts +8 -7
  48. package/src/repo.test.ts +158 -4
  49. package/src/repo.ts +10 -2
  50. package/src/routes/config.ts +19 -2
  51. package/src/routes/helpers.ts +9 -2
  52. package/src/routes/market.ts +49 -37
  53. package/src/routes/repo-import.ts +3 -2
  54. package/src/routes/sources.ts +2 -1
  55. package/src/routes.test.ts +107 -1
  56. package/src/routes.ts +21 -3
  57. package/src/store/migrate.ts +3 -2
  58. package/src/store/store.ts +7 -2
  59. package/src/store.test.ts +50 -0
  60. package/src/update.ts +4 -2
@@ -97,6 +97,10 @@ export declare class SkillHubStore {
97
97
  * When `dir` is given, every baseline path under that skill directory is
98
98
  * dropped first, so files the upstream removed never linger in the
99
99
  * baseline and skew later update diffs.
100
+ *
101
+ * `dir` may be '' — a skill whose SKILL.md sits at the repo root. Its prefix
102
+ * is empty, so the whole baseline is replaced: that skill owns the tree, and
103
+ * keeping stale paths would make every later diff report "changed" forever.
100
104
  */
101
105
  mergeSourceManifest(repo: string, manifest: Record<string, number>, dir?: string): Promise<void>;
102
106
  /** skillName → collection name for every recorded origin (derived from sources). */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-skill-hub",
3
- "version": "0.3.13",
3
+ "version": "0.3.15",
4
4
  "description": "In-GUI skill hub for DeepSeek Harness (dsh): browse the full local skill catalog from the official ctx.skills registry (every root + third-party providers), toggle skills on/off, inspect bodies, surface frontmatter diagnostics, and scaffold new skills — plus a codex-style skill market (built-in catalog, upstream update checks, one-click update-all) with tracked source sync. The full manager beyond the read-only dsh-skill-manager browser.",
5
5
  "type": "module",
6
6
  "engines": {
@@ -61,19 +61,7 @@
61
61
  ],
62
62
  "compatibility": {
63
63
  "dshVersions": [
64
- "0.1.0-rc.7",
65
- "0.1.1-rc.2",
66
- "0.1.2-alpha.2",
67
- "0.1.2-alpha.3",
68
- "0.1.2-alpha.4",
69
- "0.1.2-alpha.5",
70
- "0.1.2-rc.1",
71
- "0.1.3-alpha.2",
72
- "0.1.5-alpha.1",
73
- "0.1.5-alpha.2",
74
- "0.1.5-rc.1",
75
- "0.1.5-rc.2",
76
- "0.1.6-alpha.1"
64
+ "0.1.7-alpha.1"
77
65
  ]
78
66
  },
79
67
  "capability": {
@@ -95,19 +83,20 @@
95
83
  },
96
84
  "peerDependencies": {
97
85
  "@deepseek-ai/cordis": "^4.0.1 || ^4.0.2",
98
- "@deepseek-ai/dsh-client-connection": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
99
- "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
100
- "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
101
- "@deepseek-ai/dsh-client-store": ">=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
102
- "@deepseek-ai/dsh-client-ui-input-trigger": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
103
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
104
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
105
- "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
106
- "@deepseek-ai/dsh-session": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
107
- "@deepseek-ai/dsh-session-query": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
108
- "@deepseek-ai/dsh-settings": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
109
- "@deepseek-ai/dsh-skill": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
110
- "@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0 || >=0.1.2-alpha.2 <0.2.0-0 || >=0.1.2-alpha.3 <0.2.0-0 || >=0.1.2-alpha.4 <0.2.0-0 || >=0.1.2-alpha.5 <0.2.0-0 || >=0.1.2-rc.1 <0.2.0-0 || >=0.1.3-alpha.2 <0.2.0-0 || >=0.1.5-alpha.1 <0.2.0-0 || >=0.1.5-alpha.2 <0.2.0-0 || >=0.1.5-rc.1 <0.2.0-0 || >=0.1.5-rc.2 <0.2.0-0 || >=0.1.6-alpha.1 <0.2.0-0",
86
+ "@deepseek-ai/dsh-client-connection": ">=0.1.7-alpha.1 <0.2.0-0",
87
+ "@deepseek-ai/dsh-client-locale": ">=0.1.7-alpha.1 <0.2.0-0",
88
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.7-alpha.1 <0.2.0-0",
89
+ "@deepseek-ai/dsh-client-store": ">=0.1.7-alpha.1 <0.2.0-0",
90
+ "@deepseek-ai/dsh-client-ui-input-trigger": ">=0.1.7-alpha.1 <0.2.0-0",
91
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.7-alpha.1 <0.2.0-0",
92
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.7-alpha.1 <0.2.0-0",
93
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.7-alpha.1 <0.2.0-0",
94
+ "@deepseek-ai/dsh-session": ">=0.1.7-alpha.1 <0.2.0-0",
95
+ "@deepseek-ai/dsh-session-query": ">=0.1.7-alpha.1 <0.2.0-0",
96
+ "@deepseek-ai/dsh-settings": ">=0.1.7-alpha.1 <0.2.0-0",
97
+ "@deepseek-ai/dsh-skill": ">=0.1.7-alpha.1 <0.2.0-0",
98
+ "@deepseek-ai/dsh-system-prompt": ">=0.1.7-alpha.1 <0.2.0-0",
99
+ "@deepseek-ai/schemastery": "^3.18.3",
111
100
  "react": "^18.2.0",
112
101
  "react-dom": "^18.2.0"
113
102
  },
@@ -121,19 +110,21 @@
121
110
  },
122
111
  "devDependencies": {
123
112
  "@deepseek-ai/cordis": "^4.0.2",
124
- "@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.1",
125
- "@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.1",
126
- "@deepseek-ai/dsh-client-store": "^0.1.6-alpha.1",
127
- "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.6-alpha.1",
128
- "@deepseek-ai/dsh-client-ui-settings": "^0.1.6-alpha.1",
129
- "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.6-alpha.1",
130
- "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.1",
131
- "@deepseek-ai/dsh-host-webserver": "^0.1.6-alpha.1",
132
- "@deepseek-ai/dsh-session": "^0.1.6-alpha.1",
133
- "@deepseek-ai/dsh-session-query": "^0.1.6-alpha.1",
134
- "@deepseek-ai/dsh-settings": "^0.1.6-alpha.1",
135
- "@deepseek-ai/dsh-skill": "^0.1.6-alpha.1",
136
- "@deepseek-ai/dsh-system-prompt": "^0.1.6-alpha.1",
113
+ "@deepseek-ai/dsh-client-connection": "^0.1.7-alpha.1",
114
+ "@deepseek-ai/dsh-client-locale": "^0.1.7-alpha.1",
115
+ "@deepseek-ai/dsh-client-store": "^0.1.7-alpha.1",
116
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.7-alpha.1",
117
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.7-alpha.1",
118
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.7-alpha.1",
119
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.7-alpha.1",
120
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.7-alpha.1",
121
+ "@deepseek-ai/dsh-host-webserver": "^0.1.7-alpha.1",
122
+ "@deepseek-ai/dsh-session": "^0.1.7-alpha.1",
123
+ "@deepseek-ai/dsh-session-query": "^0.1.7-alpha.1",
124
+ "@deepseek-ai/dsh-settings": "^0.1.7-alpha.1",
125
+ "@deepseek-ai/dsh-skill": "^0.1.7-alpha.1",
126
+ "@deepseek-ai/dsh-system-prompt": "^0.1.7-alpha.1",
127
+ "@deepseek-ai/schemastery": "^3.18.3",
137
128
  "@types/js-yaml": "^4.0.9",
138
129
  "@types/node": "^22.20.0",
139
130
  "@types/react": "~18.3.1",
@@ -142,7 +133,6 @@
142
133
  "lightningcss": "1.32.0",
143
134
  "react": "^18.3.1",
144
135
  "react-dom": "^18.3.1",
145
- "schemastery": "^3.18.0",
146
136
  "tsdown": "0.22.2",
147
137
  "typescript": "~5.7.2",
148
138
  "vitest": "^3.0.0",
@@ -2,8 +2,10 @@
2
2
  * The dsh-skill-hub plugin settings card: bridges the hub's settings
3
3
  * namespace (bound through the official settings transport) onto the
4
4
  * family-style staged card form (enabled master switch + agent announcement).
5
- * Registered into the official `settings.plugin.item` slot keyed by that
6
- * namespace, so the plugin shows up in Settings → 插件 on dsh rc.7+.
5
+ * Registered into `plugins.bundle.config` under the bundle's package name, so
6
+ * the Plugins manager renders it on dsh-skill-hub's own page. That slot is
7
+ * dispatched with `view: 'page'` only, so the card always renders its full
8
+ * form (no `summary` one-liner variant).
7
9
  */
8
10
 
9
11
  import { useEffect, useState, type ReactElement } from 'react'
@@ -54,7 +56,7 @@ export interface SkillHubSettingsCardFace {
54
56
 
55
57
  /** Props the slot renderer binds (locale copy + injected form actions). */
56
58
  export type SkillHubSettingsCardProps =
57
- PropsRuntime<'settings.plugin.item'> & PropsLocale<'dsh-skill-hub'> & InjectFace<SkillHubSettingsCardFace>
59
+ PropsRuntime<'plugins.bundle.config'> & PropsLocale<'dsh-skill-hub'> & InjectFace<SkillHubSettingsCardFace>
58
60
 
59
61
  /** Bridges the hub's config scope onto the card's staged form. */
60
62
  export class SkillHubSettingsCardController {
@@ -2,11 +2,10 @@
2
2
  * Vendored UI icons for dsh-skill-hub.
3
3
  *
4
4
  * These were originally imported from `@deepseek-ai/dsh-client-ui-primitives`.
5
- * That package is still published for the rc.7/rc.2 SDK families, but newer dsh
6
- * web builds no longer expose it as a standalone plugin module — they keep a
7
- * static compatibility module instead. Vendoring the few tiny outline icons the
8
- * hub uses makes the browser half self-contained and equally compatible with
9
- * older and newer dsh hosts.
5
+ * That package is not exposed as a standalone plugin module by the dsh web
6
+ * build this plugin targets — dsh keeps a static compatibility module instead.
7
+ * Vendoring the few tiny outline icons the hub uses keeps the browser half
8
+ * self-contained and independent of how that module is packaged.
10
9
  *
11
10
  * SVG paths are copied verbatim from dsh-client-ui-primitives (MIT licensed)
12
11
  * so the visuals remain pixel-identical to the dsh icon family.
@@ -2,16 +2,17 @@
2
2
  * Browser-half entry for the dsh-skill-hub plugin — runs inside the dsh
3
3
  * web GUI.
4
4
  *
5
- * Registers the dsh-skill-hub locale dictionaries and mounts two Settings
6
- * surfaces, both through official slots (no DOM injection):
7
- * - a plugin-management card in the `settings.plugin.item` slot (Settings →
8
- * 插件 → 可配置插件列表), keyed by the hub's settings namespace and bound
9
- * through the official settings transport (dsh rc.7 serves every
10
- * registered namespace to the web client, and the tab dispatches cards by
11
- * namespace) — the family-bucket card pattern (PluginSettingsCard +
12
- * CardForm vendored from dsh-task-board);
5
+ * Registers the dsh-skill-hub locale dictionaries and mounts:
13
6
  * - a top-level Settings section (Settings → 技能) hosting the skill hub
14
- * panel: catalog, search, enable/disable, diagnostics, new-skill form.
7
+ * panel: catalog, search, enable/disable, diagnostics, new-skill form;
8
+ * - the chat "/" menu skill dots, colored from the same config the panel reads.
9
+ *
10
+ * - a configuration card in the `plugins.bundle.config` slot, keyed by the
11
+ * BUNDLE PACKAGE NAME, rendered on the plugin's own page in the Plugins
12
+ * manager (sidebar → 插件 → dsh-skill-hub). dsh 0.1.7 has no auto-generated
13
+ * config page: the manager only renders that section for bundles that
14
+ * register this slot, and the card writes through the shared config form
15
+ * (`ctx.configForms.get(skill-hub)`), the same form the host routes read.
15
16
  *
16
17
  * Failure policy: mounting problems are logged, never thrown — the web
17
18
  * shell fails the whole boot when a plugin apply throws, and an external
@@ -25,24 +26,25 @@
25
26
  import type { Context as ClientContext } from '@deepseek-ai/cordis'
26
27
  // Type-only: pulls the locale plugin's Context merge (ctx.locale).
27
28
  import type {} from '@deepseek-ai/dsh-client-locale/client'
28
- // Type-only: pulls the settings-scope service merge and the settings.section slot.
29
+ // Type-only: pulls the configForms service merge and the settings.section slot.
29
30
  import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
30
31
  // Type-only: pulls the LocaleNamespaceMap merge table.
31
32
  import type {} from '@deepseek-ai/dsh-client-ui-slots'
32
- // Type-only: pulls the settings-plugins SlotMap merge (settings.plugin.item).
33
- import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
34
33
  // Type-only: pulls the Context merge for ctx.inputTriggers (slash-dots wiring).
35
34
  import type {} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
35
+ // Type-only: pulls the plugin-manager SlotMap merge (plugins.bundle.config).
36
+ import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
36
37
  // Type-only: pulls the connection/reset event.
37
38
  import type {} from '@deepseek-ai/dsh-client-connection/client'
38
39
 
39
- // 0.1.2-alpha.2: slots service moved to ui-renderer; provide local augmentation for build.
40
+ // The slots service is owned by ui-renderer, which this build does not depend
41
+ // on; augment the Context locally so the browser half typechecks.
40
42
  declare module '@deepseek-ai/cordis' {
41
43
  interface Context {
42
44
  slots: any
43
45
  }
44
46
  }
45
- import type { HubSettingsValue } from '../protocol.ts'
47
+ import { HUB_ENTRY_ID, type HubSettingsValue } from '../protocol.ts'
46
48
  import { SkillHubApi } from './api.ts'
47
49
  import { en, zh, type HubKey } from './locales.ts'
48
50
  import { applySettingsNavIcon } from './settings-nav-icon.ts'
@@ -63,11 +65,11 @@ declare module '@deepseek-ai/dsh-client-ui-slots' {
63
65
  /**
64
66
  * Required services (fiber inject waiting — the runtime must be up first).
65
67
  * `connection`/`remote` are the settings transport's own prerequisites
66
- * (`ctx.settingsScope.bind` resolves them on the caller's fiber), and
67
- * `settingsScope` is the namespace-scope binder itself; mirror the official
68
- * settings-plugins inject list.
68
+ * (`ctx.configForms.get` resolves them on the caller's fiber), and
69
+ * `configForms` is the shared settings-form service itself; mirror the
70
+ * official settings-plugins inject list.
69
71
  */
70
- export const inject = ['slots', 'locale', 'connection', 'remote', 'settingsScope', 'inputTriggers']
72
+ export const inject = ['slots', 'locale', 'connection', 'remote', 'configForms', 'inputTriggers']
71
73
 
72
74
  /** Type-only surface (export discipline: no value exports beyond the plugin contract). */
73
75
  export type { SkillHubPanelProps } from './panel/SkillHubPanel.tsx'
@@ -83,30 +85,29 @@ export function apply(ctx: ClientContext): void {
83
85
  const t = ctx.locale.bind(NS)
84
86
  const api = new SkillHubApi()
85
87
 
86
- // The card edits the hub's settings namespace through the official
87
- // settings transport — the configurable-plugins tab only dispatches cards
88
- // whose key the Host serves, and the Host serves every registered namespace
89
- // since rc.7, so this is what makes the card appear (and stay in sync).
90
- // Single scope instance reused for both the card and slash-dots to avoid
91
- // duplicate subscriptions (review #3).
92
- const scope = ctx.settingsScope.bind<HubSettingsValue>({ namespace: NS })
88
+ // The hub's config form is the same Loader entry the host writes and the
89
+ // Plugins manager renders; the browser half reads it only to color the dots
90
+ // (single form instance, so slash-dots keeps one subscription).
91
+ const scope = ctx.configForms.get<HubSettingsValue>(HUB_ENTRY_ID)
93
92
  const settingsCard = new SkillHubSettingsCardController(scope)
94
93
 
95
94
  // Chat `/` 菜单技能圆点:为每个候选行加可调用性圆点(蓝=模型可调,绿=仅用户),颜色与面板图例同步;仅装饰,不自动预填 "/"
96
95
  // inject 含 inputTriggers 保证 fiber 就绪后再 wrap,slash-dots 内的 undefined 防御仅用于单元测试 mock
97
96
  ctx.effect(() => setupSkillSlashDots(ctx, api, scope), 'dsh-skill-hub: slash dots')
98
97
 
99
- // Plugin-management card: Settings → 插件 → 可配置插件列表.
100
- // rc.7's slot contract declares this keyed slot with options `key`
101
- // (the settings namespace the card edits), so registration is fully typed.
98
+ // The plugin's own configuration page. It must be registered here: the
99
+ // Plugins manager only renders the config section for bundles that appear in
100
+ // `plugins.bundle.config` (`ledger.bundles.has(openPkg.name)`), and its key is
101
+ // the BUNDLE PACKAGE NAME — not the settings entry id (`skill-hub`). There is
102
+ // no auto-generated fallback page in dsh 0.1.7.
102
103
  ctx.effect(
103
- () => ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
104
- name: 'settings.plugin.item',
105
- key: NS,
104
+ () => ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({
105
+ name: 'plugins.bundle.config',
106
+ key: 'dsh-skill-hub',
106
107
  locale: NS,
107
108
  inject: () => settingsCard.inject(),
108
109
  }, SkillHubSettingsCard)),
109
- 'dsh-skill-hub: settings card',
110
+ 'dsh-skill-hub: plugin config',
110
111
  )
111
112
 
112
113
  // Top-level Settings section: the skill management page.
@@ -74,6 +74,7 @@ export const zhMarket = {
74
74
  'repo.cancel': '取消',
75
75
  'repo.cancelled': '已取消 · 已导入 {imported}/{total} 已保留,临时文件已清理',
76
76
  'repo.importingCurrent': '正在导入: {name} ({done}/{total})',
77
+ 'repo.root.self': '仓库根',
77
78
  'repo.root.skills': 'skills',
78
79
  'repo.root.designTemplates': 'design-templates',
79
80
  'repo.root.generic': '{root}',
@@ -154,6 +155,7 @@ export const enMarket: Record<keyof typeof zhMarket, string> = {
154
155
  'repo.cancel': 'Cancel',
155
156
  'repo.cancelled': 'Cancelled · {imported}/{total} kept, temp files cleaned',
156
157
  'repo.importingCurrent': 'Importing: {name} ({done}/{total})',
158
+ 'repo.root.self': 'Repo root',
157
159
  'repo.root.skills': 'Skills',
158
160
  'repo.root.designTemplates': 'Templates',
159
161
  'repo.root.generic': '{root}',
@@ -14,8 +14,11 @@ import css from './panel.module.css'
14
14
  /**
15
15
  * 已知技能根的显示名键;未知根直接显示目录名。
16
16
  * zh 字典里这两项就是目录名本身(与接线前的硬编码文本一致)。
17
+ * 空串是「技能就在仓库根」的哨兵根(host 侧 REPO_ROOT),目录名无从显示,
18
+ * 给它一个可读标签,否则筛选按钮会是一片空白。
17
19
  */
18
- function rootLabelKey(root: string): 'repo.root.skills' | 'repo.root.designTemplates' | 'repo.root.generic' {
20
+ function rootLabelKey(root: string): 'repo.root.self' | 'repo.root.skills' | 'repo.root.designTemplates' | 'repo.root.generic' {
21
+ if (root === '') return 'repo.root.self'
19
22
  if (root === 'skills') return 'repo.root.skills'
20
23
  if (root === 'design-templates') return 'repo.root.designTemplates'
21
24
  return 'repo.root.generic'
@@ -94,7 +97,7 @@ export function RepoScanCard(props: { hub: SkillHubState }): JSX.Element | null
94
97
  />
95
98
  <button type='button' className={css.button + (repoFilter === 'all' ? ' ' + css.primary : '')} style={{ padding:'6px 10px', fontSize:12 }} onClick={() => setRepoFilter('all')}>{tt('repo.allRoots', { count: entries.length })}</button>
96
99
  {roots.map((root) => (
97
- <button key={root} type='button' className={css.button + (repoFilter === root ? ' ' + css.primary : '')} style={{ padding:'6px 10px', fontSize:12 }} onClick={() => setRepoFilter(root)}>{tt(rootLabelKey(root), { root })} {counts.get(root) ?? 0}</button>
100
+ <button key={root === '' ? 'repo-root' : root} type='button' className={css.button + (repoFilter === root ? ' ' + css.primary : '')} style={{ padding:'6px 10px', fontSize:12 }} onClick={() => setRepoFilter(root)}>{tt(rootLabelKey(root), { root })} {counts.get(root) ?? 0}</button>
98
101
  ))}
99
102
  </div>
100
103
  )
@@ -122,7 +125,7 @@ export function RepoScanCard(props: { hub: SkillHubState }): JSX.Element | null
122
125
  />
123
126
  <div className={css.rowMain}>
124
127
  <div className={css.rowName}>{entry.name}</div>
125
- <div className={css.rowDesc}>{entry.dir} · {tt('repo.files', { count: entry.fileCount, size: formatBytes(entry.totalBytes) })}</div>
128
+ <div className={css.rowDesc}>{entry.dir === '' ? tt('repo.root.self') : entry.dir} · {tt('repo.files', { count: entry.fileCount, size: formatBytes(entry.totalBytes) })}</div>
126
129
  </div>
127
130
  {existing
128
131
  ? <span className={css.badge + ' ' + css.badgeReadonly}>{tt('repo.existing')}</span>
@@ -43,7 +43,7 @@ export function SkillHubPanel(props: SkillHubPanelProps): React.JSX.Element {
43
43
  catalog, loading, loadError, successBanner, updateState, detail, detailLoading, showForm, formName, formDesc,
44
44
  formRoot, formBusy, formMessage, hubConfig, tab, skillView, sourceFilter, sortKey, search,
45
45
  workspace, setWorkspace,
46
- sourcesState, tagBusy, batchBusy, busyNames, normalized, origins, sourceOptions, filtered,
46
+ sourcesState, tagBusy, batchBusy, sourceOptions, filtered,
47
47
  conflictDialog, confirmDialog, deleteSkillDialog, deleteGroupDialog, confirmClearTrash, branchChoice, branchBusy, marketSyncDialog,
48
48
  syncBusy, editingTag, editName, membersDraft, editSearch, uses, groupsState, sourceCheck, checkingSource, syncingSource,
49
49
  showLegend, editMode,
@@ -38,7 +38,7 @@ export function SourcesView(props: { hub: SkillHubState }): JSX.Element {
38
38
  const uncategorized = sourceFiltered.filter((skill) => origins[skill.name] === undefined && !isProjectSource(skill.source))
39
39
  const personalDisabled = (catalog?.disabled ?? []).filter((record) => origins[record.name] === undefined)
40
40
  .filter((record) => normalized.length === 0 || record.name.toLocaleLowerCase().includes(normalized) || record.description.toLocaleLowerCase().includes(normalized))
41
- .filter((record) => sourceFilter === 'all' || sourceFilter === PRIVATE_SOURCE)
41
+ .filter(() => sourceFilter === 'all' || sourceFilter === PRIVATE_SOURCE)
42
42
  const allPersonalNames = [...uncategorized.map((s) => s.name), ...personalDisabled.map((r) => r.name)]
43
43
  const hasPersonal = allPersonalNames.length > 0
44
44
  const defaultTopKeys: string[] = [
@@ -5,14 +5,7 @@
5
5
  */
6
6
 
7
7
  import { useCallback, useState, type FormEvent } from 'react'
8
- import type {
9
- GroupsResponse,
10
- SkillTag,
11
- SourceGroupReorderRequest,
12
- TagDeleteRequest,
13
- TagMembersRequest,
14
- TagReorderRequest,
15
- } from '../../../protocol.ts'
8
+ import type { GroupsResponse, SkillTag } from '../../../protocol.ts'
16
9
  import type { SkillHubApi } from '../../api.ts'
17
10
  import { errorMessage } from '../../helpers.ts'
18
11
  import { conflictsOnClose, type GroupSwitchState } from '../../grouping.ts'
@@ -10,7 +10,6 @@
10
10
  */
11
11
 
12
12
  import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
13
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
14
13
 
15
14
  /** One staged field: its draft text plus the clear/reset marker. */
16
15
  interface StagedEdit {
@@ -107,12 +106,18 @@ export interface FieldState {
107
106
  invalid: boolean
108
107
  }
109
108
 
110
- /** The settings scope face the form consumes (subset of SettingsScope<T>). */
109
+ /**
110
+ * The settings-form face this card consumes. Declared structurally rather than
111
+ * imported so the card stays independent of the settings package's generics:
112
+ * the browser half passes it `ctx.configForms.get<HubSettingsValue>(HUB_ENTRY_ID)`,
113
+ * which satisfies this shape (ConfigForm<T>).
114
+ */
111
115
  export interface FormScope {
112
116
  subscribe(listener: () => void): () => void
113
117
  getSnapshot(): { status: string; writable: boolean; value?: Record<string, unknown>; base?: unknown; user?: unknown }
114
- set(field: string, value: unknown): Promise<void>
115
- unset(field: string): Promise<void>
118
+ /** Both write paths report host acceptance (ConfigForm.set/unset contract). */
119
+ set(field: string, value: unknown): Promise<boolean>
120
+ unset(field: string): Promise<boolean>
116
121
  }
117
122
 
118
123
  /** Stages one card's edits and writes them through the settings transport. */
@@ -1,12 +1,14 @@
1
1
  /**
2
2
  * Slash-menu skill dots: source lookup + candidates wrapping. The wrapper's
3
3
  * contract is pure and injectable — fake api (catalog) and fake settings scope
4
- * (getSnapshot), no DOM — so it tests cleanly in the node vitest environment.
5
- * The module caches the catalog map; resetModelCache keeps tests independent.
4
+ * (getSnapshot) — so it tests cleanly in the node vitest environment. The dot
5
+ * itself is injected into the rendered DOM, which does not exist here, so these
6
+ * tests assert that candidate rows pass through untouched. The module caches
7
+ * the catalog map; resetModelCache keeps tests independent.
6
8
  */
7
9
 
8
10
  import { beforeEach, describe, expect, it } from 'vitest'
9
- import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
11
+ import type { ConfigForm } from '@deepseek-ai/dsh-client-ui-settings/client'
10
12
  import type { InputTriggerCandidate, InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
11
13
  import type { HubSettingsValue } from '../protocol.ts'
12
14
  import type { SkillHubApi } from './api.ts'
@@ -33,14 +35,14 @@ function registry(...sources: InputTriggerSource[]): InputTriggerServiceContract
33
35
  }
34
36
 
35
37
  /** Fake settings scope snapshotting a fixed HubSettingsValue. */
36
- function scopeWith(value: HubSettingsValue): SettingsScope<HubSettingsValue> {
38
+ function scopeWith(value: HubSettingsValue): ConfigForm<HubSettingsValue> {
37
39
  return {
38
40
  getSnapshot: () => ({ status: 'ready', value, base: undefined, user: undefined, revision: 1, writable: true, mode: 'host' }),
39
41
  subscribe: () => () => {},
40
42
  set: async () => {},
41
43
  unset: async () => {},
42
44
  mutate: async () => {},
43
- } as unknown as SettingsScope<HubSettingsValue>
45
+ } as unknown as ConfigForm<HubSettingsValue>
44
46
  }
45
47
 
46
48
  /** Fake hub api whose catalog lists the given skills. */
@@ -85,11 +87,10 @@ describe('findSkillSource', () => {
85
87
  })
86
88
 
87
89
  describe('wrapSkillSource', () => {
88
- it('adds a model-colored dot to model-callable skills and a user-colored dot to user-only ones (old host, no drilled)', async () => {
90
+ it('leaves candidate rows untouched — the dot goes to the DOM, not the icon slot', async () => {
89
91
  const source = skillSource(async () => [
90
92
  { name: 'code-review', description: 'review code' },
91
93
  { name: 'personal-note', description: 'only me' },
92
- { name: 'known-default', description: 'fallback unknown' },
93
94
  ])
94
95
  const api = apiWith([
95
96
  { name: 'code-review', modelInvocable: true },
@@ -99,16 +100,13 @@ describe('wrapSkillSource', () => {
99
100
 
100
101
  const restore = wrapSkillSource(source, api, scope)
101
102
  try {
102
- // Old host: CandidateRequest 无 drilled 字段,注入彩色点
103
- const rows = await source.candidates({ sessionId: 's1' as never }, { query: '', position: 'leading', signal: new AbortController().signal } as unknown as import('@deepseek-ai/dsh-client-ui-input-trigger/client').CandidateRequest)
104
-
105
- expect(rows).toHaveLength(3)
106
- // Model-callable → model color.
107
- expect((rows[0].icon as unknown as { props: { style: { background: string } } }).props.style.background).toBe('#112233')
108
- // User-only → user color.
109
- expect((rows[1].icon as unknown as { props: { style: { background: string } } }).props.style.background).toBe('#445566')
110
- // Unknown name → model default (still a dot, safer than no dot).
111
- expect(rows[2].icon).toBeDefined()
103
+ const rows = await source.candidates({ sessionId: 's1' as never }, { query: '', position: 'leading', drilled: false, signal: new AbortController().signal })
104
+
105
+ expect(rows).toHaveLength(2)
106
+ // MenuView renders `icon` through ReferenceIcon (enum-narrowed), so a
107
+ // custom element would never show — rows must carry no icon at all.
108
+ expect(rows[0].icon).toBeUndefined()
109
+ expect(rows[1].icon).toBeUndefined()
112
110
  // Original fields pass through untouched.
113
111
  expect(rows[0].name).toBe('code-review')
114
112
  expect(rows[0].description).toBe('review code')
@@ -117,33 +115,18 @@ describe('wrapSkillSource', () => {
117
115
  }
118
116
  })
119
117
 
120
- it('falls back to the default colors when settings omit them and catalog fails (old host)', async () => {
118
+ it('still returns the rows when the catalog route is down', async () => {
121
119
  const source = skillSource(async () => [{ name: 'lonely', description: '' }])
122
120
  const api = { catalog: async () => { throw new Error('route down') } } as unknown as SkillHubApi
123
121
  const scope = scopeWith({ enabled: true, announceToAgent: true, showUseCount: true, showUseTime: true, showGroupSummary: true })
124
122
 
125
- const restore = wrapSkillSource(source, api, scope)
126
- try {
127
- const rows = await source.candidates({ sessionId: 's1' as never }, { query: '', position: 'leading', signal: new AbortController().signal } as unknown as import('@deepseek-ai/dsh-client-ui-input-trigger/client').CandidateRequest)
128
- // Catalog failed → unknown name → model default dot still renders.
129
- expect(rows[0].icon).toBeDefined()
130
- expect((rows[0].icon as unknown as { props: { style: { background: string } } }).props.style.background).toBe('#2f81f7')
131
- } finally {
132
- restore()
133
- }
134
- })
135
-
136
- it('skips dot injection on new host (alpha.2, drilled present) for ReferenceIcon compat', async () => {
137
- const source = skillSource(async () => [{ name: 'code-review', description: 'review code' }])
138
- const api = apiWith([{ name: 'code-review', modelInvocable: true }])
139
- const scope = scopeWith({ enabled: true, announceToAgent: true, showUseCount: true, showUseTime: true, showGroupSummary: true, dotModelColor: '#112233', dotUserColor: '#445566' })
140
-
141
123
  const restore = wrapSkillSource(source, api, scope)
142
124
  try {
143
125
  const rows = await source.candidates({ sessionId: 's1' as never }, { query: '', position: 'leading', drilled: false, signal: new AbortController().signal })
126
+ // A failed catalog only means no dots (unknown names are skipped); the
127
+ // menu itself must keep working.
144
128
  expect(rows).toHaveLength(1)
145
- expect(rows[0].icon).toBeUndefined()
146
- expect(rows[0].name).toBe('code-review')
129
+ expect(rows[0].name).toBe('lonely')
147
130
  } finally {
148
131
  restore()
149
132
  }