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/README.md +1 -1
- package/README.zh.md +1 -1
- package/lib/client.js +23 -47
- package/lib/client.js.map +1 -1
- package/lib/index.js +4 -5
- package/lib/types/client/SkillHubSettingsCard.d.ts +5 -3
- package/lib/types/client/icons.d.ts +4 -5
- package/lib/types/client/index.d.ts +4 -4
- package/lib/types/client/slash-dots.d.ts +17 -8
- package/lib/types/index.d.ts +4 -5
- package/package.json +29 -40
- package/src/client/SkillHubSettingsCard.tsx +5 -3
- package/src/client/icons.tsx +4 -5
- package/src/client/index.tsx +18 -16
- package/src/client/slash-dots.test.ts +16 -33
- package/src/client/slash-dots.tsx +27 -57
- package/src/index.ts +9 -11
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.
|
|
4514
|
-
*
|
|
4515
|
-
*
|
|
4516
|
-
*
|
|
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
|
|
6
|
-
*
|
|
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<'
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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 (
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
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
|
package/lib/types/index.d.ts
CHANGED
|
@@ -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.
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
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.
|
|
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.
|
|
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.
|
|
99
|
-
"@deepseek-ai/dsh-client-locale": ">=0.1.
|
|
100
|
-
"@deepseek-ai/dsh-client-runtime": ">=0.1.
|
|
101
|
-
"@deepseek-ai/dsh-client-store": ">=0.1.
|
|
102
|
-
"@deepseek-ai/dsh-client-ui-input-trigger": ">=0.1.
|
|
103
|
-
"@deepseek-ai/dsh-client-ui-settings": ">=0.1.
|
|
104
|
-
"@deepseek-ai/dsh-client-ui-slots": ">=0.1.
|
|
105
|
-
"@deepseek-ai/dsh-host-webserver": ">=0.1.
|
|
106
|
-
"@deepseek-ai/dsh-session": ">=0.1.
|
|
107
|
-
"@deepseek-ai/dsh-session-query": ">=0.1.
|
|
108
|
-
"@deepseek-ai/dsh-settings": ">=0.1.
|
|
109
|
-
"@deepseek-ai/dsh-skill": ">=0.1.
|
|
110
|
-
"@deepseek-ai/dsh-system-prompt": ">=0.1.
|
|
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.
|
|
125
|
-
"@deepseek-ai/dsh-client-locale": "^0.1.6-alpha.
|
|
126
|
-
"@deepseek-ai/dsh-client-store": "^0.1.6-alpha.
|
|
127
|
-
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.6-alpha.
|
|
128
|
-
"@deepseek-ai/dsh-client-ui-
|
|
129
|
-
"@deepseek-ai/dsh-client-ui-settings
|
|
130
|
-
"@deepseek-ai/dsh-client-ui-
|
|
131
|
-
"@deepseek-ai/dsh-
|
|
132
|
-
"@deepseek-ai/dsh-
|
|
133
|
-
"@deepseek-ai/dsh-session
|
|
134
|
-
"@deepseek-ai/dsh-
|
|
135
|
-
"@deepseek-ai/dsh-
|
|
136
|
-
"@deepseek-ai/dsh-
|
|
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
|
|
6
|
-
*
|
|
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<'
|
|
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 {
|
package/src/client/icons.tsx
CHANGED
|
@@ -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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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.
|
package/src/client/index.tsx
CHANGED
|
@@ -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 (
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
|
100
|
-
//
|
|
101
|
-
//
|
|
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('
|
|
104
|
-
name: '
|
|
105
|
-
key:
|
|
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:
|
|
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)
|
|
5
|
-
*
|
|
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('
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
//
|
|
107
|
-
expect(
|
|
108
|
-
|
|
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('
|
|
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].
|
|
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
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* the
|
|
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 {
|
|
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
|
-
*
|
|
96
|
-
*
|
|
97
|
-
* @param
|
|
98
|
-
* @
|
|
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
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
188
|
-
|
|
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.
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
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
|
-
//
|
|
104
|
-
//
|
|
105
|
-
// and the
|
|
106
|
-
//
|
|
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 })
|