dsh-skill-hub 0.3.0 → 0.3.2
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 +68 -231
- package/README.zh.md +67 -205
- package/lib/client.js +739 -2
- package/lib/client.js.map +1 -1
- package/lib/index.js +2 -3
- package/lib/types/client/SkillHubSettingsCard.d.ts +1 -1
- package/lib/types/client/index.d.ts +6 -1
- package/lib/types/client/settings-form.d.ts +1 -1
- package/lib/types/client/slash-dots.d.ts +2 -9
- package/lib/types/index.d.ts +2 -1
- package/package.json +38 -29
- package/src/client/SkillHubSettingsCard.tsx +1 -1
- package/src/client/index.tsx +10 -1
- package/src/client/settings-form.ts +2 -1
- package/src/client/slash-dots.test.ts +24 -6
- package/src/client/slash-dots.tsx +55 -1
- package/src/index.ts +2 -2
package/lib/index.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
|
-
import { settingsNamespace } from "@deepseek-ai/dsh-settings";
|
|
3
2
|
import z from "schemastery";
|
|
4
3
|
import { mkdir, mkdtemp, readFile, readdir, rename, rm, stat, writeFile } from "node:fs/promises";
|
|
5
4
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
@@ -3621,7 +3620,7 @@ const Config = z.object({
|
|
|
3621
3620
|
* page edit this namespace through the official settings transport, and the
|
|
3622
3621
|
* plugin consumes the same resolved value — one source of truth.
|
|
3623
3622
|
*/
|
|
3624
|
-
const CONFIG_NAMESPACE =
|
|
3623
|
+
const CONFIG_NAMESPACE = "dsh-skill-hub";
|
|
3625
3624
|
/** Schema of the hub's settings namespace: the card's fields (booleans + optional dot colors). */
|
|
3626
3625
|
const HubSettingsSchema = z.object({
|
|
3627
3626
|
enabled: z.boolean().default(HUB_CONFIG_DEFAULTS.enabled),
|
|
@@ -3654,7 +3653,7 @@ function apply(ctx, config) {
|
|
|
3654
3653
|
let providerControl;
|
|
3655
3654
|
let stats;
|
|
3656
3655
|
const saved = () => {
|
|
3657
|
-
return ctx.settings.describe().find((entry) => entry.ns ===
|
|
3656
|
+
return ctx.settings.describe().find((entry) => entry.ns === "dsh-skill-hub")?.user ?? {};
|
|
3658
3657
|
};
|
|
3659
3658
|
const updateConfig = async (patch) => {
|
|
3660
3659
|
const user = { ...saved() };
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* namespace, so the plugin shows up in Settings → 插件 on dsh rc.7+.
|
|
7
7
|
*/
|
|
8
8
|
import { type ReactElement } from 'react';
|
|
9
|
-
import type { SnapshotStore } from '@deepseek-ai/dsh-client-
|
|
9
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store';
|
|
10
10
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots';
|
|
11
11
|
import { type CardShell, type FieldState, type FormScope } from './settings-form.ts';
|
|
12
12
|
import { DEFAULT_DOT_MODEL_COLOR, DEFAULT_DOT_USER_COLOR } from './panel/format.ts';
|
|
@@ -21,7 +21,12 @@
|
|
|
21
21
|
* what cordis loading needs plus types only — all value exports stay
|
|
22
22
|
* internal.
|
|
23
23
|
*/
|
|
24
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
24
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
25
|
+
declare module '@deepseek-ai/cordis' {
|
|
26
|
+
interface Context {
|
|
27
|
+
slots: any;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
25
30
|
import { type HubKey } from './locales.ts';
|
|
26
31
|
declare module '@deepseek-ai/dsh-client-ui-slots' {
|
|
27
32
|
interface LocaleNamespaceMap {
|
|
@@ -72,7 +72,7 @@ export declare class CardForm {
|
|
|
72
72
|
/** @param scope - the bound settings scope for this card's namespace. */
|
|
73
73
|
constructor(scope: FormScope, specs: FieldSpec[]);
|
|
74
74
|
/** Publish a projection of this form, rebuilt whenever the scope or a draft changes. */
|
|
75
|
-
bind<T>(project: () => T): import("@deepseek-ai/dsh-client-
|
|
75
|
+
bind<T>(project: () => T): import("@deepseek-ai/dsh-client-store").SnapshotStore<T>;
|
|
76
76
|
/** Read the card-level state: what the Host serves, and what a save would do. */
|
|
77
77
|
shell(): CardShell;
|
|
78
78
|
/** Read one field's state from the effective section and its staged draft. */
|
|
@@ -17,7 +17,8 @@
|
|
|
17
17
|
* source registry, which the frozen contract exposes only as `registerSource`
|
|
18
18
|
* / `sessionOf` — the lookup below is defensive: it never throws).
|
|
19
19
|
*/
|
|
20
|
-
import type { ClientContext
|
|
20
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis';
|
|
21
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client';
|
|
21
22
|
import type { InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client';
|
|
22
23
|
import type { HubSettingsValue } from '../protocol.ts';
|
|
23
24
|
import type { SkillHubApi } from './api.ts';
|
|
@@ -34,14 +35,6 @@ export declare function findSkillSource(service: InputTriggerServiceContract): I
|
|
|
34
35
|
* catalog wins after reconnect; exported for deterministic unit tests.
|
|
35
36
|
*/
|
|
36
37
|
export declare function resetModelCache(): void;
|
|
37
|
-
/**
|
|
38
|
-
* Wrap the core skill source so every menu row carries the invocation dot.
|
|
39
|
-
* Exported for unit tests; production wiring goes through setupSkillSlashDots.
|
|
40
|
-
* @param source - the registered `/skill` source.
|
|
41
|
-
* @param api - hub browser API for the modelInvocable lookup.
|
|
42
|
-
* @param scope - hub settings scope for the dot colors.
|
|
43
|
-
* @returns a disposer restoring the original candidates.
|
|
44
|
-
*/
|
|
45
38
|
export declare function wrapSkillSource(source: InputTriggerSource, api: SkillHubApi, scope: SettingsScope<HubSettingsValue>): () => void;
|
|
46
39
|
/**
|
|
47
40
|
* Mount the slash-menu dots on the registered `/skill` source. Idempotent and
|
package/lib/types/index.d.ts
CHANGED
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* source changes.
|
|
8
8
|
*/
|
|
9
9
|
import type { Context } from '@deepseek-ai/cordis';
|
|
10
|
+
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings';
|
|
10
11
|
import z from 'schemastery';
|
|
11
12
|
import { type HubSettingsValue } from './protocol.ts';
|
|
12
13
|
/** Stable cordis plugin name (matches cordis.patch.yml insert id). */
|
|
@@ -38,7 +39,7 @@ export declare const Config: z<Config>;
|
|
|
38
39
|
* page edit this namespace through the official settings transport, and the
|
|
39
40
|
* plugin consumes the same resolved value — one source of truth.
|
|
40
41
|
*/
|
|
41
|
-
export declare const CONFIG_NAMESPACE:
|
|
42
|
+
export declare const CONFIG_NAMESPACE: SettingsNamespace;
|
|
42
43
|
/** Schema of the hub's settings namespace: the card's fields (booleans + optional dot colors). */
|
|
43
44
|
export declare const HubSettingsSchema: z<HubSettingsValue>;
|
|
44
45
|
/** Model-facing announcement: plugin presence, capabilities, and limits. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-skill-hub",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
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": {
|
|
@@ -30,7 +30,6 @@
|
|
|
30
30
|
},
|
|
31
31
|
"client": {
|
|
32
32
|
"inject": [
|
|
33
|
-
"@deepseek-ai/dsh-client-runtime",
|
|
34
33
|
"@deepseek-ai/dsh-client-connection",
|
|
35
34
|
"@deepseek-ai/dsh-client-ui-settings"
|
|
36
35
|
],
|
|
@@ -63,7 +62,8 @@
|
|
|
63
62
|
"compatibility": {
|
|
64
63
|
"dshVersions": [
|
|
65
64
|
"0.1.0-rc.7",
|
|
66
|
-
"0.1.1-rc.2"
|
|
65
|
+
"0.1.1-rc.2",
|
|
66
|
+
"0.1.2-alpha.2"
|
|
67
67
|
]
|
|
68
68
|
},
|
|
69
69
|
"capability": {
|
|
@@ -84,37 +84,46 @@
|
|
|
84
84
|
"schemastery": "^3.18.0"
|
|
85
85
|
},
|
|
86
86
|
"peerDependencies": {
|
|
87
|
-
"@deepseek-ai/cordis": "^4.0.1",
|
|
88
|
-
"@deepseek-ai/dsh-client-connection": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
|
|
89
|
-
"@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
|
|
87
|
+
"@deepseek-ai/cordis": "^4.0.1 || ^4.0.2",
|
|
88
|
+
"@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",
|
|
89
|
+
"@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",
|
|
90
90
|
"@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
|
|
91
|
-
"@deepseek-ai/dsh-client-
|
|
92
|
-
"@deepseek-ai/dsh-client-ui-
|
|
93
|
-
"@deepseek-ai/dsh-client-ui-
|
|
94
|
-
"@deepseek-ai/dsh-
|
|
95
|
-
"@deepseek-ai/dsh-
|
|
96
|
-
"@deepseek-ai/dsh-session
|
|
97
|
-
"@deepseek-ai/dsh-
|
|
98
|
-
"@deepseek-ai/dsh-
|
|
99
|
-
"@deepseek-ai/dsh-
|
|
91
|
+
"@deepseek-ai/dsh-client-store": ">=0.1.2-alpha.2 <0.2.0-0",
|
|
92
|
+
"@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",
|
|
93
|
+
"@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",
|
|
94
|
+
"@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",
|
|
95
|
+
"@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",
|
|
96
|
+
"@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",
|
|
97
|
+
"@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",
|
|
98
|
+
"@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",
|
|
99
|
+
"@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",
|
|
100
|
+
"@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",
|
|
100
101
|
"react": "^18.2.0",
|
|
101
102
|
"react-dom": "^18.2.0"
|
|
102
103
|
},
|
|
104
|
+
"peerDependenciesMeta": {
|
|
105
|
+
"@deepseek-ai/dsh-client-runtime": {
|
|
106
|
+
"optional": true
|
|
107
|
+
},
|
|
108
|
+
"@deepseek-ai/dsh-client-store": {
|
|
109
|
+
"optional": true
|
|
110
|
+
}
|
|
111
|
+
},
|
|
103
112
|
"devDependencies": {
|
|
104
|
-
"@deepseek-ai/cordis": "^4.0.
|
|
105
|
-
"@deepseek-ai/dsh-client-connection": "
|
|
106
|
-
"@deepseek-ai/dsh-client-locale": "
|
|
107
|
-
"@deepseek-ai/dsh-client-
|
|
108
|
-
"@deepseek-ai/dsh-client-ui-input-trigger": "
|
|
109
|
-
"@deepseek-ai/dsh-client-ui-settings": "
|
|
110
|
-
"@deepseek-ai/dsh-client-ui-settings-plugins": "
|
|
111
|
-
"@deepseek-ai/dsh-client-ui-slots": "
|
|
112
|
-
"@deepseek-ai/dsh-host-webserver": "
|
|
113
|
-
"@deepseek-ai/dsh-session": "
|
|
114
|
-
"@deepseek-ai/dsh-session-query": "
|
|
115
|
-
"@deepseek-ai/dsh-settings": "
|
|
116
|
-
"@deepseek-ai/dsh-skill": "
|
|
117
|
-
"@deepseek-ai/dsh-system-prompt": "
|
|
113
|
+
"@deepseek-ai/cordis": "^4.0.2",
|
|
114
|
+
"@deepseek-ai/dsh-client-connection": "^0.1.2-alpha.2",
|
|
115
|
+
"@deepseek-ai/dsh-client-locale": "^0.1.2-alpha.2",
|
|
116
|
+
"@deepseek-ai/dsh-client-store": "^0.1.2-alpha.2",
|
|
117
|
+
"@deepseek-ai/dsh-client-ui-input-trigger": "^0.1.2-alpha.2",
|
|
118
|
+
"@deepseek-ai/dsh-client-ui-settings": "^0.1.2-alpha.2",
|
|
119
|
+
"@deepseek-ai/dsh-client-ui-settings-plugins": "^0.1.2-alpha.2",
|
|
120
|
+
"@deepseek-ai/dsh-client-ui-slots": "^0.1.2-alpha.2",
|
|
121
|
+
"@deepseek-ai/dsh-host-webserver": "^0.1.2-alpha.2",
|
|
122
|
+
"@deepseek-ai/dsh-session": "^0.1.2-alpha.2",
|
|
123
|
+
"@deepseek-ai/dsh-session-query": "^0.1.2-alpha.2",
|
|
124
|
+
"@deepseek-ai/dsh-settings": "^0.1.2-alpha.2",
|
|
125
|
+
"@deepseek-ai/dsh-skill": "^0.1.2-alpha.2",
|
|
126
|
+
"@deepseek-ai/dsh-system-prompt": "^0.1.2-alpha.2",
|
|
118
127
|
"@types/js-yaml": "^4.0.9",
|
|
119
128
|
"@types/node": "^22.20.0",
|
|
120
129
|
"@types/react": "~18.3.1",
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { useEffect, useState, type ReactElement } from 'react'
|
|
10
|
-
import type { SnapshotStore } from '@deepseek-ai/dsh-client-
|
|
10
|
+
import type { SnapshotStore } from '@deepseek-ai/dsh-client-store'
|
|
11
11
|
import type { InjectFace, PropsLocale, PropsRuntime } from '@deepseek-ai/dsh-client-ui-slots'
|
|
12
12
|
import { SkillHubApi } from './api.ts'
|
|
13
13
|
import { ColorField, NumberField, PluginSettingsCard, SwitchField } from './settings-card.tsx'
|
package/src/client/index.tsx
CHANGED
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
* internal.
|
|
23
23
|
*/
|
|
24
24
|
|
|
25
|
-
import type { ClientContext } from '@deepseek-ai/
|
|
25
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
26
26
|
// Type-only: pulls the locale plugin's Context merge (ctx.locale).
|
|
27
27
|
import type {} from '@deepseek-ai/dsh-client-locale/client'
|
|
28
28
|
// Type-only: pulls the settings-scope service merge and the settings.section slot.
|
|
@@ -33,6 +33,15 @@ import type {} from '@deepseek-ai/dsh-client-ui-slots'
|
|
|
33
33
|
import type {} from '@deepseek-ai/dsh-client-ui-settings-plugins/client'
|
|
34
34
|
// Type-only: pulls the Context merge for ctx.inputTriggers (slash-dots wiring).
|
|
35
35
|
import type {} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
|
36
|
+
// Type-only: pulls the connection/reset event.
|
|
37
|
+
import type {} from '@deepseek-ai/dsh-client-connection/client'
|
|
38
|
+
|
|
39
|
+
// 0.1.2-alpha.2: slots service moved to ui-renderer; provide local augmentation for build.
|
|
40
|
+
declare module '@deepseek-ai/cordis' {
|
|
41
|
+
interface Context {
|
|
42
|
+
slots: any
|
|
43
|
+
}
|
|
44
|
+
}
|
|
36
45
|
import type { HubSettingsValue } from '../protocol.ts'
|
|
37
46
|
import { SkillHubApi } from './api.ts'
|
|
38
47
|
import { en, zh, type HubKey } from './locales.ts'
|
|
@@ -9,7 +9,8 @@
|
|
|
9
9
|
* land keeps its drafts, so the user can correct them instead of retyping.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
-
import { createSnapshotStore
|
|
12
|
+
import { createSnapshotStore } from '@deepseek-ai/dsh-client-store'
|
|
13
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
13
14
|
|
|
14
15
|
/** One staged field: its draft text plus the clear/reset marker. */
|
|
15
16
|
interface StagedEdit {
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { beforeEach, describe, expect, it } from 'vitest'
|
|
9
|
-
import type { SettingsScope } from '@deepseek-ai/dsh-client-
|
|
9
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
10
10
|
import type { InputTriggerCandidate, InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
|
11
11
|
import type { HubSettingsValue } from '../protocol.ts'
|
|
12
12
|
import type { SkillHubApi } from './api.ts'
|
|
@@ -39,7 +39,8 @@ function scopeWith(value: HubSettingsValue): SettingsScope<HubSettingsValue> {
|
|
|
39
39
|
subscribe: () => () => {},
|
|
40
40
|
set: async () => {},
|
|
41
41
|
unset: async () => {},
|
|
42
|
-
|
|
42
|
+
mutate: async () => {},
|
|
43
|
+
} as unknown as SettingsScope<HubSettingsValue>
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
/** Fake hub api whose catalog lists the given skills. */
|
|
@@ -84,7 +85,7 @@ describe('findSkillSource', () => {
|
|
|
84
85
|
})
|
|
85
86
|
|
|
86
87
|
describe('wrapSkillSource', () => {
|
|
87
|
-
it('adds a model-colored dot to model-callable skills and a user-colored dot to user-only ones', async () => {
|
|
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 () => {
|
|
88
89
|
const source = skillSource(async () => [
|
|
89
90
|
{ name: 'code-review', description: 'review code' },
|
|
90
91
|
{ name: 'personal-note', description: 'only me' },
|
|
@@ -98,7 +99,8 @@ describe('wrapSkillSource', () => {
|
|
|
98
99
|
|
|
99
100
|
const restore = wrapSkillSource(source, api, scope)
|
|
100
101
|
try {
|
|
101
|
-
|
|
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)
|
|
102
104
|
|
|
103
105
|
expect(rows).toHaveLength(3)
|
|
104
106
|
// Model-callable → model color.
|
|
@@ -115,14 +117,14 @@ describe('wrapSkillSource', () => {
|
|
|
115
117
|
}
|
|
116
118
|
})
|
|
117
119
|
|
|
118
|
-
it('falls back to the default colors when settings omit them and catalog fails', async () => {
|
|
120
|
+
it('falls back to the default colors when settings omit them and catalog fails (old host)', async () => {
|
|
119
121
|
const source = skillSource(async () => [{ name: 'lonely', description: '' }])
|
|
120
122
|
const api = { catalog: async () => { throw new Error('route down') } } as unknown as SkillHubApi
|
|
121
123
|
const scope = scopeWith({ enabled: true, announceToAgent: true, showUseCount: true, showUseTime: true, showGroupSummary: true })
|
|
122
124
|
|
|
123
125
|
const restore = wrapSkillSource(source, api, scope)
|
|
124
126
|
try {
|
|
125
|
-
const rows = await source.candidates({ sessionId: 's1' as never }, { query: '', position: 'leading', signal: new AbortController().signal })
|
|
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)
|
|
126
128
|
// Catalog failed → unknown name → model default dot still renders.
|
|
127
129
|
expect(rows[0].icon).toBeDefined()
|
|
128
130
|
expect((rows[0].icon as unknown as { props: { style: { background: string } } }).props.style.background).toBe('#2f81f7')
|
|
@@ -131,6 +133,22 @@ describe('wrapSkillSource', () => {
|
|
|
131
133
|
}
|
|
132
134
|
})
|
|
133
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
|
+
const restore = wrapSkillSource(source, api, scope)
|
|
142
|
+
try {
|
|
143
|
+
const rows = await source.candidates({ sessionId: 's1' as never }, { query: '', position: 'leading', drilled: false, signal: new AbortController().signal })
|
|
144
|
+
expect(rows).toHaveLength(1)
|
|
145
|
+
expect(rows[0].icon).toBeUndefined()
|
|
146
|
+
expect(rows[0].name).toBe('code-review')
|
|
147
|
+
} finally {
|
|
148
|
+
restore()
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
134
152
|
it('restores the original candidates on dispose', async () => {
|
|
135
153
|
const originalCandidates = async (): Promise<InputTriggerCandidate[]> => [{ name: 'plain', description: '' }]
|
|
136
154
|
const source = skillSource(originalCandidates)
|
|
@@ -18,9 +18,12 @@
|
|
|
18
18
|
* / `sessionOf` — the lookup below is defensive: it never throws).
|
|
19
19
|
*/
|
|
20
20
|
|
|
21
|
-
import type { ClientContext
|
|
21
|
+
import type { Context as ClientContext } from '@deepseek-ai/cordis'
|
|
22
|
+
import type { SettingsScope } from '@deepseek-ai/dsh-client-ui-settings/client'
|
|
22
23
|
// Type-only: pulls the Context merge for ctx.inputTriggers.
|
|
23
24
|
import type {} from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
|
25
|
+
// Type-only: pulls the connection/reset event.
|
|
26
|
+
import type {} from '@deepseek-ai/dsh-client-connection/client'
|
|
24
27
|
import type { InputTriggerCandidate, InputTriggerServiceContract, InputTriggerSource } from '@deepseek-ai/dsh-client-ui-input-trigger/client'
|
|
25
28
|
import { createElement } from 'react'
|
|
26
29
|
import type { HubSettingsValue } from '../protocol.ts'
|
|
@@ -116,16 +119,67 @@ function dotIcon(color: string): InputTriggerCandidate['icon'] {
|
|
|
116
119
|
* @param scope - hub settings scope for the dot colors.
|
|
117
120
|
* @returns a disposer restoring the original candidates.
|
|
118
121
|
*/
|
|
122
|
+
/**
|
|
123
|
+
* DOM 兜底:在 alpha.2 新版 MenuView(icon 仅枚举)下通过直接操作
|
|
124
|
+
* 已渲染的 `[role="option"]` 列表注入彩色点,绕过 `icon` 限制。
|
|
125
|
+
* 旧版仍走 `icon` 注入,此处仅为新版。
|
|
126
|
+
*/
|
|
127
|
+
function injectDotsViaDOM(modelByName: Map<string, boolean>, modelColor: string, userColor: string): void {
|
|
128
|
+
if (typeof document === 'undefined' || typeof requestAnimationFrame === 'undefined') return
|
|
129
|
+
const run = (): void => {
|
|
130
|
+
const options = document.querySelectorAll('[role="option"]')
|
|
131
|
+
for (const opt of options) {
|
|
132
|
+
// 已注入则跳过(由 data 属性标记)
|
|
133
|
+
if (opt.querySelector('[data-skill-dot]') !== null) continue
|
|
134
|
+
// 技能名在 .itemName 中,取文本匹配 catalog
|
|
135
|
+
const nameEl = opt.querySelector('[class*="itemName"]') as HTMLElement | null
|
|
136
|
+
if (nameEl === null) continue
|
|
137
|
+
const name = (nameEl.textContent ?? '').trim()
|
|
138
|
+
if (name.length === 0) continue
|
|
139
|
+
if (!modelByName.has(name) && nameEl.textContent !== name) continue // 保守:仅处理已知名
|
|
140
|
+
const color = (modelByName.get(name) ?? true) ? modelColor : userColor
|
|
141
|
+
const dot = document.createElement('span')
|
|
142
|
+
dot.setAttribute('data-skill-dot', '')
|
|
143
|
+
dot.setAttribute('aria-hidden', 'true')
|
|
144
|
+
dot.style.display = 'inline-block'
|
|
145
|
+
dot.style.width = '6px'
|
|
146
|
+
dot.style.height = '6px'
|
|
147
|
+
dot.style.borderRadius = '3px'
|
|
148
|
+
dot.style.background = color
|
|
149
|
+
dot.style.flex = 'none'
|
|
150
|
+
// 插在名称前;若存在 icon 槽则插在其后,兼容旧版结构
|
|
151
|
+
const iconEl = opt.querySelector('[class*="itemIcon"]')
|
|
152
|
+
if (iconEl !== null && iconEl.nextSibling !== null) {
|
|
153
|
+
iconEl.parentElement?.insertBefore(dot, iconEl.nextSibling)
|
|
154
|
+
} else {
|
|
155
|
+
nameEl.parentElement?.insertBefore(dot, nameEl)
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
// 菜单由 React 异步渲染,需等下一帧
|
|
160
|
+
requestAnimationFrame(() => setTimeout(run, 0))
|
|
161
|
+
}
|
|
162
|
+
|
|
119
163
|
export function wrapSkillSource(source: InputTriggerSource, api: SkillHubApi, scope: SettingsScope<HubSettingsValue>): () => void {
|
|
120
164
|
const original = source.candidates
|
|
121
165
|
source.candidates = async (session, req) => {
|
|
122
166
|
const items = await original(session, req)
|
|
123
167
|
if (req.signal.aborted) return items
|
|
168
|
+
// Dual-compat: 0.1.2-alpha.2 的 MenuView 将 icon 收窄为 'file'|'folder'|'session'
|
|
169
|
+
// 并通过 ReferenceIcon 渲染,旧版的自定义 ReactElement 点已无法展示。
|
|
170
|
+
// 以 `drilled` 是否存在探测新版(alpha.2 必有,rc 无)。
|
|
171
|
+
const isNewHost = req !== null && typeof req === 'object' && 'drilled' in (req as unknown as Record<string, unknown>)
|
|
124
172
|
const modelByName = await modelInvocableMap(api)
|
|
125
173
|
if (req.signal.aborted) return items
|
|
126
174
|
const snapshot = scope.getSnapshot()
|
|
127
175
|
const modelColor = snapshot.value?.dotModelColor ?? DEFAULT_DOT_MODEL_COLOR
|
|
128
176
|
const userColor = snapshot.value?.dotUserColor ?? DEFAULT_DOT_USER_COLOR
|
|
177
|
+
if (isNewHost) {
|
|
178
|
+
// 新版:不通过 icon(枚举限制),改为 DOM 注入
|
|
179
|
+
injectDotsViaDOM(modelByName, modelColor, userColor)
|
|
180
|
+
return items
|
|
181
|
+
}
|
|
182
|
+
// 旧版:保持原有 icon 注入
|
|
129
183
|
return items.map((item) => ({
|
|
130
184
|
...item,
|
|
131
185
|
icon: dotIcon((modelByName.get(item.name) ?? true) ? modelColor : userColor),
|
package/src/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Context } from '@deepseek-ai/cordis'
|
|
11
11
|
import type { SkillProviderControl } from '@deepseek-ai/dsh-skill'
|
|
12
|
-
import {
|
|
12
|
+
import type { SettingsNamespace } from '@deepseek-ai/dsh-settings'
|
|
13
13
|
import z from 'schemastery'
|
|
14
14
|
import type {} from '@deepseek-ai/dsh-host-webserver'
|
|
15
15
|
import type {} from '@deepseek-ai/dsh-skill'
|
|
@@ -67,7 +67,7 @@ export const Config: z<Config> = z.object({
|
|
|
67
67
|
* page edit this namespace through the official settings transport, and the
|
|
68
68
|
* plugin consumes the same resolved value — one source of truth.
|
|
69
69
|
*/
|
|
70
|
-
export const CONFIG_NAMESPACE =
|
|
70
|
+
export const CONFIG_NAMESPACE = 'dsh-skill-hub' as SettingsNamespace
|
|
71
71
|
|
|
72
72
|
/** Schema of the hub's settings namespace: the card's fields (booleans + optional dot colors). */
|
|
73
73
|
export const HubSettingsSchema: z<HubSettingsValue> = z.object({
|