dsh-skill-picker 0.3.4 → 0.4.0
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 +17 -1
- package/lib/client.js +17 -101
- package/lib/client.js.map +2 -2
- package/package.json +2 -3
- package/src/client/index.jsx +26 -127
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-skill-picker",
|
|
3
3
|
"description": "DSH Web GUI skill picker: a button beside the composer opens a searchable list of installed skills; picking one inserts the official `/skill-name` gesture into the input box, so the skill loads with your message (WorkBuddy-style skill invocation for DeepSeek Harness).",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"exports": {
|
|
@@ -23,8 +23,7 @@
|
|
|
23
23
|
"client": {
|
|
24
24
|
"inject": [
|
|
25
25
|
"@deepseek-ai/dsh-client-runtime",
|
|
26
|
-
"@deepseek-ai/dsh-client-locale"
|
|
27
|
-
"@deepseek-ai/dsh-client-ui-input-trigger"
|
|
26
|
+
"@deepseek-ai/dsh-client-locale"
|
|
28
27
|
],
|
|
29
28
|
"platform": "web"
|
|
30
29
|
},
|
package/src/client/index.jsx
CHANGED
|
@@ -445,12 +445,6 @@ function SkillPickerButton(props) {
|
|
|
445
445
|
|
|
446
446
|
/** Apply the browser half: register the picker into the composer tool row. */
|
|
447
447
|
export function apply(ctx) {
|
|
448
|
-
// Resolve the trigger pipeline service at apply scope (not inside an effect),
|
|
449
|
-
// exactly like the official ui-skill plugin does — in DSH 0.1.2-alpha.x the
|
|
450
|
-
// service lives in @deepseek-ai/dsh-client-ui-input-trigger and is only
|
|
451
|
-
// reachable from the plugin root context.
|
|
452
|
-
const inputTriggers = (typeof ctx.get === 'function' ? ctx.get('inputTriggers') : ctx.inputTriggers)
|
|
453
|
-
|
|
454
448
|
// Primary skill source: the official host skills API. In DSH 0.1.2-alpha.x
|
|
455
449
|
// the RPC moved from `connection.api.skills` (rc.x) to `remote.skills`
|
|
456
450
|
// (used by the official ui-skill plugin); try both before falling back.
|
|
@@ -481,28 +475,6 @@ export function apply(ctx) {
|
|
|
481
475
|
}
|
|
482
476
|
}
|
|
483
477
|
|
|
484
|
-
// Host-route fallback: same scan the ⚡ panel uses (official provider roots).
|
|
485
|
-
const fetchHostSkills = async () => {
|
|
486
|
-
const cwd = typeof currentCwd === 'string' && currentCwd !== '' ? `?cwd=${encodeURIComponent(currentCwd)}` : ''
|
|
487
|
-
const res = await fetch(`/dsh-skill-picker/skills${cwd}`, { headers: { accept: 'application/json' } })
|
|
488
|
-
const json = await res.json()
|
|
489
|
-
if (!json.ok) throw new Error(json.error || 'bad response')
|
|
490
|
-
return Array.isArray(json.skills) ? json.skills : []
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// Resolve skills by official RPC first, then the host scan route; never
|
|
494
|
-
// throws (returns [] when both fail) so `/` completion degrades gracefully.
|
|
495
|
-
const resolveSkills = async (sessionId) => {
|
|
496
|
-
try {
|
|
497
|
-
return await listSkills(sessionId)
|
|
498
|
-
} catch {
|
|
499
|
-
try {
|
|
500
|
-
return await fetchHostSkills()
|
|
501
|
-
} catch {
|
|
502
|
-
return []
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
}
|
|
506
478
|
syncCwd()
|
|
507
479
|
const unsubscribe = ctx.sessions.list.subscribe(syncCwd)
|
|
508
480
|
ctx.effect(() => {
|
|
@@ -523,107 +495,34 @@ export function apply(ctx) {
|
|
|
523
495
|
}
|
|
524
496
|
}, 'dsh-skill-picker: composer input slot')
|
|
525
497
|
|
|
526
|
-
// Fuzzy `/` completion
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
//
|
|
498
|
+
// Fuzzy `/` completion: instead of registering a parallel source group
|
|
499
|
+
// (which would appear as a second list next to the official one), expose a
|
|
500
|
+
// global matcher that the patched official ui-skill candidates calls. The
|
|
501
|
+
// official group stays THE single `/` list; only its matching behaviour is
|
|
502
|
+
// upgraded to fuzzy + pinyin (name AND description, subsequence scoring).
|
|
530
503
|
ctx.effect(() => {
|
|
531
|
-
//
|
|
532
|
-
//
|
|
533
|
-
const
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
const source = {
|
|
551
|
-
trigger: '/',
|
|
552
|
-
name: 'skill-fuzzy',
|
|
553
|
-
order: -10,
|
|
554
|
-
async candidates(session, { query, signal }) {
|
|
555
|
-
const skills = await resolveSkills(session.sessionId)
|
|
556
|
-
if (signal.aborted) return []
|
|
557
|
-
namesCache.set(session.sessionId, skills.map((s) => s.name))
|
|
558
|
-
notifyLexicon(session.sessionId)
|
|
559
|
-
const ordered = rankByUsage(skills, loadUsage())
|
|
560
|
-
const q = String(query ?? '').trim().toLowerCase()
|
|
561
|
-
if (q === '') {
|
|
562
|
-
// Empty `/` surfaces ALL skills, usage first (same rule as ⚡ panel).
|
|
563
|
-
return ordered.map((s) => ({ name: s.name, description: s.description }))
|
|
564
|
-
}
|
|
565
|
-
// Typing uses fuzzysort (subsequence matching + relevance score) over
|
|
566
|
-
// name AND description, so partial/gappy queries and keywords match.
|
|
567
|
-
// The search string also carries the skill's pinyin forms (spaced,
|
|
568
|
-
// joined, initials — name and description), so `ji yi` / `jiyi` / `jy`
|
|
569
|
-
// match Chinese skill names & descriptions.
|
|
570
|
-
const targets = ordered.map((s) => ({
|
|
571
|
-
s,
|
|
572
|
-
search: `${s.name} ${s.description ?? ''} ${skillPinyinText(s.name, s.description ?? '')}`,
|
|
573
|
-
}))
|
|
574
|
-
const results = fuzzysort.go(q, targets, {
|
|
575
|
-
key: 'search',
|
|
576
|
-
limit: 12,
|
|
577
|
-
threshold: -10000,
|
|
578
|
-
})
|
|
579
|
-
return results
|
|
580
|
-
.filter((r) => r.score > 0)
|
|
581
|
-
.map((r) => ({ name: r.obj.s.name, description: r.obj.s.description }))
|
|
582
|
-
},
|
|
583
|
-
warm(session) {
|
|
584
|
-
refreshNames(session.sessionId)
|
|
585
|
-
},
|
|
586
|
-
lexicon(session) {
|
|
587
|
-
return namesCache.get(session.sessionId)
|
|
588
|
-
},
|
|
589
|
-
subscribeLexicon(session, listener) {
|
|
590
|
-
const key = session.sessionId
|
|
591
|
-
const set = lexiconListeners.get(key) ?? new Set()
|
|
592
|
-
set.add(listener)
|
|
593
|
-
lexiconListeners.set(key, set)
|
|
594
|
-
return () => {
|
|
595
|
-
const cur = lexiconListeners.get(key)
|
|
596
|
-
if (cur !== undefined) {
|
|
597
|
-
cur.delete(listener)
|
|
598
|
-
if (cur.size === 0) lexiconListeners.delete(key)
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
},
|
|
602
|
-
onPick({ candidate }) {
|
|
603
|
-
// Record usage (same rule as the ⚡ panel) so slash-picked skills
|
|
604
|
-
// rank higher the next time they open `/`.
|
|
605
|
-
try {
|
|
606
|
-
const usage = loadUsage()
|
|
607
|
-
const name = candidate.name
|
|
608
|
-
const next = { ...usage, [name]: { count: (usage[name]?.count ?? 0) + 1, lastUsed: Date.now() } }
|
|
609
|
-
saveUsage(next)
|
|
610
|
-
} catch {
|
|
611
|
-
/* usage recording is best-effort */
|
|
612
|
-
}
|
|
613
|
-
return { text: `/${candidate.name} ` }
|
|
614
|
-
},
|
|
615
|
-
}
|
|
616
|
-
// inputTriggers is resolved at apply scope (see top of apply); fail loudly
|
|
617
|
-
// instead of silently dropping the fuzzy `/` source.
|
|
618
|
-
if (inputTriggers === undefined || typeof inputTriggers.registerSource !== 'function') {
|
|
619
|
-
console.warn('[dsh-skill-picker] inputTriggers service unavailable; fuzzy / completion disabled (⚡ panel still works)')
|
|
620
|
-
return
|
|
504
|
+
// Mirror the ⚡ panel's `rankByUsage` semantics: recently/frequently used
|
|
505
|
+
// skills first, then the untouched rest — so usage history keeps working.
|
|
506
|
+
const fuzzyMatch = (skills, query = '') => {
|
|
507
|
+
const ordered = rankByUsage(skills, loadUsage())
|
|
508
|
+
const q = String(query).trim().toLowerCase()
|
|
509
|
+
if (q === '') return ordered
|
|
510
|
+
const targets = ordered.map((s) => ({
|
|
511
|
+
s,
|
|
512
|
+
search: `${s.name} ${s.description ?? ''} ${skillPinyinText(s.name, s.description ?? '')}`,
|
|
513
|
+
}))
|
|
514
|
+
const results = fuzzysort.go(q, targets, {
|
|
515
|
+
key: 'search',
|
|
516
|
+
limit: 12,
|
|
517
|
+
threshold: -10000,
|
|
518
|
+
})
|
|
519
|
+
return results
|
|
520
|
+
.filter((r) => r.score > 0)
|
|
521
|
+
.map((r) => r.obj.s)
|
|
621
522
|
}
|
|
622
|
-
|
|
523
|
+
window.__dshSkillPickerFuzzy = fuzzyMatch
|
|
623
524
|
return () => {
|
|
624
|
-
|
|
625
|
-
namesCache.clear()
|
|
626
|
-
lexiconListeners.clear()
|
|
525
|
+
if (window.__dshSkillPickerFuzzy === fuzzyMatch) delete window.__dshSkillPickerFuzzy
|
|
627
526
|
}
|
|
628
|
-
}, 'dsh-skill-picker: fuzzy / source')
|
|
527
|
+
}, 'dsh-skill-picker: fuzzy matcher for official / source')
|
|
629
528
|
}
|