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/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 = settingsNamespace("dsh-skill-hub");
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 === CONFIG_NAMESPACE)?.user ?? {};
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-runtime/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/dsh-client-runtime/client';
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-runtime/client").SnapshotStore<T>;
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, SettingsScope } from '@deepseek-ai/dsh-client-runtime/client';
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
@@ -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: import("@deepseek-ai/dsh-settings").SettingsNamespace;
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.0",
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-ui-input-trigger": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
92
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
93
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
94
- "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
95
- "@deepseek-ai/dsh-session": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
96
- "@deepseek-ai/dsh-session-query": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
97
- "@deepseek-ai/dsh-settings": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
98
- "@deepseek-ai/dsh-skill": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
99
- "@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
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.1",
105
- "@deepseek-ai/dsh-client-connection": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
106
- "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
107
- "@deepseek-ai/dsh-client-runtime": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
108
- "@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",
109
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
110
- "@deepseek-ai/dsh-client-ui-settings-plugins": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
111
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
112
- "@deepseek-ai/dsh-host-webserver": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
113
- "@deepseek-ai/dsh-session": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
114
- "@deepseek-ai/dsh-session-query": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
115
- "@deepseek-ai/dsh-settings": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
116
- "@deepseek-ai/dsh-skill": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
117
- "@deepseek-ai/dsh-system-prompt": ">=0.1.0-rc.7 <0.2.0-0 || >=0.1.1-rc.2 <0.2.0-0",
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-runtime/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'
@@ -22,7 +22,7 @@
22
22
  * internal.
23
23
  */
24
24
 
25
- import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
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, type SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'
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-runtime/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
- } as SettingsScope<HubSettingsValue>
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
- const rows = await source.candidates({ sessionId: 's1' as never }, { query: '', position: 'leading', signal: new AbortController().signal })
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, SettingsScope } from '@deepseek-ai/dsh-client-runtime/client'
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 { settingsNamespace } from '@deepseek-ai/dsh-settings'
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 = settingsNamespace('dsh-skill-hub')
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({