@goodandready/dsh-model-sync 0.3.10 → 0.3.11

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 CHANGED
@@ -107,6 +107,15 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
107
107
 
108
108
  ---
109
109
 
110
+ ## 🛠️ Enhancements & Fixes in v0.3.11
111
+
112
+ * 🗂️ **Streamlined Settings UI Surface**:
113
+ * Removed the redundant top-level settings sidebar section fallback (`settings.section`), keeping configuration strictly inside the standard DSH plugin settings card (`settings.plugin.item`).
114
+ * 🛡️ **Hardened Cordis Service Access**:
115
+ * Service access for `settings` and `llm` is now performed via `ctx.get(...)` with safe fallback to direct property inspection, preventing silent resolution failures on Cordis proxy implementations.
116
+
117
+ ---
118
+
110
119
  ## 🛠️ Enhancements & Fixes in v0.3.8
111
120
 
112
121
  * ⚙️ **Reactive Settings Card Binding (`settingsScope`)**:
package/README.ru.md CHANGED
@@ -84,6 +84,15 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
84
84
 
85
85
  ---
86
86
 
87
+ ## 🛠️ Улучшения и исправления в v0.3.11
88
+
89
+ * 🗂️ **Чистый интерфейс настроек (DSH Design Contract)**:
90
+ * Удалена избыточная регистрация fallback-раздела верхнего уровня бокового меню (`settings.section`), настройки доступны только через стандартную карточку в «Настройки → Плагины → Настройки плагинов» (`settings.plugin.item`).
91
+ * 🛡️ **Надёжный доступ к сервисам Cordis**:
92
+ * Доступ к сервисам ядра (`settings`, `llm`) переведён на `ctx.get(...)` с безопасным fallback к прямым свойствам контекста для исключения ошибок проксирования.
93
+
94
+ ---
95
+
87
96
  ## 🛠️ Улучшения и исправления в v0.3.8
88
97
 
89
98
  * ⚙️ **Реактивное управление настройками через `settingsScope`**:
package/README.zh.md CHANGED
@@ -73,3 +73,13 @@ dsh plugin --profile web add @goodandready/dsh-model-sync
73
73
  ## 📄 开源协议
74
74
 
75
75
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
76
+
77
+ ## 🛠️ v0.3.11 改进与修复
78
+
79
+ * 🗂️ **规范化设置界面**:
80
+ * 移除侧边栏顶层多余的 `settings.section` fallback,统一使用标准插件设置卡片 `settings.plugin.item`。
81
+ * 🛡️ **增强 Cordis 服务访问稳定性**:
82
+ * 使用 `ctx.get(...)` 安全获取 `settings` 与 `llm` 服务,避免代理对象属性解析异常。
83
+
84
+ ---
85
+
package/lib/client.js CHANGED
@@ -49,7 +49,6 @@ window.__ModuleLoader__.load({
49
49
  }
50
50
  const API = '/dsh-model-sync'
51
51
  const CAPABILITY_LABELS = { vision: { en: 'Vision', ru: 'Зрение' }, tools: { en: 'Tools', ru: 'Инструменты' }, reasoning: { en: 'Reasoning', ru: 'Рассуждение' }, embeddings: { en: 'Embeddings', ru: 'Эмбеддинги' } }
52
- function capabilityKeys(models) { return [...new Set((models ?? []).flatMap((model) => Object.entries(model?.capabilities ?? {}).filter(([, enabled]) => enabled === true).map(([key]) => key)))].sort() }
53
52
  function filterModelsByCapabilities(models, selected) { const required = [...new Set((selected ?? []).filter((key) => typeof key === 'string' && key))]; return (models ?? []).filter((model) => required.every((key) => model?.capabilities?.[key] === true)) }
54
53
  const NS = 'dsh-model-sync'
55
54
  function useLocale(ctx) {
@@ -86,7 +85,7 @@ window.__ModuleLoader__.load({
86
85
  const doTry = (provider, modelId) => { setTryState((s)=>({ ...s, [provider+':'+modelId]: { loading: true } })); fetch(`${API}/try`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider, model: modelId }) }).then(async r=>{ const b=await r.json(); if(!r.ok) throw new Error(b?.error?.message||`HTTP ${r.status}`); return b }).then(b=>setTryState((s)=>({ ...s, [provider+':'+modelId]: { ok: b.ok, latency: b.latencyMs, status: b.status, error: b.error } }))).catch(e=>setTryState((s)=>({ ...s, [provider+':'+modelId]: { error: String(e.message||e) } }))) }
87
86
  const load = React.useCallback(() => { setError(''); return fetch(`${API}/status`).then((r) => r.json()).then(setStatus).catch((e) => setError(String(e?.message ?? e))) }, [])
88
87
  const loadCredentials = React.useCallback(() => { setError(''); return fetch(`${API}/credentials`).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then(setCredentialStatus).catch((e) => setError(String(e?.message ?? e))) }, [])
89
- React.useEffect(() => { ensureStyles(); load(); loadCredentials(); const timer = setInterval(load, 15000); return () => clearInterval(timer) }, [load, loadCredentials])
88
+ React.useEffect(() => { ensureStyles(); load(); loadCredentials(); const timer = setInterval(() => { if (typeof document !== 'undefined' && document.hidden) return; load() }, 15000); return () => clearInterval(timer) }, [load, loadCredentials])
90
89
  const execute = (targetProvider, dryRunOverride = dryRun) => { if (!dryRunOverride && removeMissing && !window.confirm(t.removeMissing)) return; setBusy(true); setError(''); fetch(`${API}/run`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ provider: targetProvider || undefined, dryRun: dryRunOverride, removeMissing }) }).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then((body) => { setStatus(body); if (dryRunOverride) setPreview({ ...body, provider: targetProvider || '' }); else setPreview(null) }).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(false)) }
91
90
  const checkHealth = (targetProvider) => { setBusy(true); setError(''); fetch(`${API}/health`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(targetProvider ? { provider: targetProvider } : {}) }).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then(setStatus).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(false)) }
92
91
  const checkCredentials = (targetProvider) => { setBusy(true); setError(''); fetch(`${API}/credentials/check`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify(targetProvider ? { provider: targetProvider } : {}) }).then(async (r) => { const body = await r.json(); if (!r.ok) throw new Error(body?.error?.message ?? `HTTP ${r.status}`); return body }).then((body) => { setCredentialStatus(body.credentials); setStatus((current) => ({ ...(current ?? {}), lastHealth: body.health })) }).catch((e) => setError(String(e?.message ?? e))).finally(() => setBusy(false)) }
@@ -184,7 +183,7 @@ window.__ModuleLoader__.load({
184
183
  function ModelSyncCard({ ctx }) {
185
184
  const locale = useLocale(ctx); const t = locale === 'ru' ? ru : en; const [open, setOpen] = React.useState(false)
186
185
  const [cardStatus, setCardStatus] = React.useState(null)
187
- React.useEffect(() => { ensureStyles(); const load = () => fetch(`${API}/status`).then(r=>r.json()).then(setCardStatus).catch(()=>{}); load(); const id=setInterval(load,15000); return ()=>clearInterval(id)}, [])
186
+ React.useEffect(() => { ensureStyles(); const load = () => { if (typeof document !== 'undefined' && document.hidden) return; fetch(`${API}/status`).then(r=>r.json()).then(setCardStatus).catch(()=>{}) }; load(); if (open) return; const id = setInterval(load, 15000); return () => clearInterval(id) }, [open])
188
187
  const cardBadge = (cardStatus?.lastRun?.results?.reduce((s,r)=>s+(r.diff?.added?.length||0),0) || 0)
189
188
  return React.createElement('li', { className: 'dms-card' + (open ? ' dms-card-open' : '') },
190
189
  React.createElement('button', { type: 'button', className: 'dms-card-header', 'aria-expanded': open, 'aria-label': `${t[open ? 'hideSettings' : 'showSettings']}: ${t.title}`, onClick: () => setOpen((value) => !value) },
@@ -198,15 +197,9 @@ window.__ModuleLoader__.load({
198
197
  try { ctx.locale?.register?.(NS, { en, ru }) } catch {}
199
198
  return () => {}
200
199
  }, 'dsh-model-sync: dictionaries')
201
- const registerPluginCard = () => {
202
- try {
203
- ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', key: NS, locale: NS, inject: () => ({ ctx }) }, (props) => React.createElement(ModelSyncCard, { ...props, ctx })))
204
- return true
205
- } catch { return false }
206
- }
207
- if (!registerPluginCard()) {
208
- ctx.slots.inject('settings.section', () => ctx.slots.register({ name: 'settings.section', id: 'dsh-model-sync', order: 25, label: () => 'Model Sync' }, (props) => React.createElement(ModelSyncSection, { ...props, ctx })))
209
- }
200
+ try {
201
+ ctx.slots.inject('settings.plugin.item', () => ctx.slots.register({ name: 'settings.plugin.item', key: NS, locale: NS, inject: () => ({ ctx }) }, (props) => React.createElement(ModelSyncCard, { ...props, ctx })))
202
+ } catch {}
210
203
  }
211
204
  module.exports = { apply, inject: ['slots', 'locale', 'settingsScope'], filterModelsByCapabilities }; return module.exports
212
205
  },
package/lib/index.js CHANGED
@@ -137,7 +137,7 @@ export function apply(ctx, config) {
137
137
  try { settings = ctx.get('settings') } catch {}
138
138
  if (!bindSettings(settings, ctx)) {
139
139
  ctx.inject(['settings'], (sctx) => {
140
- bindSettings(sctx.settings, sctx)
140
+ bindSettings((typeof sctx.get === 'function' ? sctx.get('settings') : null) ?? sctx.settings, sctx)
141
141
  })
142
142
  }
143
143
 
@@ -3,13 +3,22 @@ import { listApiKeyProviders } from './inventory.js'
3
3
  export function createProviderService(ctx) {
4
4
  const readSettings = () => {
5
5
  try {
6
- return ctx.get('settings')
6
+ return (typeof ctx.get === 'function' ? (ctx.get('settings') ?? ctx.settings) : ctx.settings)
7
7
  } catch {
8
- return undefined
8
+ return ctx.settings
9
9
  }
10
10
  }
11
11
 
12
- const listProviders = () => listApiKeyProviders(ctx.llm, readSettings())
12
+ const readLlm = () => {
13
+ try {
14
+ const llm = typeof ctx.get === 'function' ? ctx.get('llm') : null
15
+ return (llm && typeof llm === 'object' && (llm.listConfigurableProviders || llm.listProviders)) ? llm : ctx.llm
16
+ } catch {
17
+ return ctx.llm
18
+ }
19
+ }
20
+
21
+ const listProviders = () => listApiKeyProviders(readLlm(), readSettings())
13
22
 
14
23
  return Object.freeze({
15
24
  listProviders,