@goodandready/dsh-dsml-artifact-guard 0.2.8 → 0.2.10
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 +16 -0
- package/README.md +2 -2
- package/lib/client.js +34 -52
- package/lib/index.js +26 -39
- package/package.json +5 -2
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
---
|
|
9
9
|
|
|
10
|
+
## [0.2.10] - 2026-10-07
|
|
11
|
+
|
|
12
|
+
### Removed
|
|
13
|
+
- **Dead `settings.register` Fallback**: removed dead legacy registration path in `apply()` and eliminated potential un-unwrapped volatile box leak on host settings delivery (#54, #55, #59).
|
|
14
|
+
- **Dead `settings.plugin.item` Slot**: removed dead slot registration in client bundle, routing exclusively through active `plugins.row.config` and `plugins.item` seats (#59).
|
|
15
|
+
|
|
16
|
+
### Optimized
|
|
17
|
+
- **Single-Pass Config Unwrapping**: optimized `unwrapConfig` error fallback to evaluate raw plain config in a single pass instead of three recursive walks (#56).
|
|
18
|
+
|
|
19
|
+
## [0.2.9] - 2026-10-07
|
|
20
|
+
|
|
21
|
+
### Fixed
|
|
22
|
+
- **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).
|
|
23
|
+
- **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).
|
|
24
|
+
- **Volatile Per-Field Settings Schema**: marked configuration fields volatile individually, enabling DSH to discover and generate settings form controls (#52).
|
|
25
|
+
|
|
10
26
|
## [0.2.8] - 2026-09-26
|
|
11
27
|
|
|
12
28
|
### Added
|
package/README.md
CHANGED
|
@@ -119,8 +119,8 @@ The guard retains a minimal 96-byte window until the next chunk or `finish` even
|
|
|
119
119
|
* **`disabled`**: Bypasses processing entirely.
|
|
120
120
|
|
|
121
121
|
### 5. Native Web UI Settings Card
|
|
122
|
-
Registered directly in
|
|
123
|
-
* **Reactive Configuration**: Adjust `mode`, `
|
|
122
|
+
Registered directly in DeepSeek Harness `plugins.row.config` and `plugins.item` slots (`lib/client.js`):
|
|
123
|
+
* **Reactive Configuration**: Adjust `mode`, `providers`, and `modelPattern` on the fly without restarting the harness.
|
|
124
124
|
* **Snapshot State Awareness**: Gracefully handles snapshot loading, ready, and unavailable states.
|
|
125
125
|
* **Protection Bypass Warning**: Displays a prominent `OFF` badge and warning banner when the guard is set to `disabled`.
|
|
126
126
|
|
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 = ((
|
|
168
|
-
|
|
169
|
-
|
|
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
|
|
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
|
|
673
|
+
return ctx.locale.register(NS, { en, zh });
|
|
679
674
|
} catch (err) {
|
|
680
675
|
/* Locale namespace already registered or unavailable */
|
|
681
676
|
}
|
|
@@ -712,9 +707,8 @@ window.__ModuleLoader__.load({
|
|
|
712
707
|
}
|
|
713
708
|
}
|
|
714
709
|
|
|
715
|
-
//
|
|
716
|
-
// page from 'plugins.row.config'.
|
|
717
|
-
// fallback for cores that still render the old card slot.
|
|
710
|
+
// Plugins page row configuration: the Plugins page renders a row's own configuration
|
|
711
|
+
// page from 'plugins.row.config'.
|
|
718
712
|
registerSlotWhenReady('plugins.row.config', () =>
|
|
719
713
|
ctx.slots.register(
|
|
720
714
|
{
|
|
@@ -747,18 +741,6 @@ window.__ModuleLoader__.load({
|
|
|
747
741
|
)
|
|
748
742
|
);
|
|
749
743
|
|
|
750
|
-
registerSlotWhenReady('settings.plugin.item', () =>
|
|
751
|
-
ctx.slots.register(
|
|
752
|
-
{
|
|
753
|
-
name: 'settings.plugin.item',
|
|
754
|
-
key: NS,
|
|
755
|
-
order: 95,
|
|
756
|
-
locale: NS,
|
|
757
|
-
inject: () => ({ ctx }),
|
|
758
|
-
},
|
|
759
|
-
(props) => React.createElement(PluginCard, { ...props, ctx: (props && props.ctx) || ctx })
|
|
760
|
-
)
|
|
761
|
-
);
|
|
762
744
|
};
|
|
763
745
|
|
|
764
746
|
return module.exports;
|
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
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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,30 +146,28 @@ export async function* sanitizeDsmlArtifacts(s, { mode = 'sanitize', onArtifact
|
|
|
139
146
|
|
|
140
147
|
export function unwrapConfig(conf) {
|
|
141
148
|
if (!conf) return {};
|
|
149
|
+
// A Volatile reference (any field is now volatile) resolves through .get().
|
|
142
150
|
if (typeof conf.get === 'function') {
|
|
143
151
|
try {
|
|
144
|
-
const
|
|
145
|
-
|
|
152
|
+
const inner = conf.get();
|
|
153
|
+
const plain = plainConfig(inner);
|
|
154
|
+
return plain && typeof plain === 'object' ? plain : {};
|
|
146
155
|
} catch (err) {
|
|
147
|
-
/*
|
|
156
|
+
/* Getter threw; return safe empty object fallback */
|
|
148
157
|
return {};
|
|
149
158
|
}
|
|
150
159
|
}
|
|
151
160
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
const val = res.get();
|
|
155
|
-
return (val && typeof val === 'object') ? val : {};
|
|
156
|
-
}
|
|
157
|
-
return (res && typeof res === 'object') ? res : {};
|
|
161
|
+
// Config() returns volatile boxes now, so unwrap them; a plain input stays plain.
|
|
162
|
+
return plainConfig(Config(conf));
|
|
158
163
|
} catch (err) {
|
|
159
|
-
/*
|
|
160
|
-
|
|
164
|
+
/* Schema validation threw; fallback to single-pass unwrapped raw config */
|
|
165
|
+
const plain = plainConfig(conf);
|
|
166
|
+
return plain && typeof plain === 'object' ? plain : {};
|
|
161
167
|
}
|
|
162
168
|
}
|
|
163
169
|
|
|
164
170
|
export function apply(ctx, initialConfig = {}) {
|
|
165
|
-
const NS = '@goodandready/dsh-dsml-artifact-guard';
|
|
166
171
|
let activeConfig = unwrapConfig(initialConfig);
|
|
167
172
|
|
|
168
173
|
let getConfig = () => (typeof initialConfig?.get === 'function' ? unwrapConfig(initialConfig) : activeConfig);
|
|
@@ -177,25 +182,7 @@ export function apply(ctx, initialConfig = {}) {
|
|
|
177
182
|
// DSH 0.1.7 SettingsForms: suppress generic duplicate form, custom card mounts in plugins seat
|
|
178
183
|
if (ctx.settings && typeof ctx.settings.configure === 'function') {
|
|
179
184
|
ctx.effect(() => ctx.settings.configure({ auto: false }), 'dsh-dsml-artifact-guard: settings policy');
|
|
180
|
-
} else if (typeof ctx.inject === 'function') {
|
|
181
|
-
// Legacy settings service fallback
|
|
182
|
-
ctx.inject(['settings'], (sctx) => {
|
|
183
|
-
if (typeof sctx.settings?.register === 'function') {
|
|
184
|
-
try {
|
|
185
|
-
const scope = sctx.settings.register(NS, Config, { base: initialConfig });
|
|
186
|
-
getConfig = () => Config(scope?.get() ?? initialConfig) ?? {};
|
|
187
|
-
if (typeof scope?.watch === 'function') {
|
|
188
|
-
sctx.effect(() => scope.watch(() => {
|
|
189
|
-
activeConfig = getConfig();
|
|
190
|
-
}));
|
|
191
|
-
}
|
|
192
|
-
} catch (err) {
|
|
193
|
-
/* Legacy settings service registration unavailable */
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
});
|
|
197
185
|
}
|
|
198
|
-
|
|
199
186
|
// Register one-click updater endpoint
|
|
200
187
|
if (ctx.webServer && typeof ctx.webServer.register === 'function') {
|
|
201
188
|
const mountUpdater = () => registerPluginUpdater(ctx, {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goodandready/dsh-dsml-artifact-guard",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.10",
|
|
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.
|
|
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
|
}
|