@goodandready/dsh-usage-guard 0.1.1 → 0.1.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/README.md CHANGED
@@ -107,6 +107,23 @@ If a counter cannot be resolved from aliases, it is safely initialized to `0`. T
107
107
  ### 6. Deduplicated Diagnostic Reporting (`told`)
108
108
  Logs informative diagnostic warnings naming the exact session, turn, step, raw payload, and recovery action (e.g. `inputTokens borrowed from alias` vs `inputTokens zeroed`). Incidents are deduplicated in memory so logs are not flooded during replays, and the cache is capped at 1,000 items to prevent memory leaks.
109
109
 
110
+ ### 7. Native Web UI Settings Card (`lib/client.js`)
111
+ * Mounts into the native Settings tab under `Settings → Plugins → Plugin Settings` (`settings.plugin.item`) with real-time status badge and full localization.
112
+
113
+ ---
114
+
115
+ ## 🚀 Changed in v0.1.2
116
+
117
+ * **Native Web UI Settings Card (`settings.plugin.item`)**:
118
+ - Added frontend client module `lib/client.js` registering a native configuration card under `Settings → Plugins → Plugin Settings` bound to namespace `dsh-usage-guard` (Issue #2).
119
+ - Interactive toggles for `repair` (automatic token counter repair) and `report` (diagnostic warning console logging).
120
+ - Real-time status badge in card header (`ACTIVE` when auto-repair is enabled, `REPORT ONLY` when passive audit is selected).
121
+ - Strict compliance with DSH native theme CSS variables, core chevron icon `IconChevronDownOutline14`, 12px border radius, and `aria-expanded` accessibility.
122
+ - Complete trilingual localization for English, Russian, and Chinese (`en`, `ru`, `zh`).
123
+ - Graceful fallback slot `settings.section` for legacy core versions without the plugin settings tab.
124
+ * **Design Contract**:
125
+ - Added official UI design contract in `docs/design/DESIGN.md` complying with `project-design-contract` and `dsh-ui-design`.
126
+
110
127
  ---
111
128
 
112
129
  ## 🚀 Changed in v0.1.1
@@ -139,7 +156,7 @@ dsh plugin --profile web add @goodandready/dsh-usage-guard
139
156
 
140
157
  ---
141
158
 
142
- ## ⚙️ Configuration Reference (`settings.yaml`)
159
+ ## ⚙️ Configuration Reference (`settings.yaml` / Web UI)
143
160
 
144
161
  ```yaml
145
162
  dsh-usage-guard:
package/README.ru.md CHANGED
@@ -107,6 +107,23 @@ graph LR
107
107
  ### 6. Дедуплицированное журналирование (`told`)
108
108
  Выводит предупреждение с идентификатором сессии, номером хода, шагом и способом починки (взят по синониму или обнулён). Одинаковые инциденты не спамят в лог при повторных проигрываниях, а размер кэша ограничен 1000 записей для предотвращения утечек памяти.
109
109
 
110
+ ### 7. Нативная карточка в Web UI (`lib/client.js`)
111
+ * Карточка настроек встроена во вкладку «Настройки → Плагины → Настройки плагинов» (`settings.plugin.item`) с динамическим бейджем статуса и полной локализацией.
112
+
113
+ ---
114
+
115
+ ## 🚀 Изменения в версии v0.1.2 (Changed in v0.1.2)
116
+
117
+ * **Нативная карточка настроек Web UI (`settings.plugin.item`)**:
118
+ - Добавлен клиентский модуль `lib/client.js`, реализующий нативную карточку управления во вкладке «Настройки → Плагины → Настройки плагинов» с пространством ключа `dsh-usage-guard` (Issue #2).
119
+ - Интерактивное управление параметрами `repair` (автоматическое исправление расхода) и `report` (вывод диагностических предупреждений в консоль).
120
+ - Динамический бейдж в шапке карточки (`АКТИВЕН` при включённом исправлении, `ТОЛЬКО ЛОГ` при пассивном аудите) без необходимости раскрывать карточку.
121
+ - Полная поддержка темы DSH (нативные CSS-переменные темы, шеврон ядра `IconChevronDownOutline14`, радиус скругления 12px, доступность `aria-expanded`).
122
+ - Полноценная локализация интерфейса карточки на русский, английский и китайский языки (`en`, `ru`, `zh`).
123
+ - Запасной fallback-слот `settings.section` на случай запуска в сборках DSH без вкладки настроек плагинов.
124
+ * **Дизайн-контракт**:
125
+ - Создан обязательный документ `docs/design/DESIGN.md` в соответствии со стандартами `project-design-contract` и `dsh-ui-design`.
126
+
110
127
  ---
111
128
 
112
129
  ## 🚀 Изменения в версии v0.1.1 (Changed in v0.1.1)
@@ -136,7 +153,7 @@ dsh plugin --profile web add @goodandready/dsh-usage-guard
136
153
 
137
154
  ---
138
155
 
139
- ## ⚙️ Параметры конфигурации (`settings.yaml`)
156
+ ## ⚙️ Параметры конфигурации (`settings.yaml` / Web UI)
140
157
 
141
158
  ```yaml
142
159
  dsh-usage-guard:
package/README.zh.md CHANGED
@@ -80,7 +80,22 @@ graph LR
80
80
  3. **非负有限数字安全性校验 (`sound`)**:剔除 `NaN`、`Infinity`、负数错误码(如 `-1`)与非法格式;
81
81
  4. **安全 0 值兜底 (`repaired`)**:彻底阻断算术污染;
82
82
  5. **内存投影注册表动态切入 (`lib/patch.js`)**:无缝覆盖前置与后置投影,完整保留 `this` 上下文;
83
- 6. **防刷屏告警与内存保护 (`told`)**:集成会话识别与容量上限(1,000 项),杜绝内存泄漏与误抑制。
83
+ 6. **防刷屏告警与内存保护 (`told`)**:集成会话识别与容量上限(1,000 项),杜绝内存泄漏与误抑制;
84
+ 7. **原生 Web UI 设置卡片 (`lib/client.js`)**:嵌入 DSH 原生设置中心(`settings.plugin.item`),实时状态徽章与多语言支持。
85
+
86
+ ---
87
+
88
+ ## 🚀 v0.1.2 版本更新说明 (Changed in v0.1.2)
89
+
90
+ * **原生 Web UI 设置卡片 (`settings.plugin.item`)**:
91
+ - 新增前端客户端模块 `lib/client.js`,在「设置 → 插件 → 插件设置」标签页注册原生卡片,绑定空间 `dsh-usage-guard`(Issue #2)。
92
+ - 支持直接交互配置 `repair`(自动修复异常计数)与 `report`(记录诊断警告)。
93
+ - 卡片头部支持实时状态徽章(开启时显示绿色 `ACTIVE`,仅审计时显示黄色 `REPORT ONLY`)。
94
+ - 严格遵循 DSH 原生设计规范(CSS 变量、12px 圆角、核心折叠图标 `IconChevronDownOutline14`、无障碍 `aria-expanded`)。
95
+ - 提供中、英、俄完整三语本地化支持(`zh` / `en` / `ru`)。
96
+ - 内置 `settings.section` 降级容错机制。
97
+ * **设计契约**:
98
+ - 补充 `docs/design/DESIGN.md`,符合 `project-design-contract` 和 `dsh-ui-design` 标准。
84
99
 
85
100
  ---
86
101
 
@@ -109,6 +124,21 @@ dsh plugin --profile web add @goodandready/dsh-usage-guard
109
124
 
110
125
  ---
111
126
 
127
+ ## ⚙️ 配置参考 (`settings.yaml` / Web UI)
128
+
129
+ ```yaml
130
+ dsh-usage-guard:
131
+ repair: true
132
+ report: true
133
+ ```
134
+
135
+ | 参数 | 类型 | 默认值 | 说明 |
136
+ |---|---|---|---|
137
+ | `repair` | `boolean` | `true` | 在累加前将缺失或非数值的 Token 计数替换为 0 |
138
+ | `report` | `boolean` | `true` | 接收到损坏用量样本时在日志输出警告信息 |
139
+
140
+ ---
141
+
112
142
  ## 📄 开源协议
113
143
 
114
144
  MIT © [GooDAnDReaDY](https://github.com/GooDAnDReaDY)
package/lib/client.js ADDED
@@ -0,0 +1,276 @@
1
+ window.__ModuleLoader__.load({
2
+ id: '@goodandready/dsh-usage-guard',
3
+ factory: (require) => {
4
+ var module = { exports: {} };
5
+ const React = require('react');
6
+ const NS = 'dsh-usage-guard';
7
+
8
+ let ChevronIcon = null;
9
+ try {
10
+ const primitives = require('@deepseek-ai/dsh-client-ui-primitives');
11
+ ChevronIcon = primitives && primitives.IconChevronDownOutline14;
12
+ } catch (_) {
13
+ ChevronIcon = null;
14
+ }
15
+
16
+ function FallbackChevron(props) {
17
+ return React.createElement('svg', {
18
+ width: 14,
19
+ height: 14,
20
+ viewBox: '0 0 14 14',
21
+ fill: 'none',
22
+ stroke: 'currentColor',
23
+ strokeWidth: 1.5,
24
+ style: props.style,
25
+ }, React.createElement('path', { d: 'M3.5 5.25L7 8.75L10.5 5.25' }));
26
+ }
27
+ const Chevron = ChevronIcon || FallbackChevron;
28
+
29
+ function PluginCard({ ctx }) {
30
+ const [expanded, setExpanded] = React.useState(false);
31
+ const [snap, setSnap] = React.useState({ status: 'loading', value: null, writable: false });
32
+ const [draft, setDraft] = React.useState({ repair: true, report: true });
33
+ const [saving, setSaving] = React.useState(false);
34
+ const [msg, setMsg] = React.useState('');
35
+
36
+ const initializedRef = React.useRef(false);
37
+
38
+ React.useEffect(() => {
39
+ let off = null;
40
+ try {
41
+ const scope = ctx.settingsScope && ctx.settingsScope.bind ? ctx.settingsScope.bind({ namespace: NS }) : null;
42
+ if (!scope) {
43
+ setSnap({ status: 'unavailable', value: null, writable: true });
44
+ return;
45
+ }
46
+ const update = () => {
47
+ const s = scope.getSnapshot ? scope.getSnapshot() : { status: 'unavailable', value: null };
48
+ setSnap(s);
49
+ if (s.status === 'ready' && s.value && !initializedRef.current) {
50
+ initializedRef.current = true;
51
+ setDraft({
52
+ repair: s.value.repair ?? true,
53
+ report: s.value.report ?? true,
54
+ });
55
+ }
56
+ };
57
+ update();
58
+ if (scope.subscribe) off = scope.subscribe(update);
59
+ } catch (_) {
60
+ setSnap({ status: 'unavailable', value: null, writable: true });
61
+ }
62
+ return () => {
63
+ try { off && off(); } catch {}
64
+ };
65
+ }, [ctx]);
66
+
67
+ const t = (() => {
68
+ try {
69
+ return ctx.locale && ctx.locale.bind ? ctx.locale.bind(NS) : (k) => k;
70
+ } catch (_) {
71
+ return (k) => k;
72
+ }
73
+ })();
74
+
75
+ const onSave = async () => {
76
+ setSaving(true);
77
+ setMsg('');
78
+ try {
79
+ if (!ctx.settingsScope || !ctx.settingsScope.bind) {
80
+ setMsg('settingsScope unavailable');
81
+ setSaving(false);
82
+ return;
83
+ }
84
+ const scope = ctx.settingsScope.bind({ namespace: NS });
85
+ const errs = [];
86
+ for (const k of ['repair', 'report']) {
87
+ try {
88
+ await scope.set(k, draft[k]);
89
+ } catch (e) {
90
+ errs.push(k + ': ' + (e && e.message ? e.message : String(e)));
91
+ }
92
+ }
93
+ if (errs.length) {
94
+ setMsg(errs.join('; '));
95
+ } else {
96
+ setMsg(t('saved') || 'Saved');
97
+ }
98
+ } catch (e) {
99
+ setMsg(String(e && e.message ? e.message : e));
100
+ }
101
+ setSaving(false);
102
+ };
103
+
104
+ const isRepairActive = draft.repair !== false;
105
+ const badgeLabel = isRepairActive ? (t('active') || 'ACTIVE') : (t('reportOnly') || 'REPORT ONLY');
106
+ const badgeClass = isRepairActive ? 'ug-badge ug-badge-green' : 'ug-badge ug-badge-yellow';
107
+
108
+ return React.createElement('div', { className: 'ug-card' },
109
+ React.createElement('style', null, `
110
+ .ug-card { border:1px solid var(--dsw-alias-border-l2); background:var(--dsw-alias-bg-layer-3); border-radius:12px; list-style:none }
111
+ .ug-head { appearance:none; width:100%; font:inherit; color:inherit; text-align:left; cursor:pointer; background:0 0; border:0; border-radius:12px; display:flex; align-items:center; gap:12px; padding:14px 16px }
112
+ .ug-title { color:var(--dsw-alias-label-primary); font-size:15px; font-weight:600; line-height:1.4 }
113
+ .ug-sub { color:var(--dsw-alias-label-secondary); font-size:13px }
114
+ .ug-body { border-top:1px solid var(--dsw-alias-border-l2); margin:0 16px; padding-bottom:8px }
115
+ .ug-field { display:flex; flex-direction:column; gap:6px; padding:12px 0 }
116
+ .ug-row { display:flex; align-items:center; justify-content:space-between; gap:12px; cursor:pointer; font-weight:500; color:var(--dsw-alias-label-primary) }
117
+ .ug-checkbox { width:16px; height:16px; accent-color:var(--dsw-alias-color-primary, #6366f1); cursor:pointer }
118
+ .ug-foot { border-top:1px solid var(--dsw-alias-border-l2); display:flex; justify-content:flex-end; align-items:center; gap:8px; padding:12px 0 4px }
119
+ .ug-save { appearance:none; font:inherit; cursor:pointer; border:1px solid transparent; border-radius:8px; padding:5px 14px; font-size:13px; background:var(--dsw-alias-label-primary); color:var(--dsw-alias-bg-layer-3) }
120
+ .ug-save:disabled { opacity:0.6; cursor:not-allowed }
121
+ .ug-chev { margin-left:auto; flex:none; color:var(--dsw-alias-label-tertiary); transition:transform .16s }
122
+ .ug-chev-open { transform:rotate(180deg) }
123
+ .ug-badge { display:inline-flex; align-items:center; gap:6px; font-size:11px; font-weight:600; padding:2px 8px; border-radius:999px; border:1px solid var(--dsw-alias-border-l2); margin-left:auto }
124
+ .ug-badge-green { background:var(--dsw-alias-color-success, #2da44e); color:#fff; border-color:transparent }
125
+ .ug-badge-yellow { background:var(--dsw-alias-color-warning, #d9a400); color:#fff; border-color:transparent }
126
+ `),
127
+ React.createElement('button', {
128
+ className: 'ug-head',
129
+ onClick: () => setExpanded(!expanded),
130
+ 'aria-expanded': expanded,
131
+ },
132
+ React.createElement('span', { className: 'ug-title' }, t('title') || 'Usage Guard'),
133
+ React.createElement('span', { className: 'ug-sub' }, t('subtitle') || 'Token-usage sanitizer & session crash protection'),
134
+ React.createElement('span', { className: badgeClass }, badgeLabel),
135
+ React.createElement(Chevron, { className: 'ug-chev' + (expanded ? ' ug-chev-open' : '') })
136
+ ),
137
+ expanded ? React.createElement('div', { className: 'ug-body' },
138
+ snap.status === 'loading' ? React.createElement('div', { className: 'ug-field', style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, t('loading') || 'Loading…') : null,
139
+ snap.status === 'unavailable' ? React.createElement('div', { className: 'ug-field', style: { color: 'var(--dsw-alias-label-secondary)', fontSize: 13 } }, t('unavailable') || 'Settings scope unavailable') : null,
140
+ snap.status === 'ready' ? React.createElement('div', null,
141
+ React.createElement('div', { className: 'ug-field' },
142
+ React.createElement('label', { className: 'ug-row' },
143
+ React.createElement('span', null, t('repair') || 'Auto-repair token counters'),
144
+ React.createElement('input', {
145
+ type: 'checkbox',
146
+ className: 'ug-checkbox',
147
+ checked: !!draft.repair,
148
+ onChange: (e) => setDraft({ ...draft, repair: e.target.checked }),
149
+ })
150
+ ),
151
+ React.createElement('div', { className: 'ug-sub' }, t('repairDesc') || 'Replace missing or non-numeric counters with 0 before summing to prevent NaN history lockups')
152
+ ),
153
+ React.createElement('div', { className: 'ug-field' },
154
+ React.createElement('label', { className: 'ug-row' },
155
+ React.createElement('span', null, t('report') || 'Log diagnostic warnings'),
156
+ React.createElement('input', {
157
+ type: 'checkbox',
158
+ className: 'ug-checkbox',
159
+ checked: !!draft.report,
160
+ onChange: (e) => setDraft({ ...draft, report: e.target.checked }),
161
+ })
162
+ ),
163
+ React.createElement('div', { className: 'ug-sub' }, t('reportDesc') || 'Log a line naming the turn, step, and raw sample whenever damaged usage arrives')
164
+ ),
165
+ React.createElement('div', { className: 'ug-foot' },
166
+ msg ? React.createElement('span', { style: { fontSize: 12, color: 'var(--dsw-alias-label-secondary)', marginRight: 'auto' } }, msg) : null,
167
+ React.createElement('button', {
168
+ className: 'ug-save',
169
+ onClick: onSave,
170
+ disabled: saving,
171
+ }, saving ? (t('saving') || 'Saving…') : (t('save') || 'Save'))
172
+ )
173
+ ) : null
174
+ ) : null
175
+ );
176
+ }
177
+
178
+ module.exports.inject = ['slots', 'locale', 'settingsScope'];
179
+ module.exports.apply = function apply(ctx) {
180
+ try {
181
+ ctx.locale.register(NS, {
182
+ en: {
183
+ title: 'Usage Guard',
184
+ subtitle: 'Token-usage sanitizer & session crash protection',
185
+ active: 'ACTIVE',
186
+ reportOnly: 'REPORT ONLY',
187
+ saved: 'Saved',
188
+ loading: 'Loading…',
189
+ unavailable: 'Settings scope unavailable',
190
+ save: 'Save',
191
+ saving: 'Saving…',
192
+ repair: 'Auto-repair token counters',
193
+ repairDesc: 'Replace missing or non-numeric counters with 0 before summing to prevent NaN history lockups.',
194
+ report: 'Log diagnostic warnings',
195
+ reportDesc: 'Log a line naming the turn, step, and raw sample whenever damaged usage arrives.',
196
+ },
197
+ ru: {
198
+ title: 'Защита истории токенов',
199
+ subtitle: 'Санитайзер расхода токенов и защита от сбоев истории',
200
+ active: 'АКТИВЕН',
201
+ reportOnly: 'ТОЛЬКО ЛОГ',
202
+ saved: 'Сохранено',
203
+ loading: 'Загрузка…',
204
+ unavailable: 'Пространство настроек недоступно',
205
+ save: 'Сохранить',
206
+ saving: 'Сохранение…',
207
+ repair: 'Автоматическое исправление расхода',
208
+ repairDesc: 'Подставлять ноль вместо повреждённых счётчиков до сложения для предотвращения блокировки истории диалогов.',
209
+ report: 'Диагностические предупреждения',
210
+ reportDesc: 'Логировать номер хода, шага и исходные данные при обнаружении битой порции расхода.',
211
+ },
212
+ zh: {
213
+ title: '用量与会话防崩守卫',
214
+ subtitle: 'Token 用量数据清洗与历史记录防白屏保护',
215
+ active: '已激活',
216
+ reportOnly: '仅记录',
217
+ saved: '已保存',
218
+ loading: '加载中…',
219
+ unavailable: '设置作用域不可用',
220
+ save: '保存',
221
+ saving: '保存中…',
222
+ repair: '自动修复用量计数',
223
+ repairDesc: '在累加前将缺失或非数字计数置为 0,彻底防止 NaN 导致会话历史永久白屏。',
224
+ report: '记录诊断告警',
225
+ reportDesc: '当检测到异常用量数据包时,在控制台记录轮次、步骤和原始数据。',
226
+ },
227
+ });
228
+ } catch (_) {}
229
+
230
+ let placed = false;
231
+ try {
232
+ if (ctx.slots && typeof ctx.slots.inject === 'function') {
233
+ ctx.slots.inject('settings.plugin.item', () => {
234
+ placed = true;
235
+ ctx.slots.register(
236
+ {
237
+ name: 'settings.plugin.item',
238
+ key: NS,
239
+ locale: NS,
240
+ inject: () => ({ ctx }),
241
+ },
242
+ PluginCard,
243
+ );
244
+ });
245
+ }
246
+ } catch (_) {}
247
+
248
+ const timer = setTimeout(() => {
249
+ if (placed) return;
250
+ try {
251
+ if (ctx.slots && typeof ctx.slots.inject === 'function') {
252
+ ctx.slots.inject('settings.section', () => {
253
+ ctx.slots.register(
254
+ {
255
+ name: 'settings.section',
256
+ id: '@goodandready/dsh-usage-guard',
257
+ order: 50,
258
+ locale: NS,
259
+ label: () => (ctx.locale && ctx.locale.bind ? ctx.locale.bind(NS)('title') : 'Usage Guard'),
260
+ inject: () => ({ ctx }),
261
+ },
262
+ PluginCard,
263
+ );
264
+ });
265
+ }
266
+ } catch (_) {}
267
+ }, 3000);
268
+
269
+ if (typeof ctx.effect === 'function') {
270
+ ctx.effect(() => () => clearTimeout(timer), 'dsh-usage-guard: fallback section');
271
+ }
272
+ };
273
+
274
+ return module.exports;
275
+ },
276
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@goodandready/dsh-usage-guard",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "Keeps a malformed token-usage sample from taking a whole session history down with it: a missing or non-numeric counter becomes zero before the harness adds it up, so the fold never yields NaN and already-broken sessions open again.",
5
5
  "keywords": [
6
6
  "deepseek-harness",
@@ -22,6 +22,7 @@
22
22
  "main": "./lib/index.js",
23
23
  "exports": {
24
24
  ".": "./lib/index.js",
25
+ "./client": "./lib/client.js",
25
26
  "./package.json": "./package.json",
26
27
  "./cordis.patch.yml": "./cordis.patch.yml"
27
28
  },
@@ -40,6 +41,10 @@
40
41
  "dsh": {
41
42
  "bundle": {
42
43
  "patch": "./cordis.patch.yml"
44
+ },
45
+ "client": {
46
+ "platform": "web",
47
+ "inject": []
43
48
  }
44
49
  },
45
50
  "peerDependencies": {