@dsh-plugins/dsh-llm-hub 1.3.1 → 1.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/CHANGELOG.md CHANGED
@@ -2,6 +2,18 @@
2
2
 
3
3
  本项目遵循 [Semantic Versioning](https://semver.org/lang/zh-CN/)。
4
4
 
5
+ ## [1.3.2] - 2026-09-24
6
+
7
+ ### 🐛 修复与健壮性提升
8
+
9
+ - **⚡ 解决 MiniMax 等大模型临时限额导致下拉永久死锁问题**:
10
+ - 核心逻辑解耦:重构可用性过滤判定,严格区分「致命凭据故障(`CREDENTIAL_MISSING` / `AUTH_REJECTED`)」与「临时配额/余额状态(`QUOTA_EXHAUSTED` / `BALANCE_EMPTY` / `PAYMENT_REQUIRED`)」;
11
+ - 临时配额用尽(如 MiniMax 5 小时滚动配额 / 周配额达到 100% 用量)仅在设置页与状态卡片进行友好 warning 预警,**不再从 `llm.listProviders()` 下拉菜单中强行抹除**;
12
+ - 彻底破除「额度用尽 → 下拉消失 → 无法选择该模型发请求 → 运行期遥测恢复机制永久无法触发」的死锁链条;
13
+ - **🔄 后台主动保鲜与自愈机制(Auto-healing Probe)**:
14
+ - 在插件生命周期内新增守护探针轮询(`TTL = 5m`),结合 `.unref()` 保证无副作用后台运行;
15
+ - 当 MiniMax 5 小时重置窗口或余额恢复后,后台探针自动感知并重置为 `available`,无需手动重启或进入设置页刷新。
16
+
5
17
  ## [1.3.1] - 2026-09-23
6
18
 
7
19
  ### ✨ UI 视觉升级与极客装配 Hub
package/lib/client.js CHANGED
@@ -187,7 +187,12 @@ window.__ModuleLoader__.load({
187
187
  usageClear: '清空记录',
188
188
  usageCleared: '已清空',
189
189
  usageClearConfirm: '确认清空?',
190
- usageNoData: '本月还没有记录'
190
+ usageNoData: '本月还没有记录',
191
+ suiteTitle: 'Webkubor DSH 扩展家族',
192
+ suiteDesc: '一键构建完整智能体开发环境',
193
+ suiteActive: '已激活',
194
+ suiteCopyInstall: '复制安装',
195
+ suiteCopied: '已复制命令'
191
196
  },
192
197
  en: {
193
198
  balance: 'Balance',
@@ -297,7 +302,12 @@ window.__ModuleLoader__.load({
297
302
  usageClear: 'Clear records',
298
303
  usageCleared: 'Cleared',
299
304
  usageClearConfirm: 'Confirm clear?',
300
- usageNoData: 'no calls this month yet'
305
+ usageNoData: 'no calls this month yet',
306
+ suiteTitle: 'Webkubor DSH Plugin Suite',
307
+ suiteDesc: 'One-click full agent development environment',
308
+ suiteActive: 'Active',
309
+ suiteCopyInstall: 'Copy install',
310
+ suiteCopied: 'Command copied'
301
311
  }
302
312
  }
303
313
 
@@ -537,6 +547,25 @@ window.__ModuleLoader__.load({
537
547
  '.dsh-llm-hub-latency-badge.is-fast{background:rgba(16,185,129,.08);color:var(--dsw-alias-state-success-primary,#10b981);border:.5px solid rgba(16,185,129,.2)}',
538
548
  '.dsh-llm-hub-latency-badge.is-normal{background:rgba(234,179,8,.08);color:var(--dsw-alias-state-warn-primary,#eab308);border:.5px solid rgba(234,179,8,.2)}',
539
549
  '.dsh-llm-hub-latency-badge.is-slow{background:rgba(239,68,68,.08);color:var(--dsw-alias-state-error-primary,#ef4444);border:.5px solid rgba(239,68,68,.2)}',
550
+ // Webkubor 插件全家桶互导矩阵
551
+ '.dsh-suite-dock{margin-top:14px;padding:14px 16px;background:var(--dsw-alias-bg-layer-2,rgba(255,255,255,0.03));border:1px solid var(--dsw-alias-border-l1,rgba(255,255,255,0.08));border-radius:10px;backdrop-filter:blur(10px)}',
552
+ '.dsh-suite-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;flex-wrap:wrap;gap:8px}',
553
+ '.dsh-suite-title{font-size:12px;font-weight:650;color:var(--dsw-alias-label-primary);margin:0;display:flex;align-items:center;gap:6px}',
554
+ '.dsh-suite-desc{font-size:10.5px;color:var(--dsw-alias-label-tertiary);letter-spacing:.2px}',
555
+ '.dsh-suite-cards{display:grid;grid-template-columns:repeat(auto-fit,minmax(180px,1fr));gap:8px}',
556
+ '.dsh-suite-card{display:flex;flex-direction:column;justify-content:space-between;padding:10px 12px;border-radius:8px;background:var(--dsw-alias-bg-layer-3,rgba(255,255,255,0.02));border:1px solid var(--dsw-alias-border-l2,rgba(255,255,255,0.05));transition:all .2s cubic-bezier(0.16,1,0.3,1)}',
557
+ '.dsh-suite-card:hover{transform:translateY(-1px);border-color:var(--dsw-alias-brand-primary,#3b82f6);box-shadow:0 4px 14px rgba(0,0,0,0.15)}',
558
+ '.dsh-suite-card-top{display:flex;align-items:center;gap:6px;margin-bottom:4px}',
559
+ '.dsh-suite-card-icon{font-size:15px;line-height:1}',
560
+ '.dsh-suite-card-name{font-size:12px;font-weight:600;color:var(--dsw-alias-label-primary);text-decoration:none}',
561
+ '.dsh-suite-card-desc{font-size:10.5px;color:var(--dsw-alias-label-secondary);line-height:1.4;margin-bottom:8px;flex:1}',
562
+ '.dsh-suite-card-bottom{display:flex;align-items:center;justify-content:space-between;gap:6px;margin-top:auto;font-size:10.5px}',
563
+ '.dsh-suite-badge-active{display:inline-flex;align-items:center;gap:4px;color:var(--dsw-alias-state-success-primary,#10b981);font-weight:600;font-size:10.5px}',
564
+ '.dsh-suite-badge-active-dot{width:5px;height:5px;border-radius:50%;background:#10b981;box-shadow:0 0 6px rgba(16,185,129,0.6)}',
565
+ '.dsh-suite-btn-action{cursor:pointer;padding:2px 8px;border-radius:4px;font-size:10px;font-weight:550;background:var(--dsw-alias-interactive-bg-subtle,rgba(255,255,255,0.06));border:1px solid var(--dsw-alias-border-l2);color:var(--dsw-alias-label-primary);transition:all .15s;text-decoration:none;display:inline-flex;align-items:center;gap:3px}',
566
+ '.dsh-suite-btn-action:hover{background:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,0.12));border-color:var(--dsw-alias-border-l1)}',
567
+ '.dsh-suite-link{color:var(--dsw-alias-label-tertiary);text-decoration:none;font-size:10px;display:inline-flex;align-items:center}',
568
+ '.dsh-suite-link:hover{color:var(--dsw-alias-label-primary)}'
540
569
  ].join('')
541
570
  document.head.appendChild(style)
542
571
  }
@@ -659,7 +688,9 @@ window.__ModuleLoader__.load({
659
688
  const verdict = Array.isArray(availabilityState.providers)
660
689
  ? availabilityState.providers.find((entry) => entry.provider === DEEPSEEK_PROVIDER)
661
690
  : undefined
662
- const hiddenFromDropdown = verdict !== undefined && verdict.state === 'unavailable'
691
+ const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
692
+ ? availabilityState.hidden.includes(DEEPSEEK_PROVIDER)
693
+ : (verdict !== undefined && verdict.state === 'unavailable')
663
694
 
664
695
  const load = React.useCallback(() => {
665
696
  if (!keyConfigured) {
@@ -881,7 +912,9 @@ window.__ModuleLoader__.load({
881
912
  const verdict = Array.isArray(availabilityState.providers)
882
913
  ? availabilityState.providers.find((entry) => entry.provider === provider)
883
914
  : undefined
884
- const hiddenFromDropdown = verdict !== undefined && verdict.state === 'unavailable'
915
+ const hiddenFromDropdown = Array.isArray(availabilityState.hidden)
916
+ ? availabilityState.hidden.includes(provider)
917
+ : (verdict !== undefined && verdict.state === 'unavailable')
885
918
 
886
919
  React.useEffect(() => {
887
920
  if (provider === '' || status === null || status.ok !== true || !status.balanceAdapter) return undefined
@@ -1632,6 +1665,115 @@ window.__ModuleLoader__.load({
1632
1665
  links)
1633
1666
  }
1634
1667
 
1668
+ /**
1669
+ * Webkubor DSH 扩展家族互导 Dock
1670
+ */
1671
+ function WebkuborSuiteDock(props) {
1672
+ const t = typeof props.t === 'function' ? props.t : fallbackT
1673
+ const [copiedPkg, setCopiedPkg] = React.useState(null)
1674
+
1675
+ ensureStyle()
1676
+
1677
+ const suite = [
1678
+ {
1679
+ id: '@dsh-plugins/dsh-bloom-theme',
1680
+ aliases: ['dsh-bloom-theme', '@dsh-plugins/dsh-bloom-theme'],
1681
+ name: 'Bloom Theme',
1682
+ icon: '🌸',
1683
+ desc: '落霞 Bloom 极客质感毛玻璃主题与代码高亮',
1684
+ repo: 'https://github.com/webkubor/dsh-bloom-theme',
1685
+ pkg: '@dsh-plugins/dsh-bloom-theme'
1686
+ },
1687
+ {
1688
+ id: '@dsh-plugins/dsh-llm-hub',
1689
+ aliases: ['dsh-llm-hub', '@dsh-plugins/dsh-llm-hub'],
1690
+ name: 'LLM Hub',
1691
+ icon: '🎛️',
1692
+ desc: '多模型网关、余额配额、多Key轮换与极客大屏',
1693
+ repo: 'https://github.com/webkubor/dsh-llm-hub',
1694
+ pkg: '@dsh-plugins/dsh-llm-hub',
1695
+ isCurrent: true
1696
+ },
1697
+ {
1698
+ id: '@dsh-plugins/dsh-user-mirror',
1699
+ aliases: ['dsh-user-mirror', 'dsh-mirror', '@dsh-plugins/dsh-user-mirror'],
1700
+ name: 'User Mirror',
1701
+ icon: '🪞',
1702
+ desc: '用户角色数字画像、习惯偏好与记忆网络',
1703
+ repo: 'https://github.com/webkubor/dsh-mirror',
1704
+ pkg: '@dsh-plugins/dsh-user-mirror'
1705
+ },
1706
+ {
1707
+ id: '@dsh-plugins/dsh-env-inspector',
1708
+ aliases: ['dsh-env-inspector', '@dsh-plugins/dsh-env-inspector'],
1709
+ name: 'Env Inspector',
1710
+ icon: '🖥️',
1711
+ desc: '端口监听释放、CLI工具链与环境大屏',
1712
+ repo: 'https://github.com/webkubor/dsh-env-inspector',
1713
+ pkg: '@dsh-plugins/dsh-env-inspector'
1714
+ }
1715
+ ]
1716
+
1717
+ const handleCopy = (pkg) => {
1718
+ const cmd = `dsh plugin install ${pkg}`
1719
+ if (navigator.clipboard) {
1720
+ navigator.clipboard.writeText(cmd)
1721
+ }
1722
+ setCopiedPkg(pkg)
1723
+ setTimeout(() => setCopiedPkg(null), 2000)
1724
+ }
1725
+
1726
+ return h('div', { className: 'dsh-suite-dock' },
1727
+ h('div', { className: 'dsh-suite-head' },
1728
+ h('h3', { className: 'dsh-suite-title' },
1729
+ h('span', null, '🌟'),
1730
+ t('suiteTitle')
1731
+ ),
1732
+ h('span', { className: 'dsh-suite-desc' }, t('suiteDesc'))
1733
+ ),
1734
+ h('div', { className: 'dsh-suite-cards' },
1735
+ ...suite.map((item) => {
1736
+ const isInstalled = item.isCurrent
1737
+ const isCopied = copiedPkg === item.pkg
1738
+
1739
+ return h('div', { key: item.id, className: 'dsh-suite-card' },
1740
+ h('div', { className: 'dsh-suite-card-top' },
1741
+ h('span', { className: 'dsh-suite-card-icon' }, item.icon),
1742
+ h('a', {
1743
+ href: item.repo,
1744
+ target: '_blank',
1745
+ rel: 'noopener noreferrer',
1746
+ className: 'dsh-suite-card-name'
1747
+ }, item.name)
1748
+ ),
1749
+ h('div', { className: 'dsh-suite-card-desc' }, item.desc),
1750
+ h('div', { className: 'dsh-suite-card-bottom' },
1751
+ isInstalled ? (
1752
+ h('span', { className: 'dsh-suite-badge-active' },
1753
+ h('span', { className: 'dsh-suite-badge-active-dot' }),
1754
+ t('suiteActive')
1755
+ )
1756
+ ) : (
1757
+ h('button', {
1758
+ type: 'button',
1759
+ className: 'dsh-suite-btn-action',
1760
+ onClick: () => handleCopy(item.pkg),
1761
+ title: `安装命令: dsh plugin install ${item.pkg}`
1762
+ }, isCopied ? `✓ ${t('suiteCopied')}` : `⚡ ${t('suiteCopyInstall')}`)
1763
+ ),
1764
+ h('a', {
1765
+ href: item.repo,
1766
+ target: '_blank',
1767
+ rel: 'noopener noreferrer',
1768
+ className: 'dsh-suite-link'
1769
+ }, 'GitHub ↗')
1770
+ )
1771
+ )
1772
+ })
1773
+ )
1774
+ )
1775
+ }
1776
+
1635
1777
  /**
1636
1778
  * 可用性判定缓存 —— 所有会话共用一份(同一个 host、同一份设置)。
1637
1779
  *
@@ -1844,6 +1986,13 @@ window.__ModuleLoader__.load({
1844
1986
  locale: NS,
1845
1987
  inject: () => ({ t, availability })
1846
1988
  }, HubFooter))
1989
+ ctx.slots.inject('settings.models.footer', () => ctx.slots.register({
1990
+ name: 'settings.models.footer',
1991
+ id: 'dsh-llm-hub-suite',
1992
+ order: 110,
1993
+ locale: NS,
1994
+ inject: () => ({ t })
1995
+ }, WebkuborSuiteDock))
1847
1996
  }
1848
1997
 
1849
1998
  exports.apply = apply
package/lib/index.js CHANGED
@@ -1490,11 +1490,29 @@ export function apply(ctx) {
1490
1490
  return [...merged.values()].sort((left, right) => left.provider.localeCompare(right.provider))
1491
1491
  }
1492
1492
 
1493
+ /**
1494
+ * 判定是否属于应从模型下拉彻底摘掉的严重凭据故障。
1495
+ *
1496
+ * ⚠️ 设计原则(避免死锁):
1497
+ * 只有确凿的凭据故障(CREDENTIAL_MISSING / AUTH_REJECTED / ACCOUNT_UNAVAILABLE)才从下拉框剔除。
1498
+ * 配额耗尽(QUOTA_EXHAUSTED)与现金余额为 0(BALANCE_EMPTY / PAYMENT_REQUIRED)属于临时限额/计费状态,
1499
+ * 绝不能从下拉框中剔除,否则会导致:
1500
+ * 1) 用户在界面上永久丢失模型入口;
1501
+ * 2) 5小时滚动配额或周配额恢复后,用户因无法选模型而无法触发运行期遥测恢复;
1502
+ * 3) 形成死锁。
1503
+ */
1504
+ const isDropDeadHidden = (verdict) => {
1505
+ if (verdict === null || typeof verdict !== 'object') return false
1506
+ if (verdict.state !== 'unavailable') return false
1507
+ const code = verdict.code
1508
+ return code === 'CREDENTIAL_MISSING' || code === 'AUTH_REJECTED' || code === 'ACCOUNT_UNAVAILABLE'
1509
+ }
1510
+
1493
1511
  /** 当前该从下拉里摘掉的 provider 集合。 */
1494
1512
  const hiddenProviders = () => {
1495
1513
  const hidden = new Set()
1496
1514
  for (const verdict of currentAvailability()) {
1497
- if (verdict.state === 'unavailable') hidden.add(verdict.provider)
1515
+ if (isDropDeadHidden(verdict)) hidden.add(verdict.provider)
1498
1516
  }
1499
1517
  return hidden
1500
1518
  }
@@ -1530,7 +1548,7 @@ export function apply(ctx) {
1530
1548
  checkedAt: lastProbeAt,
1531
1549
  probing: probeAll !== null,
1532
1550
  providers,
1533
- hidden: providers.filter((verdict) => verdict.state === 'unavailable').map((verdict) => verdict.provider)
1551
+ hidden: providers.filter(isDropDeadHidden).map((verdict) => verdict.provider)
1534
1552
  }
1535
1553
  }
1536
1554
 
@@ -1630,8 +1648,16 @@ export function apply(ctx) {
1630
1648
  const timer = setTimeout(() => {
1631
1649
  refreshAvailability().catch(() => {})
1632
1650
  }, 1500)
1633
- return () => clearTimeout(timer)
1634
- }, 'dsh-llm-hub: initial availability probe')
1651
+ timer.unref?.()
1652
+ const interval = setInterval(() => {
1653
+ ensureAvailabilityFresh()
1654
+ }, AVAILABILITY_TTL_MS)
1655
+ interval.unref?.()
1656
+ return () => {
1657
+ clearTimeout(timer)
1658
+ clearInterval(interval)
1659
+ }
1660
+ }, 'dsh-llm-hub: initial and periodic availability probe')
1635
1661
 
1636
1662
  // 把不可用的 provider 从 llm 服务的路由表里摘掉。
1637
1663
  //
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-plugins/dsh-llm-hub",
3
- "version": "1.3.1",
3
+ "version": "1.3.2",
4
4
  "description": "DSH 模型管理层:网关可达性探测、模型目录拉取、DeepSeek + MiniMax/智谱/Moonshot/StepFun 余额与配额、本月用量统计、余额预警、Provider 健康看板,再把本机装好的 codex / claude / agy 注册成子代理(装了才出现)。零运行时依赖,不改动 DSH 安装。",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",