@goodandready/dsh-dsml-artifact-guard 0.2.8 → 0.2.9

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
@@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ---
9
9
 
10
+ ## [0.2.9] - 2026-10-07
11
+
12
+ ### Fixed
13
+ - **Settings Card Locale Snapshot Extraction**: safely extract active locale string from `LocaleSnapshot` object (`snap?.active`) returned by `ctx.locale.getLocale()` / `getSnapshot()`, eliminating fatal `TypeError: rawLang.startsWith is not a function` during settings card mount (#50, #57).
14
+ - **Russian Language Registration & Ownership**: removed embedded `ru` dictionary from the plugin bundle to prevent `(namespace, 'ru')` duplicate registration collisions in core; delegate Russian translations to `dsh-russian-lang` via `props.t` and `ctx.locale.bind(NS)` (#58).
15
+ - **Volatile Per-Field Settings Schema**: marked configuration fields volatile individually, enabling DSH to discover and generate settings form controls (#52).
16
+
10
17
  ## [0.2.8] - 2026-09-26
11
18
 
12
19
  ### Added
package/lib/client.js CHANGED
@@ -91,38 +91,6 @@ window.__ModuleLoader__.load({
91
91
  saveError: '保存失败: {msg}',
92
92
  };
93
93
 
94
- const ru = {
95
- title: 'Защита от артефактов DSML',
96
- sub: 'Фильтрация утекших замыкающих тегов протокола DSML из потоков моделей DeepSeek',
97
- version: 'Версия',
98
- updateChecking: 'Проверка обновлений…',
99
- updateAvailable: 'Доступно обновление: v{v}',
100
- upToDate: 'Установлена последняя версия',
101
- updateNow: 'Обновить сейчас',
102
- updating: 'Обновление…',
103
- updateSuccess: 'Обновлено до v{v}. Перезапустите службу DSH для применения изменений.',
104
- updateFailed: 'Ошибка обновления: {msg}',
105
- checkUpdateFailed: 'Не удалось проверить обновления',
106
- mode: 'Режим работы',
107
- modeSanitize: 'Санация (вырезать утекшие замыкающие теги)',
108
- modeAudit: 'Аудит (только логировать предупреждения в журнал)',
109
- modeDisabled: 'Отключено (пропускать все потоки без изменений)',
110
- modelPattern: 'Шаблон модели (RegExp)',
111
- modelPatternHint: 'Регулярное выражение без учёта регистра для ID модели (по умолчанию: deepseek).',
112
- providers: 'Целевые провайдеры (опционально)',
113
- providersPlaceholder: 'все провайдеры (через запятую, напр. commandcode, deepseek-official)',
114
- providersHint: 'Список ID провайдеров через запятую. Оставьте пустым для защиты всех провайдеров.',
115
- legacyDetected: 'Обнаружены устаревшие параметры providerId / modelId (действуют как дополнительное правило).',
116
- legacyClear: 'Очистить устаревшие поля',
117
- providerId: 'Целевой провайдер (устаревшее)',
118
- modelId: 'Целевая модель (устаревшее)',
119
- disabledNotice: '⚠️ Защита отключена. Замыкающие теги DSML будут попадать в интерфейс чата.',
120
- statusUnavailable: 'Служба настроек недоступна.',
121
- saving: 'Сохранение…',
122
- ready: 'Сохранить настройки',
123
- saved: 'Настройки сохранены',
124
- saveError: 'Ошибка сохранения: {msg}',
125
- };
126
94
 
127
95
  const DEFAULTS = {
128
96
  mode: 'sanitize',
@@ -164,11 +132,38 @@ window.__ModuleLoader__.load({
164
132
  }
165
133
  const scope = scopeRef.current;
166
134
 
167
- const rawLang = ((typeof _ctx?.locale?.getLocale === 'function' ? _ctx.locale.getLocale() : null) || (typeof navigator !== 'undefined' && navigator.language) || 'en');
168
- const currentLang = rawLang.startsWith('ru') ? 'ru' : (rawLang.startsWith('zh') ? 'zh' : 'en');
169
- const dict = currentLang === 'ru' ? ru : (currentLang === 'zh' ? zh : en);
135
+ const rawLang = (() => {
136
+ try {
137
+ const snap = typeof _ctx?.locale?.getLocale === 'function'
138
+ ? _ctx.locale.getLocale()
139
+ : (typeof _ctx?.locale?.getSnapshot === 'function' ? _ctx.locale.getSnapshot() : null);
140
+ if (typeof snap?.active === 'string' && snap.active) return snap.active;
141
+ if (typeof snap === 'string' && snap) return snap;
142
+ } catch (err) {
143
+ /* Fallback if locale service getter fails */
144
+ }
145
+ if (typeof navigator !== 'undefined' && typeof navigator.language === 'string' && navigator.language) {
146
+ return navigator.language;
147
+ }
148
+ return 'en';
149
+ })();
150
+ const currentLang = rawLang.startsWith('zh') ? 'zh' : 'en';
151
+ const dict = currentLang === 'zh' ? zh : en;
152
+
153
+ const tBound = React.useMemo(() => {
154
+ if (typeof t === 'function') return t;
155
+ if (typeof _ctx?.locale?.bind === 'function') {
156
+ try {
157
+ return _ctx.locale.bind(NS);
158
+ } catch (err) {
159
+ /* Locale bind fallback */
160
+ }
161
+ }
162
+ return null;
163
+ }, [t, _ctx]);
164
+
170
165
  const tt = (k, params) => {
171
- let str = (typeof t === 'function' ? t(k) : null) || dict[k] || en[k] || k;
166
+ let str = (typeof tBound === 'function' ? tBound(k) : null) || dict[k] || en[k] || k;
172
167
  if (params && typeof params === 'object') {
173
168
  for (const [pk, pv] of Object.entries(params)) {
174
169
  str = str.replace(new RegExp('\\{' + pk + '\\}', 'g'), String(pv));
@@ -675,7 +670,7 @@ window.__ModuleLoader__.load({
675
670
  if (typeof ctx.locale?.register === 'function') {
676
671
  const registerLocale = () => {
677
672
  try {
678
- return ctx.locale.register(NS, { en, zh, ru });
673
+ return ctx.locale.register(NS, { en, zh });
679
674
  } catch (err) {
680
675
  /* Locale namespace already registered or unavailable */
681
676
  }
package/lib/index.js CHANGED
@@ -9,17 +9,24 @@ const KEEP = 96;
9
9
  export const name = '@goodandready/dsh-dsml-artifact-guard';
10
10
  export const inject = ['llm', 'settings', 'webServer'];
11
11
 
12
- const rawConfig = Schema.object({
13
- mode: Schema.string().default('sanitize'),
14
- modelPattern: Schema.string().default('deepseek'),
15
- providers: Schema.array(Schema.string()).default([]),
16
- providerId: Schema.string(),
17
- modelId: Schema.string(),
12
+ // DSH builds a namespace's settings form from the PER-FIELD volatile markers of the
13
+ // entry schema. Wrapping the object in .volatile() produced none of them, so no form was
14
+ // ever generated. Mark each user-editable field instead.
15
+ export const Config = Schema.object({
16
+ mode: Schema.string().default('sanitize').volatile().description('How to handle detected artifact markup'),
17
+ modelPattern: Schema.string().default('deepseek').volatile().description('Model name substring this plugin guards'),
18
+ providers: Schema.array(Schema.string()).default([]).volatile().description('Restrict guarding to these providers; empty means all'),
19
+ providerId: Schema.string().volatile().description('Legacy exact provider id match'),
20
+ modelId: Schema.string().volatile().description('Legacy exact model id match'),
18
21
  });
19
22
 
20
- export const Config = typeof rawConfig.volatile === 'function'
21
- ? rawConfig.volatile()
22
- : rawConfig.extra('volatile', true);
23
+ // Unwrap Volatile boxes before any caller reads a value.
24
+ export function plainConfig(value) {
25
+ if (value === null || typeof value !== 'object') return value;
26
+ if (Array.isArray(value)) return value.map(plainConfig);
27
+ if (typeof value.get === 'function') return plainConfig(value.get());
28
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, plainConfig(v)]));
29
+ }
23
30
 
24
31
  export function matchesScope(o, c) {
25
32
  if (!c || c.mode === 'disabled') return false;
@@ -139,25 +146,21 @@ export async function* sanitizeDsmlArtifacts(s, { mode = 'sanitize', onArtifact
139
146
 
140
147
  export function unwrapConfig(conf) {
141
148
  if (!conf) return {};
142
- if (typeof conf.get === 'function') {
149
+ // A Volatile reference (any field is now volatile) resolves through .get().
150
+ const readRef = (val) => {
143
151
  try {
144
- const val = conf.get();
145
- return (val && typeof val === 'object') ? val : {};
146
- } catch (err) {
147
- /* Volatile getter failed */
152
+ const inner = typeof val?.get === 'function' ? val.get() : val;
153
+ return inner && typeof inner === 'object' ? inner : {};
154
+ } catch {
148
155
  return {};
149
156
  }
150
- }
157
+ };
158
+ if (typeof conf.get === 'function') return readRef(conf);
151
159
  try {
152
- const res = Config(conf);
153
- if (res && typeof res.get === 'function') {
154
- const val = res.get();
155
- return (val && typeof val === 'object') ? val : {};
156
- }
157
- return (res && typeof res === 'object') ? res : {};
158
- } catch (err) {
159
- /* Config validation fallback to object */
160
- return (conf && typeof conf === 'object') ? conf : {};
160
+ // Config() returns volatile boxes now, so unwrap them; a plain input stays plain.
161
+ return plainConfig(Config(conf));
162
+ } catch {
163
+ return plainConfig(conf) && typeof plainConfig(conf) === 'object' ? plainConfig(conf) : {};
161
164
  }
162
165
  }
163
166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-dsml-artifact-guard",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Sanitizes leaked DeepSeek DSML closing tags from model text streams",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -33,12 +33,15 @@
33
33
  },
34
34
  "peerDependencies": {
35
35
  "@deepseek-ai/cordis": "^4.0.1",
36
- "@deepseek-ai/schemastery": "^3.18.1"
36
+ "@deepseek-ai/schemastery": "^3.18.4"
37
37
  },
38
38
  "exports": {
39
39
  ".": "./lib/index.js",
40
40
  "./client": "./lib/client.js",
41
41
  "./cordis.patch.yml": "./cordis.patch.yml",
42
42
  "./package.json": "./package.json"
43
+ },
44
+ "devDependencies": {
45
+ "@deepseek-ai/schemastery": "^3.18.4"
43
46
  }
44
47
  }