dsh-skill-hub 0.3.13 → 0.3.14

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/lib/index.js CHANGED
@@ -4510,11 +4510,10 @@ const Config = z.object({
4510
4510
  statsScanMinutes: z.number().min(1).max(1440).default(HUB_CONFIG_DEFAULTS.statsScanMinutes)
4511
4511
  });
4512
4512
  /**
4513
- * Settings namespace hosting the hub's runtime config. Since dsh rc.7 the
4514
- * host serves every registered settings namespace to the web client (the
4515
- * dsh-host-apiproxy allowlist is gone), so the browser card and the settings
4516
- * page edit this namespace through the official settings transport, and the
4517
- * plugin consumes the same resolved value — one source of truth.
4513
+ * Settings namespace hosting the hub's runtime config. The host serves every
4514
+ * registered settings namespace to the web client, so the browser card and the
4515
+ * settings page edit this namespace through the official settings transport,
4516
+ * and the plugin consumes the same resolved value one source of truth.
4518
4517
  */
4519
4518
  const CONFIG_NAMESPACE = "dsh-skill-hub";
4520
4519
  /** Schema of the hub's settings namespace: the card's fields (booleans + optional dot colors). */
@@ -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
  import { type ReactElement } from 'react';
9
11
  import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
@@ -34,7 +36,7 @@ export interface SkillHubSettingsCardFace {
34
36
  resetField: (field: string) => void;
35
37
  }
36
38
  /** Props the slot renderer binds (locale copy + injected form actions). */
37
- export type SkillHubSettingsCardProps = PropsRuntime<'settings.plugin.item'> & PropsLocale<'dsh-skill-hub'> & InjectFace<SkillHubSettingsCardFace>;
39
+ export type SkillHubSettingsCardProps = PropsRuntime<'plugins.bundle.config'> & PropsLocale<'dsh-skill-hub'> & InjectFace<SkillHubSettingsCardFace>;
38
40
  /** Bridges the hub's config scope onto the card's staged form. */
39
41
  export declare class SkillHubSettingsCardController {
40
42
  private readonly form;
@@ -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.
@@ -6,10 +6,10 @@
6
6
  * surfaces, both through official slots (no DOM injection):
7
7
  * - a plugin-management card in the `settings.plugin.item` slot (Settings →
8
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);
9
+ * through the official settings transport (the Host serves every registered
10
+ * namespace to the web client, and the tab dispatches cards by namespace) —
11
+ * the family-bucket card pattern (PluginSettingsCard + CardForm vendored
12
+ * from dsh-task-board);
13
13
  * - a top-level Settings section (Settings → 技能) hosting the skill hub
14
14
  * panel: catalog, search, enable/disable, diagnostics, new-skill form.
15
15
  *
@@ -2,14 +2,15 @@
2
2
  * Slash-menu skill dots: puts the invocation-status dot (model-callable blue /
3
3
  * user-only green) in front of every skill candidate in the chat `/` menu.
4
4
  *
5
- * Mechanism (mirrors how dsh-at-file fills the menu icon slot): the candidate
6
- * menu's rows already render an optional `icon` slot (`MenuView` renders
7
- * `item.icon` in a 16×16 leading span when it's defined), but the core `/skill`
8
- * source (`dsh-client-ui-skill`) returns candidates without `icon`. This module
9
- * wraps that source's `candidates` and stamps each row with a colored dot,
10
- * reusing the same settings (dotModelColor / dotUserColor) and the same
11
- * `modelInvocable` classification the panel legend uses — so the chat menu and
12
- * the Settings 技能 panel stay in sync, and editing the color updates both.
5
+ * Mechanism: hooks the `/skill` source's `candidates` so the hub learns which
6
+ * rows are skill candidates and how each one classifies for model invocation,
7
+ * then injects the dot straight into the rendered option rows. The menu's
8
+ * `icon` slot is unusable for this — `MenuView` narrowed `icon` to an enum
9
+ * ('file' | 'folder' | 'session') and renders it through `ReferenceIcon`, so a
10
+ * custom element never reaches the DOM. Colors come from the same settings
11
+ * (dotModelColor / dotUserColor) and the same `modelInvocable` classification
12
+ * the panel legend uses so the chat menu and the Settings 技能 panel stay
13
+ * in sync, and editing the color updates both.
13
14
  *
14
15
  * The skill source is registered by the core plugin under the `name` "skill"
15
16
  * on the `/` trigger; re-registering the same name would throw, so this wraps
@@ -35,6 +36,14 @@ export declare function findSkillSource(service: InputTriggerServiceContract): I
35
36
  * catalog wins after reconnect; exported for deterministic unit tests.
36
37
  */
37
38
  export declare function resetModelCache(): void;
39
+ /**
40
+ * Wrap the core skill source so every menu row carries the invocation dot.
41
+ * Exported for unit tests; production wiring goes through setupSkillSlashDots.
42
+ * @param source - the registered `/skill` source.
43
+ * @param api - hub browser API for the modelInvocable lookup.
44
+ * @param scope - hub settings scope for the dot colors.
45
+ * @returns a disposer restoring the original candidates.
46
+ */
38
47
  export declare function wrapSkillSource(source: InputTriggerSource, api: SkillHubApi, scope: SettingsScope<HubSettingsValue>): () => void;
39
48
  /**
40
49
  * Mount the slash-menu dots on the registered `/skill` source. Idempotent and
@@ -33,11 +33,10 @@ export interface Config {
33
33
  }
34
34
  export declare const Config: z<Config>;
35
35
  /**
36
- * Settings namespace hosting the hub's runtime config. Since dsh rc.7 the
37
- * host serves every registered settings namespace to the web client (the
38
- * dsh-host-apiproxy allowlist is gone), so the browser card and the settings
39
- * page edit this namespace through the official settings transport, and the
40
- * plugin consumes the same resolved value — one source of truth.
36
+ * Settings namespace hosting the hub's runtime config. The host serves every
37
+ * registered settings namespace to the web client, so the browser card and the
38
+ * settings page edit this namespace through the official settings transport,
39
+ * and the plugin consumes the same resolved value one source of truth.
41
40
  */
42
41
  export declare const CONFIG_NAMESPACE: SettingsNamespace;
43
42
  /** Schema of the hub's settings namespace: the card's fields (booleans + optional dot colors). */
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.14",
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.6-alpha.2"
77
65
  ]
78
66
  },
79
67
  "capability": {
@@ -95,19 +83,19 @@
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.6-alpha.2 <0.2.0-0",
87
+ "@deepseek-ai/dsh-client-locale": ">=0.1.6-alpha.2 <0.2.0-0",
88
+ "@deepseek-ai/dsh-client-runtime": ">=0.1.6-alpha.2 <0.2.0-0",
89
+ "@deepseek-ai/dsh-client-store": ">=0.1.6-alpha.2 <0.2.0-0",
90
+ "@deepseek-ai/dsh-client-ui-input-trigger": ">=0.1.6-alpha.2 <0.2.0-0",
91
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.6-alpha.2 <0.2.0-0",
92
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.6-alpha.2 <0.2.0-0",
93
+ "@deepseek-ai/dsh-host-webserver": ">=0.1.6-alpha.2 <0.2.0-0",
94
+ "@deepseek-ai/dsh-session": ">=0.1.6-alpha.2 <0.2.0-0",
95
+ "@deepseek-ai/dsh-session-query": ">=0.1.6-alpha.2 <0.2.0-0",
96
+ "@deepseek-ai/dsh-settings": ">=0.1.6-alpha.2 <0.2.0-0",
97
+ "@deepseek-ai/dsh-skill": ">=0.1.6-alpha.2 <0.2.0-0",
98
+ "@deepseek-ai/dsh-system-prompt": ">=0.1.6-alpha.2 <0.2.0-0",
111
99
  "react": "^18.2.0",
112
100
  "react-dom": "^18.2.0"
113
101
  },
@@ -121,19 +109,20 @@
121
109
  },
122
110
  "devDependencies": {
123
111
  "@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",
112
+ "@deepseek-ai/dsh-client-connection": "^0.1.6-alpha.2",
113
+ "@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.2",
114
+ "@deepseek-ai/dsh-client-store": "^0.1.6-alpha.2",
115
+ "@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.6-alpha.2",
116
+ "@deepseek-ai/dsh-client-ui-plugin-manager": "^0.1.6-alpha.2",
117
+ "@deepseek-ai/dsh-client-ui-settings": "^0.1.6-alpha.2",
118
+ "@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.6-alpha.2",
119
+ "@deepseek-ai/dsh-client-ui-slots": "^0.1.6-alpha.2",
120
+ "@deepseek-ai/dsh-host-webserver": "^0.1.6-alpha.2",
121
+ "@deepseek-ai/dsh-session": "^0.1.6-alpha.2",
122
+ "@deepseek-ai/dsh-session-query": "^0.1.6-alpha.2",
123
+ "@deepseek-ai/dsh-settings": "^0.1.6-alpha.2",
124
+ "@deepseek-ai/dsh-skill": "^0.1.6-alpha.2",
125
+ "@deepseek-ai/dsh-system-prompt": "^0.1.6-alpha.2",
137
126
  "@types/js-yaml": "^4.0.9",
138
127
  "@types/node": "^22.20.0",
139
128
  "@types/react": "~18.3.1",
@@ -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.
@@ -6,10 +6,10 @@
6
6
  * surfaces, both through official slots (no DOM injection):
7
7
  * - a plugin-management card in the `settings.plugin.item` slot (Settings →
8
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);
9
+ * through the official settings transport (the Host serves every registered
10
+ * namespace to the web client, and the tab dispatches cards by namespace) —
11
+ * the family-bucket card pattern (PluginSettingsCard + CardForm vendored
12
+ * from dsh-task-board);
13
13
  * - a top-level Settings section (Settings → 技能) hosting the skill hub
14
14
  * panel: catalog, search, enable/disable, diagnostics, new-skill form.
15
15
  *
@@ -29,14 +29,15 @@ import type {} from '@deepseek-ai/dsh-client-locale/client'
29
29
  import type {} from '@deepseek-ai/dsh-client-ui-settings/client'
30
30
  // Type-only: pulls the LocaleNamespaceMap merge table.
31
31
  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
32
  // Type-only: pulls the Context merge for ctx.inputTriggers (slash-dots wiring).
35
33
  import type {} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
34
+ // Type-only: pulls the plugin-manager SlotMap merge (plugins.bundle.config).
35
+ import type {} from '@deepseek-ai/dsh-client-ui-plugin-manager/client'
36
36
  // Type-only: pulls the connection/reset event.
37
37
  import type {} from '@deepseek-ai/dsh-client-connection/client'
38
38
 
39
- // 0.1.2-alpha.2: slots service moved to ui-renderer; provide local augmentation for build.
39
+ // The slots service is owned by ui-renderer, which this build does not depend
40
+ // on; augment the Context locally so the browser half typechecks.
40
41
  declare module '@deepseek-ai/cordis' {
41
42
  interface Context {
42
43
  slots: any
@@ -85,8 +86,8 @@ export function apply(ctx: ClientContext): void {
85
86
 
86
87
  // The card edits the hub's settings namespace through the official
87
88
  // 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).
89
+ // whose key the Host serves, and the Host serves every registered namespace,
90
+ // so this is what makes the card appear (and stay in sync).
90
91
  // Single scope instance reused for both the card and slash-dots to avoid
91
92
  // duplicate subscriptions (review #3).
92
93
  const scope = ctx.settingsScope.bind<HubSettingsValue>({ namespace: NS })
@@ -96,17 +97,18 @@ export function apply(ctx: ClientContext): void {
96
97
  // inject 含 inputTriggers 保证 fiber 就绪后再 wrap,slash-dots 内的 undefined 防御仅用于单元测试 mock
97
98
  ctx.effect(() => setupSkillSlashDots(ctx, api, scope), 'dsh-skill-hub: slash dots')
98
99
 
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.
100
+ // Plugin configuration: the hub renders its own form on the bundle's page in
101
+ // the Plugins manager (sidebar 插件 → dsh-skill-hub), keyed by the bundle's
102
+ // package name the manager dispatches that key with `view: 'page'`.
103
+ // No extra settings tab is contributed.
102
104
  ctx.effect(
103
- () => ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({
104
- name: 'settings.plugin.item',
105
- key: NS,
105
+ () => ctx.slots.inject('plugins.bundle.config', () => ctx.slots.register({
106
+ name: 'plugins.bundle.config',
107
+ key: 'dsh-skill-hub',
106
108
  locale: NS,
107
109
  inject: () => settingsCard.inject(),
108
110
  }, SkillHubSettingsCard)),
109
- 'dsh-skill-hub: settings card',
111
+ 'dsh-skill-hub: plugin config',
110
112
  )
111
113
 
112
114
  // Top-level Settings section: the skill management page.
@@ -1,8 +1,10 @@
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'
@@ -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
  }
@@ -2,14 +2,15 @@
2
2
  * Slash-menu skill dots: puts the invocation-status dot (model-callable blue /
3
3
  * user-only green) in front of every skill candidate in the chat `/` menu.
4
4
  *
5
- * Mechanism (mirrors how dsh-at-file fills the menu icon slot): the candidate
6
- * menu's rows already render an optional `icon` slot (`MenuView` renders
7
- * `item.icon` in a 16×16 leading span when it's defined), but the core `/skill`
8
- * source (`dsh-client-ui-skill`) returns candidates without `icon`. This module
9
- * wraps that source's `candidates` and stamps each row with a colored dot,
10
- * reusing the same settings (dotModelColor / dotUserColor) and the same
11
- * `modelInvocable` classification the panel legend uses — so the chat menu and
12
- * the Settings 技能 panel stay in sync, and editing the color updates both.
5
+ * Mechanism: hooks the `/skill` source's `candidates` so the hub learns which
6
+ * rows are skill candidates and how each one classifies for model invocation,
7
+ * then injects the dot straight into the rendered option rows. The menu's
8
+ * `icon` slot is unusable for this — `MenuView` narrowed `icon` to an enum
9
+ * ('file' | 'folder' | 'session') and renders it through `ReferenceIcon`, so a
10
+ * custom element never reaches the DOM. Colors come from the same settings
11
+ * (dotModelColor / dotUserColor) and the same `modelInvocable` classification
12
+ * the panel legend uses so the chat menu and the Settings 技能 panel stay
13
+ * in sync, and editing the color updates both.
13
14
  *
14
15
  * The skill source is registered by the core plugin under the `name` "skill"
15
16
  * on the `/` trigger; re-registering the same name would throw, so this wraps
@@ -24,8 +25,7 @@ import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
24
25
  import type {} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
25
26
  // Type-only: pulls the connection/reset event.
26
27
  import type {} from '@deepseek-ai/dsh-client-connection/client'
27
- import type { InputTriggerCandidate, InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
28
- import { createElement } from 'react'
28
+ import type { InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
29
29
  import type { HubSettingsValue } from '../protocol.ts'
30
30
  import type { SkillHubApi } from './api.ts'
31
31
  import { DEFAULT_DOT_MODEL_COLOR, DEFAULT_DOT_USER_COLOR } from './panel/format.ts'
@@ -92,37 +92,11 @@ async function modelInvocableMap(api: SkillHubApi): Promise<Map<string, boolean>
92
92
  }
93
93
 
94
94
  /**
95
- * One menu-row dot element. Inline span so it needs no CSS module; the
96
- * candidate menu centers it inside its 16×16 leading icon slot.
97
- * @param color - the dot's background color.
98
- * @returns a React node (memory-only, never crosses the Host boundary).
99
- */
100
- function dotIcon(color: string): InputTriggerCandidate['icon'] {
101
- return createElement('span', {
102
- 'aria-hidden': true,
103
- style: {
104
- display: 'inline-block',
105
- width: 6,
106
- height: 6,
107
- borderRadius: 3,
108
- background: color,
109
- flex: 'none',
110
- },
111
- }) as unknown as InputTriggerCandidate['icon']
112
- }
113
-
114
- /**
115
- * Wrap the core skill source so every menu row carries the invocation dot.
116
- * Exported for unit tests; production wiring goes through setupSkillSlashDots.
117
- * @param source - the registered `/skill` source.
118
- * @param api - hub browser API for the modelInvocable lookup.
119
- * @param scope - hub settings scope for the dot colors.
120
- * @returns a disposer restoring the original candidates.
121
- */
122
- /**
123
- * DOM 兜底:在 alpha.2 新版 MenuView(icon 仅枚举)下通过直接操作
124
- * 已渲染的 `[role="option"]` 列表注入彩色点,绕过 `icon` 限制。
125
- * 旧版仍走 `icon` 注入,此处仅为新版。
95
+ * 通过直接操作已渲染的 `[role="option"]` 列表注入彩色点,
96
+ * 绕过 MenuView `icon` 的枚举限制。
97
+ * @param modelByName - 技能名 模型可调用性;不在表内的候选不注点。
98
+ * @param modelColor - 模型可调用技能的点色。
99
+ * @param userColor - 仅用户可调用技能的点色。
126
100
  */
127
101
  function injectDotsViaDOM(modelByName: Map<string, boolean>, modelColor: string, userColor: string): void {
128
102
  if (typeof document === 'undefined' || typeof requestAnimationFrame === 'undefined') return
@@ -145,7 +119,7 @@ function injectDotsViaDOM(modelByName: Map<string, boolean>, modelColor: string,
145
119
  // 仅对技能生效:非技能候选(/command、@file 等)不在 catalog,不注点
146
120
  if (!modelByName.has(name)) continue
147
121
  const color = (modelByName.get(name) ?? true) ? modelColor : userColor
148
- // 复刻旧版 icon 槽:16×16 容器居中 6px 点,与升级前 `dotIcon` 在 `itemIcon` 内的效果一致
122
+ // 布局对齐菜单行原有的 icon 槽:16×16 容器居中 6px
149
123
  const wrapper = document.createElement('span')
150
124
  wrapper.setAttribute('data-skill-dot', '')
151
125
  wrapper.setAttribute('aria-hidden', 'true')
@@ -163,37 +137,33 @@ function injectDotsViaDOM(modelByName: Map<string, boolean>, modelColor: string,
163
137
  dot.style.background = color
164
138
  dot.style.flex = 'none'
165
139
  wrapper.appendChild(dot)
166
- // 新版无 icon 槽,直接插在名称前,与旧版 `itemIcon` 位置一致
140
+ // 直接插在名称前,与菜单行原有 icon 槽的位置一致
167
141
  nameEl.parentElement?.insertBefore(wrapper, nameEl)
168
142
  }
169
143
  }
170
144
  requestAnimationFrame(() => setTimeout(run, 0))
171
145
  }
172
146
 
147
+ /**
148
+ * Wrap the core skill source so every menu row carries the invocation dot.
149
+ * Exported for unit tests; production wiring goes through setupSkillSlashDots.
150
+ * @param source - the registered `/skill` source.
151
+ * @param api - hub browser API for the modelInvocable lookup.
152
+ * @param scope - hub settings scope for the dot colors.
153
+ * @returns a disposer restoring the original candidates.
154
+ */
173
155
  export function wrapSkillSource(source: InputTriggerSource, api: SkillHubApi, scope: SettingsScope<HubSettingsValue>): () => void {
174
156
  const original = source.candidates
175
157
  source.candidates = async (session, req) => {
176
158
  const items = await original(session, req)
177
159
  if (req.signal.aborted) return items
178
- // Dual-compat: 0.1.2-alpha.2 的 MenuView 将 icon 收窄为 'file'|'folder'|'session'
179
- // 并通过 ReferenceIcon 渲染,旧版的自定义 ReactElement 点已无法展示。
180
- // 以 `drilled` 是否存在探测新版(alpha.2 必有,rc 无)。
181
- const isNewHost = req !== null && typeof req === 'object' && 'drilled' in (req as unknown as Record<string, unknown>)
182
160
  const modelByName = await modelInvocableMap(api)
183
161
  if (req.signal.aborted) return items
184
162
  const snapshot = scope.getSnapshot()
185
163
  const modelColor = snapshot.value?.dotModelColor ?? DEFAULT_DOT_MODEL_COLOR
186
164
  const userColor = snapshot.value?.dotUserColor ?? DEFAULT_DOT_USER_COLOR
187
- if (isNewHost) {
188
- // 新版:不通过 icon(枚举限制),改为 DOM 注入
189
- injectDotsViaDOM(modelByName, modelColor, userColor)
190
- return items
191
- }
192
- // 旧版:保持原有 icon 注入
193
- return items.map((item) => ({
194
- ...item,
195
- icon: dotIcon((modelByName.get(item.name) ?? true) ? modelColor : userColor),
196
- }))
165
+ injectDotsViaDOM(modelByName, modelColor, userColor)
166
+ return items
197
167
  }
198
168
  return () => {
199
169
  source.candidates = original
package/src/index.ts CHANGED
@@ -62,11 +62,10 @@ export const Config: z<Config> = z.object({
62
62
  })
63
63
 
64
64
  /**
65
- * Settings namespace hosting the hub's runtime config. Since dsh rc.7 the
66
- * host serves every registered settings namespace to the web client (the
67
- * dsh-host-apiproxy allowlist is gone), so the browser card and the settings
68
- * page edit this namespace through the official settings transport, and the
69
- * plugin consumes the same resolved value — one source of truth.
65
+ * Settings namespace hosting the hub's runtime config. The host serves every
66
+ * registered settings namespace to the web client, so the browser card and the
67
+ * settings page edit this namespace through the official settings transport,
68
+ * and the plugin consumes the same resolved value one source of truth.
70
69
  */
71
70
  export const CONFIG_NAMESPACE = 'dsh-skill-hub' as SettingsNamespace
72
71
 
@@ -99,12 +98,11 @@ export const SKILL_HUB_GUIDANCE = [
99
98
  * @param config - resolved plugin config (schema defaults applied by the loader).
100
99
  */
101
100
  export function apply(ctx: Context, config?: Config): void {
102
- // The hub's runtime configuration lives in dsh's own settings service:
103
- // since rc.7 the host serves every registered settings namespace to the
104
- // web client (dsh-host-apiproxy's allowlist is gone), so the browser card
105
- // and the config route edit this namespace through the official settings
106
- // transport, and the host consumes the very same resolved value one
107
- // source of truth. The cordis composition entry seeds the base layer; the
101
+ // The hub's runtime configuration lives in dsh's own settings service: the
102
+ // host serves every registered settings namespace to the web client, so the
103
+ // browser card and the config route edit this namespace through the official
104
+ // settings transport, and the host consumes the very same resolved value
105
+ // one source of truth. The cordis composition entry seeds the base layer; the
108
106
  // sidecar config survives only as a one-time migration source below.
109
107
  const base = config ?? {}
110
108
  const settingsScope = ctx.settings.register(CONFIG_NAMESPACE, HubSettingsSchema, { base })