dsh-session-guard 3.2.5 → 4.0.1
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.ja.md +159 -143
- package/CHANGELOG.ko.md +159 -143
- package/CHANGELOG.md +232 -216
- package/INSTALL.ja.md +5 -0
- package/INSTALL.ko.md +5 -0
- package/INSTALL.md +5 -0
- package/INSTALL.zh.md +5 -0
- package/LICENSE +21 -21
- package/README.de.md +375 -0
- package/README.en.md +16 -1
- package/README.es.md +374 -0
- package/README.fr.md +373 -0
- package/README.it.md +374 -0
- package/README.ja.md +16 -1
- package/README.ko.md +16 -1
- package/README.md +30 -6
- package/README.ru.md +374 -0
- package/cordis.patch.yml +10 -10
- package/dsh.plugin.json +16 -16
- package/lib/client.js +0 -345
- package/lib/client.js.map +1 -1
- package/package.json +8 -10
- package/src/bridge.js +136 -136
- package/src/client/index.ts +156 -168
- package/src/client/locales.ts +1 -199
- package/src/client/pause-button-text.ts +60 -60
- package/src/client/pause-button.tsx +129 -129
- package/src/client/scope-face.ts +20 -20
- package/src/client/settings-card.tsx +391 -391
- package/src/client/styles.ts +30 -30
- package/src/detect.js +27 -27
- package/src/gate.js +93 -93
- package/src/pause-gate.js +544 -544
- package/src/pause-store.js +101 -101
- package/src/retry.js +227 -227
- package/src/scheduler.js +42 -42
- package/src/step-gate.js +399 -399
- package/src/store.js +77 -77
- package/src/wiring.js +305 -305
- package/locale/de.json +0 -6
- package/locale/en.json +0 -6
- package/locale/es.json +0 -6
- package/locale/fr.json +0 -6
- package/locale/it.json +0 -6
- package/locale/ja.json +0 -6
- package/locale/ko.json +0 -6
- package/locale/ru.json +0 -6
- package/locale/zh.json +0 -6
- package/src/client/shims.d.ts +0 -21
package/src/client/locales.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/** `session-guard` client dictionaries (zh / en / ja / ko
|
|
1
|
+
/** `session-guard` client dictionaries (zh / en / ja / ko). */
|
|
2
2
|
|
|
3
3
|
/** Dictionary namespace owned by this plugin. */
|
|
4
4
|
export const NS = 'session-guard'
|
|
@@ -159,203 +159,5 @@ export const ko: Record<keyof typeof zh, string> = {
|
|
|
159
159
|
'status.peak_all': '피크·전체 정지',
|
|
160
160
|
}
|
|
161
161
|
|
|
162
|
-
/** French dictionary (keys mirror zh). */
|
|
163
|
-
export const fr: Record<keyof typeof zh, string> = {
|
|
164
|
-
'badge.normal': 'Normal',
|
|
165
|
-
'badge.paused': 'En pause (pic)',
|
|
166
|
-
'badge_paused_peak': 'Pause pic en cours',
|
|
167
|
-
'settings.enabled': 'Pause auto en période de pic',
|
|
168
|
-
'settings.enabled.desc': 'Mettre automatiquement en pause les sessions actives pendant les heures de pic',
|
|
169
|
-
'settings.offPeakAutoResume': 'Reprise auto hors pic',
|
|
170
|
-
'settings.offPeakAutoResume.desc': 'Reprendre automatiquement les sessions en pause pendant les heures creuses',
|
|
171
|
-
'settings.weekendMode': 'Mode week-end',
|
|
172
|
-
'settings.weekendMode.desc': 'Détecter les week-ends → pas de pause auto le week-end',
|
|
173
|
-
'settings.queueFallback': 'File d\u2019attente de secours',
|
|
174
|
-
'settings.queueFallback.desc': 'Repli sur la file d\u2019attente verrouillée quand la passerelle personnalisée est indisponible (fail-open)',
|
|
175
|
-
'settings.retryEnabled': 'Nouvelle tentative auto',
|
|
176
|
-
'settings.retryEnabled.desc': 'Reprise auto après échec transitoire (désactivé par défaut, conservateur)',
|
|
177
|
-
'settings.timezone': 'Fuseau horaire',
|
|
178
|
-
'settings.peakWindows': 'Plages horaires de pic',
|
|
179
|
-
'settings.providerGuard': 'Garde source officielle',
|
|
180
|
-
'settings.providerGuard.desc': 'En période de pic, seules les sources officielles DeepSeek sont bloquées ; les fournisseurs locaux/tiers continuent',
|
|
181
|
-
'settings.officialProviders': 'IDs de fournisseur officiels supplémentaires',
|
|
182
|
-
'settings.officialProviders.desc': 'Correspondance exacte, priorité maximale (séparés par des virgules)',
|
|
183
|
-
'settings.officialBaseURLs': 'Hôtes des points de terminaison officiels',
|
|
184
|
-
'settings.officialBaseURLs.desc': 'Hôtes après normalisation de la baseURL (séparés par des virgules)',
|
|
185
|
-
'settings.deferredMode': 'Mode d\u2019interception',
|
|
186
|
-
'settings.deferredMode.desc': 'Suspendre sans erreur, ou générer une erreur et mettre en file différée',
|
|
187
|
-
'settings.deferredResume': 'Reprise auto après le pic',
|
|
188
|
-
'settings.deferredResume.desc': 'Si désactivé, les requêtes/sessions différées restent en attente jusqu\u2019à un /resume manuel',
|
|
189
|
-
'settings.deferredResumeText': 'Message de reprise',
|
|
190
|
-
'settings.deferredResumeText.desc': 'Texte envoyé lorsqu\u2019une session en mode erreur reprend hors pic',
|
|
191
|
-
'settings.guardSubagents': 'Inclure les sous-agents',
|
|
192
|
-
'settings.guardSubagents.desc': 'Les requêtes des sous-agents sont aussi facturées ; gardées par défaut',
|
|
193
|
-
'settings.officialHint': 'Ordre de décision : liste d\u2019ids explicites → point de terminaison baseURL → point de terminaison par défaut du catalogue → id intégré. Pour un verdict erroné, ouvrez /session-guard/provider?provider=<id> et consultez matchedBy.',
|
|
194
|
-
'settings.unavailable': 'Paramètres indisponibles : assurez-vous que dsh-session-guard est assemblé dans ce profil.',
|
|
195
|
-
'status.normal': 'Fonctionnement normal',
|
|
196
|
-
'status.paused_peak': 'Pause pic en cours',
|
|
197
|
-
'status.peak_official': 'Pic · officiel uniquement',
|
|
198
|
-
'status.peak_all': 'Pic · tout en pause',
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
/** German dictionary (keys mirror zh). */
|
|
202
|
-
export const de: Record<keyof typeof zh, string> = {
|
|
203
|
-
'badge.normal': 'Normal',
|
|
204
|
-
'badge.paused': 'Pausiert (Spitzenzeit)',
|
|
205
|
-
'badge_paused_peak': 'Spitzenzeit-Pause aktiv',
|
|
206
|
-
'settings.enabled': 'Auto-Pause zur Spitzenzeit',
|
|
207
|
-
'settings.enabled.desc': 'Laufende Sitzungen während der Spitzenzeiten automatisch pausieren',
|
|
208
|
-
'settings.offPeakAutoResume': 'Auto-Fortsetzung außerhalb der Spitzenzeit',
|
|
209
|
-
'settings.offPeakAutoResume.desc': 'Pausierte Sitzungen außerhalb der Spitzenzeiten automatisch fortsetzen',
|
|
210
|
-
'settings.weekendMode': 'Wochenendmodus',
|
|
211
|
-
'settings.weekendMode.desc': 'Wochenenden erkennen → keine Auto-Pause am Wochenende',
|
|
212
|
-
'settings.queueFallback': 'Fallback-Warteschlange',
|
|
213
|
-
'settings.queueFallback.desc': 'Auf Sperr-Warteschlange zurückgreifen, wenn eigenes Sitzungs-Gate nicht verfügbar ist (fail-open)',
|
|
214
|
-
'settings.retryEnabled': 'Automatischer Wiederholungsversuch',
|
|
215
|
-
'settings.retryEnabled.desc': 'Automatische Fortsetzung nach kurzzeitigem Fehler (standardmäßig aus, konservativ)',
|
|
216
|
-
'settings.timezone': 'Zeitzone',
|
|
217
|
-
'settings.peakWindows': 'Spitzenzeitfenster',
|
|
218
|
-
'settings.providerGuard': 'Offizielle-Quellen-Prüfung',
|
|
219
|
-
'settings.providerGuard.desc': 'Zur Spitzenzeit nur DeepSeek-offizielle Quellen blockieren; lokale/Dritt-Anbieter laufen weiter',
|
|
220
|
-
'settings.officialProviders': 'Zusätzliche offizielle Provider-IDs',
|
|
221
|
-
'settings.officialProviders.desc': 'Exakte Übereinstimmung, höchste Priorität (kommagetrennt)',
|
|
222
|
-
'settings.officialBaseURLs': 'Offizielle Endpunkt-Hosts',
|
|
223
|
-
'settings.officialBaseURLs.desc': 'Hosts nach baseURL-Normalisierung (kommagetrennt)',
|
|
224
|
-
'settings.deferredMode': 'Abfangmodus',
|
|
225
|
-
'settings.deferredMode.desc': 'Ohne Fehler anhalten oder Fehler erzeugen und in Warteschlange stellen',
|
|
226
|
-
'settings.deferredResume': 'Auto-Fortsetzung nach Spitzenzeit',
|
|
227
|
-
'settings.deferredResume.desc': 'Wenn aus, bleiben zurückgestellte Anfragen/Sitzungen bis zum manuellen /resume geparkt',
|
|
228
|
-
'settings.deferredResumeText': 'Fortsetzungsnachricht',
|
|
229
|
-
'settings.deferredResumeText.desc': 'Text, der gesendet wird, wenn eine Sitzung im Fehlermodus nach der Spitzenzeit fortgesetzt wird',
|
|
230
|
-
'settings.guardSubagents': 'Unteragenten einbeziehen',
|
|
231
|
-
'settings.guardSubagents.desc': 'Anfragen von Unteragenten werden ebenfalls abgerechnet; standardmäßig überwacht',
|
|
232
|
-
'settings.officialHint': 'Entscheidungsreihenfolge: explizite ID-Liste → baseURL-Endpunkt → Katalog-Standardendpunkt → eingebaute ID. Bei Fehlentscheidung /session-guard/provider?provider=<id> öffnen und matchedBy prüfen.',
|
|
233
|
-
'settings.unavailable': 'Einstellungen nicht verfügbar: Stellen Sie sicher, dass dsh-session-guard in diesem Profil eingebunden ist.',
|
|
234
|
-
'status.normal': 'Normaler Betrieb',
|
|
235
|
-
'status.paused_peak': 'Spitzenzeit-Pause aktiv',
|
|
236
|
-
'status.peak_official': 'Spitzenzeit · nur offizielle',
|
|
237
|
-
'status.peak_all': 'Spitzenzeit · alle pausiert',
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/** Italian dictionary (keys mirror zh). */
|
|
241
|
-
export const it: Record<keyof typeof zh, string> = {
|
|
242
|
-
'badge.normal': 'Normale',
|
|
243
|
-
'badge.paused': 'In pausa (picco)',
|
|
244
|
-
'badge_paused_peak': 'Pausa di picco attiva',
|
|
245
|
-
'settings.enabled': 'Pausa automatica durante il picco',
|
|
246
|
-
'settings.enabled.desc': 'Metti automaticamente in pausa le sessioni attive durante le ore di picco',
|
|
247
|
-
'settings.offPeakAutoResume': 'Ripresa automatica fuori picco',
|
|
248
|
-
'settings.offPeakAutoResume.desc': 'Riprendi automaticamente le sessioni in pausa durante le ore fuori picco',
|
|
249
|
-
'settings.weekendMode': 'Modalità fine settimana',
|
|
250
|
-
'settings.weekendMode.desc': 'Rileva i fine settimana → nessuna pausa automatica nel weekend',
|
|
251
|
-
'settings.queueFallback': 'Coda di riserva con blocco',
|
|
252
|
-
'settings.queueFallback.desc': 'Ripiega sulla coda di attesa bloccata quando il gate di sessione personalizzato non è disponibile (fail-open)',
|
|
253
|
-
'settings.retryEnabled': 'Nuovo tentativo automatico',
|
|
254
|
-
'settings.retryEnabled.desc': 'Ripresa automatica dopo errore transitorio (disattivato per impostazione predefinita, conservativo)',
|
|
255
|
-
'settings.timezone': 'Fuso orario',
|
|
256
|
-
'settings.peakWindows': 'Finestre temporali di picco',
|
|
257
|
-
'settings.providerGuard': 'Guardia sorgente ufficiale',
|
|
258
|
-
'settings.providerGuard.desc': 'Durante il picco blocca solo le sorgenti ufficiali DeepSeek; i provider locali/terzi continuano a funzionare',
|
|
259
|
-
'settings.officialProviders': 'ID provider ufficiali aggiuntivi',
|
|
260
|
-
'settings.officialProviders.desc': 'Corrispondenza esatta, priorità massima (separati da virgola)',
|
|
261
|
-
'settings.officialBaseURLs': 'Host degli endpoint ufficiali',
|
|
262
|
-
'settings.officialBaseURLs.desc': 'Host dopo la normalizzazione della baseURL (separati da virgola)',
|
|
263
|
-
'settings.deferredMode': 'Modalità di intercettazione',
|
|
264
|
-
'settings.deferredMode.desc': 'Sospendi senza errore, oppure genera errore e accoda come differito',
|
|
265
|
-
'settings.deferredResume': 'Ripresa automatica dopo il picco',
|
|
266
|
-
'settings.deferredResume.desc': 'Se disattivato, le richieste/sessioni differite restano in attesa fino a un /resume manuale',
|
|
267
|
-
'settings.deferredResumeText': 'Messaggio di ripresa',
|
|
268
|
-
'settings.deferredResumeText.desc': 'Testo inviato quando una sessione in modalità errore riprende fuori picco',
|
|
269
|
-
'settings.guardSubagents': 'Includi sottoagenti',
|
|
270
|
-
'settings.guardSubagents.desc': 'Anche le richieste dei sottoagenti sono fatturate; protette per impostazione predefinita',
|
|
271
|
-
'settings.officialHint': 'Ordine di decisione: elenco id espliciti → endpoint baseURL → endpoint predefinito del catalogo → id integrato. Per un verdetto errato apri /session-guard/provider?provider=<id> e controlla matchedBy.',
|
|
272
|
-
'settings.unavailable': 'Impostazioni non disponibili: assicurati che dsh-session-guard sia integrato in questo profilo.',
|
|
273
|
-
'status.normal': 'Funzionamento normale',
|
|
274
|
-
'status.paused_peak': 'Pausa di picco attiva',
|
|
275
|
-
'status.peak_official': 'Picco · solo ufficiale',
|
|
276
|
-
'status.peak_all': 'Picco · tutto in pausa',
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/** Russian dictionary (keys mirror zh). */
|
|
280
|
-
export const ru: Record<keyof typeof zh, string> = {
|
|
281
|
-
'badge.normal': 'Норма',
|
|
282
|
-
'badge.paused': 'Приостановлено (пик)',
|
|
283
|
-
'badge_paused_peak': 'Пиковая пауза активна',
|
|
284
|
-
'settings.enabled': 'Автопауза в час пик',
|
|
285
|
-
'settings.enabled.desc': 'Автоматически приостанавливать активные сессии в часы пиковой нагрузки',
|
|
286
|
-
'settings.offPeakAutoResume': 'Автопродолжение вне пика',
|
|
287
|
-
'settings.offPeakAutoResume.desc': 'Автоматически возобновлять приостановленные сессии в непиковые часы',
|
|
288
|
-
'settings.weekendMode': 'Режим выходных',
|
|
289
|
-
'settings.weekendMode.desc': 'Определять выходные → не приостанавливать автоматически в выходные',
|
|
290
|
-
'settings.queueFallback': 'Резервная очередь блокировки',
|
|
291
|
-
'settings.queueFallback.desc': 'Откат к очереди блокировки, когда собственный гейт сессий недоступен (fail-open)',
|
|
292
|
-
'settings.retryEnabled': 'Автоповтор',
|
|
293
|
-
'settings.retryEnabled.desc': 'Автоматическое продолжение после временного сбоя (по умолчанию выключено, консервативно)',
|
|
294
|
-
'settings.timezone': 'Часовой пояс',
|
|
295
|
-
'settings.peakWindows': 'Пиковые временные окна',
|
|
296
|
-
'settings.providerGuard': 'Проверка официального источника',
|
|
297
|
-
'settings.providerGuard.desc': 'В час пик блокируются только официальные источники DeepSeek; локальные/сторонние провайдеры работают',
|
|
298
|
-
'settings.officialProviders': 'Дополнительные ID официальных провайдеров',
|
|
299
|
-
'settings.officialProviders.desc': 'Точное совпадение, наивысший приоритет (через запятую)',
|
|
300
|
-
'settings.officialBaseURLs': 'Хосты официальных эндпоинтов',
|
|
301
|
-
'settings.officialBaseURLs.desc': 'Хосты после нормализации baseURL (через запятую)',
|
|
302
|
-
'settings.deferredMode': 'Режим перехвата',
|
|
303
|
-
'settings.deferredMode.desc': 'Приостановить без ошибки, или выдать ошибку и поставить в отложенную очередь',
|
|
304
|
-
'settings.deferredResume': 'Автопродолжение после пика',
|
|
305
|
-
'settings.deferredResume.desc': 'Если выключено, отложенные запросы/сессии ожидают ручного /resume',
|
|
306
|
-
'settings.deferredResumeText': 'Сообщение возобновления',
|
|
307
|
-
'settings.deferredResumeText.desc': 'Текст, отправляемый при возобновлении сессии в режиме ошибки после пика',
|
|
308
|
-
'settings.guardSubagents': 'Включить субагентов',
|
|
309
|
-
'settings.guardSubagents.desc': 'Запросы субагентов также тарифицируются; по умолчанию защищаются',
|
|
310
|
-
'settings.officialHint': 'Порядок принятия решения: явный список id → эндпоинт baseURL → эндпоинт по умолчанию из каталога → встроенный id. При ошибочном вердикте откройте /session-guard/provider?provider=<id> и проверьте matchedBy.',
|
|
311
|
-
'settings.unavailable': 'Настройки недоступны: убедитесь, что dsh-session-guard подключён к этому профилю.',
|
|
312
|
-
'status.normal': 'Штатная работа',
|
|
313
|
-
'status.paused_peak': 'Пиковая пауза активна',
|
|
314
|
-
'status.peak_official': 'Пик · только официальные',
|
|
315
|
-
'status.peak_all': 'Пик · все приостановлены',
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
/** Spanish dictionary (keys mirror zh). */
|
|
319
|
-
export const es: Record<keyof typeof zh, string> = {
|
|
320
|
-
'badge.normal': 'Normal',
|
|
321
|
-
'badge.paused': 'Pausado (pico)',
|
|
322
|
-
'badge_paused_peak': 'Pausa por pico activa',
|
|
323
|
-
'settings.enabled': 'Pausa automática en hora pico',
|
|
324
|
-
'settings.enabled.desc': 'Pausar automáticamente las sesiones activas durante las horas pico',
|
|
325
|
-
'settings.offPeakAutoResume': 'Reanudación automática fuera de pico',
|
|
326
|
-
'settings.offPeakAutoResume.desc': 'Reanudar automáticamente las sesiones pausadas durante las horas valle',
|
|
327
|
-
'settings.weekendMode': 'Modo fin de semana',
|
|
328
|
-
'settings.weekendMode.desc': 'Detectar fines de semana → sin pausa automática el fin de semana',
|
|
329
|
-
'settings.queueFallback': 'Cola de bloqueo de reserva',
|
|
330
|
-
'settings.queueFallback.desc': 'Alternativa a la cola de bloqueo cuando la puerta de sesión personalizada no está disponible (fail-open)',
|
|
331
|
-
'settings.retryEnabled': 'Reintento automático',
|
|
332
|
-
'settings.retryEnabled.desc': 'Reanudación automática tras fallo transitorio (desactivado por defecto, conservador)',
|
|
333
|
-
'settings.timezone': 'Zona horaria',
|
|
334
|
-
'settings.peakWindows': 'Ventanas horarias de pico',
|
|
335
|
-
'settings.providerGuard': 'Protección de fuente oficial',
|
|
336
|
-
'settings.providerGuard.desc': 'En hora pico solo se bloquean las fuentes oficiales de DeepSeek; los proveedores locales/terceros siguen funcionando',
|
|
337
|
-
'settings.officialProviders': 'IDs de proveedor oficiales adicionales',
|
|
338
|
-
'settings.officialProviders.desc': 'Coincidencia exacta, prioridad máxima (separados por comas)',
|
|
339
|
-
'settings.officialBaseURLs': 'Hosts de endpoints oficiales',
|
|
340
|
-
'settings.officialBaseURLs.desc': 'Hosts tras normalización de la baseURL (separados por comas)',
|
|
341
|
-
'settings.deferredMode': 'Modo de intercepción',
|
|
342
|
-
'settings.deferredMode.desc': 'Suspender sin error, o generar error y encolar como diferido',
|
|
343
|
-
'settings.deferredResume': 'Reanudación automática tras el pico',
|
|
344
|
-
'settings.deferredResume.desc': 'Si se desactiva, las solicitudes/sesiones diferidas quedan en espera hasta un /resume manual',
|
|
345
|
-
'settings.deferredResumeText': 'Mensaje de reanudación',
|
|
346
|
-
'settings.deferredResumeText.desc': 'Texto enviado cuando una sesión en modo error se reanuda fuera de pico',
|
|
347
|
-
'settings.guardSubagents': 'Incluir subagentes',
|
|
348
|
-
'settings.guardSubagents.desc': 'Las solicitudes de subagentes también se facturan; protegidas por defecto',
|
|
349
|
-
'settings.officialHint': 'Orden de decisión: lista explícita de ids → endpoint de baseURL → endpoint predeterminado del catálogo → id integrado. Para un veredicto erróneo abra /session-guard/provider?provider=<id> y consulte matchedBy.',
|
|
350
|
-
'settings.unavailable': 'Configuración no disponible: asegúrese de que dsh-session-guard esté integrado en este perfil.',
|
|
351
|
-
'status.normal': 'Funcionamiento normal',
|
|
352
|
-
'status.paused_peak': 'Pausa por pico activa',
|
|
353
|
-
'status.peak_official': 'Pico · solo oficial',
|
|
354
|
-
'status.peak_all': 'Pico · todo pausado',
|
|
355
|
-
}
|
|
356
|
-
|
|
357
162
|
/** Dictionary key union. */
|
|
358
163
|
export type SessionGuardKey = keyof typeof zh
|
|
359
|
-
|
|
360
|
-
/** Combined dictionary map — all 9 languages, for one-call registration. */
|
|
361
|
-
export const dictionaries: Record<string, Record<string, string>> = { zh, en, ja, ko, fr, de, it, ru, es }
|
|
@@ -1,60 +1,60 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-session-guard — 暂停/继续按钮文案与状态投影(纯函数,零依赖,可单测)。
|
|
3
|
-
*
|
|
4
|
-
* 交互(v0.2.0 修订):
|
|
5
|
-
* - 未暂停 → 「暂停会话」(**可点**:点击 = 在下一次 step 边界手动暂停该会话);
|
|
6
|
-
* - 已暂停(step 门已拉 / 已请求但未到边界)→ 「继续会话」(点击 = 放行并本峰不再拦)。
|
|
7
|
-
* 按钮不置灰:step 暂停虽然会自动发生,但按钮同时是**手动暂停**入口。
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
/** 按钮状态。 */
|
|
11
|
-
export interface PauseButtonState {
|
|
12
|
-
/** 是否处于暂停态(step 门已挂起,或已请求暂停但还没到边界) */
|
|
13
|
-
paused: boolean
|
|
14
|
-
/** 挂起起始时间戳(毫秒);未知为 null */
|
|
15
|
-
heldSince?: number | null
|
|
16
|
-
/** 暂停是否来自用户手动请求(而非高峰自动拉门) */
|
|
17
|
-
manual?: boolean
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/** 按钮文案。 */
|
|
21
|
-
export function pauseButtonLabel(state: PauseButtonState): string {
|
|
22
|
-
return state.paused === true ? '继续会话' : '暂停会话'
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** 悬浮说明:写清当前状态与点击后的语义。 */
|
|
26
|
-
export function pauseButtonTitle(state: PauseButtonState): string {
|
|
27
|
-
if (state.paused !== true) {
|
|
28
|
-
return '暂停会话:在下一次 step 的模型请求前暂停(高峰 + 官方源时会自动暂停)'
|
|
29
|
-
}
|
|
30
|
-
const since = typeof state.heldSince === 'number' && Number.isFinite(state.heldSince) ? new Date(state.heldSince) : null
|
|
31
|
-
const at = since === null ? null : `${String(since.getHours()).padStart(2, '0')}:${String(since.getMinutes()).padStart(2, '0')}`
|
|
32
|
-
const why = state.manual === true ? '手动暂停' : '高峰自动暂停'
|
|
33
|
-
const base = at === null ? `已暂停(${why})` : `已暂停(${why},自 ${at})`
|
|
34
|
-
return `${base} · 点击继续:放行当前 step,且本高峰内不再拦该会话`
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/** 把 `/session-guard/state` 的响应体投影为按钮状态(形状异常 → 未暂停,fail-open)。 */
|
|
38
|
-
export function readPauseState(body: unknown): PauseButtonState {
|
|
39
|
-
const b = (body ?? {}) as {
|
|
40
|
-
ok?: unknown
|
|
41
|
-
paused?: { step?: unknown; manual?: unknown }
|
|
42
|
-
stepGate?: { since?: unknown; manual?: unknown }
|
|
43
|
-
state?: { pausedStep?: unknown; stepHeldSince?: unknown; stepManual?: unknown }
|
|
44
|
-
}
|
|
45
|
-
if (b.ok !== true) return { paused: false, heldSince: null, manual: false }
|
|
46
|
-
const manual = b.paused?.manual === true || b.stepGate?.manual === true || b.state?.stepManual === true
|
|
47
|
-
const paused = b.paused?.step === true || b.state?.pausedStep === true || manual
|
|
48
|
-
const raw = b.stepGate?.since ?? b.state?.stepHeldSince ?? null
|
|
49
|
-
const heldSince = typeof raw === 'number' && Number.isFinite(raw) ? raw : null
|
|
50
|
-
return { paused, heldSince, manual }
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
/** 把 SSE `/session-guard/events` 的 `state` 快照投影为按钮状态。 */
|
|
54
|
-
export function readStepSnapshot(snapshot: unknown): PauseButtonState {
|
|
55
|
-
const s = (snapshot ?? {}) as { held?: unknown; since?: unknown; manual?: unknown }
|
|
56
|
-
const manual = s.manual === true
|
|
57
|
-
const paused = s.held === true || manual
|
|
58
|
-
const heldSince = typeof s.since === 'number' && Number.isFinite(s.since) ? s.since : null
|
|
59
|
-
return { paused, heldSince, manual }
|
|
60
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-session-guard — 暂停/继续按钮文案与状态投影(纯函数,零依赖,可单测)。
|
|
3
|
+
*
|
|
4
|
+
* 交互(v0.2.0 修订):
|
|
5
|
+
* - 未暂停 → 「暂停会话」(**可点**:点击 = 在下一次 step 边界手动暂停该会话);
|
|
6
|
+
* - 已暂停(step 门已拉 / 已请求但未到边界)→ 「继续会话」(点击 = 放行并本峰不再拦)。
|
|
7
|
+
* 按钮不置灰:step 暂停虽然会自动发生,但按钮同时是**手动暂停**入口。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** 按钮状态。 */
|
|
11
|
+
export interface PauseButtonState {
|
|
12
|
+
/** 是否处于暂停态(step 门已挂起,或已请求暂停但还没到边界) */
|
|
13
|
+
paused: boolean
|
|
14
|
+
/** 挂起起始时间戳(毫秒);未知为 null */
|
|
15
|
+
heldSince?: number | null
|
|
16
|
+
/** 暂停是否来自用户手动请求(而非高峰自动拉门) */
|
|
17
|
+
manual?: boolean
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 按钮文案。 */
|
|
21
|
+
export function pauseButtonLabel(state: PauseButtonState): string {
|
|
22
|
+
return state.paused === true ? '继续会话' : '暂停会话'
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** 悬浮说明:写清当前状态与点击后的语义。 */
|
|
26
|
+
export function pauseButtonTitle(state: PauseButtonState): string {
|
|
27
|
+
if (state.paused !== true) {
|
|
28
|
+
return '暂停会话:在下一次 step 的模型请求前暂停(高峰 + 官方源时会自动暂停)'
|
|
29
|
+
}
|
|
30
|
+
const since = typeof state.heldSince === 'number' && Number.isFinite(state.heldSince) ? new Date(state.heldSince) : null
|
|
31
|
+
const at = since === null ? null : `${String(since.getHours()).padStart(2, '0')}:${String(since.getMinutes()).padStart(2, '0')}`
|
|
32
|
+
const why = state.manual === true ? '手动暂停' : '高峰自动暂停'
|
|
33
|
+
const base = at === null ? `已暂停(${why})` : `已暂停(${why},自 ${at})`
|
|
34
|
+
return `${base} · 点击继续:放行当前 step,且本高峰内不再拦该会话`
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 把 `/session-guard/state` 的响应体投影为按钮状态(形状异常 → 未暂停,fail-open)。 */
|
|
38
|
+
export function readPauseState(body: unknown): PauseButtonState {
|
|
39
|
+
const b = (body ?? {}) as {
|
|
40
|
+
ok?: unknown
|
|
41
|
+
paused?: { step?: unknown; manual?: unknown }
|
|
42
|
+
stepGate?: { since?: unknown; manual?: unknown }
|
|
43
|
+
state?: { pausedStep?: unknown; stepHeldSince?: unknown; stepManual?: unknown }
|
|
44
|
+
}
|
|
45
|
+
if (b.ok !== true) return { paused: false, heldSince: null, manual: false }
|
|
46
|
+
const manual = b.paused?.manual === true || b.stepGate?.manual === true || b.state?.stepManual === true
|
|
47
|
+
const paused = b.paused?.step === true || b.state?.pausedStep === true || manual
|
|
48
|
+
const raw = b.stepGate?.since ?? b.state?.stepHeldSince ?? null
|
|
49
|
+
const heldSince = typeof raw === 'number' && Number.isFinite(raw) ? raw : null
|
|
50
|
+
return { paused, heldSince, manual }
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** 把 SSE `/session-guard/events` 的 `state` 快照投影为按钮状态。 */
|
|
54
|
+
export function readStepSnapshot(snapshot: unknown): PauseButtonState {
|
|
55
|
+
const s = (snapshot ?? {}) as { held?: unknown; since?: unknown; manual?: unknown }
|
|
56
|
+
const manual = s.manual === true
|
|
57
|
+
const paused = s.held === true || manual
|
|
58
|
+
const heldSince = typeof s.since === 'number' && Number.isFinite(s.since) ? s.since : null
|
|
59
|
+
return { paused, heldSince, manual }
|
|
60
|
+
}
|
|
@@ -1,129 +1,129 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* dsh-session-guard — 「暂停会话 / 继续会话」按钮(`conversation.input.right`,order 20)。
|
|
3
|
-
*
|
|
4
|
-
* 交互(v0.2.0 修订):
|
|
5
|
-
* - 未暂停 → 「暂停会话」,**可点**:点击 POST `{action:'stepPause'}`,在**下一次 step 边界**暂停
|
|
6
|
-
* (不打断当前 step;step 1 也拦,不受峰谷 / provider 限制);
|
|
7
|
-
* - 已暂停(高峰自动拉门 **或** 手动请求已登记)→ 「继续会话」,点击 POST `{action:'stepResume'}`;
|
|
8
|
-
* - 状态更新双通道:① SSE `/session-guard/events?session=<id>` 即时推送;② 10s 轮询兜底
|
|
9
|
-
* (SSE 不可用 / 断线时仍能收敛)。全部 fail-open,绝不抛错。
|
|
10
|
-
*
|
|
11
|
-
* 与 input-traffic 的「❄ 冻结追加 / 恢复追加」并列(order 30 在其右侧),互不取代:
|
|
12
|
-
* 本按钮控 step 门,冻结按钮控回合级冻结 + 队列摘除。
|
|
13
|
-
*/
|
|
14
|
-
import { useEffect, useState } from 'react'
|
|
15
|
-
import {
|
|
16
|
-
pauseButtonLabel,
|
|
17
|
-
pauseButtonTitle,
|
|
18
|
-
readPauseState,
|
|
19
|
-
readStepSnapshot,
|
|
20
|
-
type PauseButtonState,
|
|
21
|
-
} from './pause-button-text'
|
|
22
|
-
import { injectClientCss } from './styles'
|
|
23
|
-
|
|
24
|
-
/** slot 运行时注入的会话级 props。 */
|
|
25
|
-
export interface PauseButtonProps {
|
|
26
|
-
sessionId?: string
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** SSE 不可用时的兜底轮询间隔(正常路径由事件驱动,几乎不触发)。 */
|
|
30
|
-
const POLL_MS = 10_000
|
|
31
|
-
|
|
32
|
-
const IDLE: PauseButtonState = { paused: false, heldSince: null, manual: false }
|
|
33
|
-
|
|
34
|
-
/** 暂停 / 继续会话按钮。 */
|
|
35
|
-
export function PauseButton({ sessionId }: PauseButtonProps) {
|
|
36
|
-
const [state, setState] = useState<PauseButtonState>(IDLE)
|
|
37
|
-
const [busy, setBusy] = useState(false)
|
|
38
|
-
|
|
39
|
-
injectClientCss()
|
|
40
|
-
|
|
41
|
-
useEffect(() => {
|
|
42
|
-
if (sessionId === undefined || sessionId === '') return
|
|
43
|
-
let cancelled = false
|
|
44
|
-
|
|
45
|
-
const poll = async (): Promise<void> => {
|
|
46
|
-
try {
|
|
47
|
-
const res = await fetch(`/session-guard/state?session=${encodeURIComponent(sessionId)}`)
|
|
48
|
-
const body = await res.json().catch(() => null)
|
|
49
|
-
if (cancelled) return
|
|
50
|
-
setState(readPauseState(body))
|
|
51
|
-
} catch {
|
|
52
|
-
// fail-open:路由不存在 / 网络失败 → 保持上一次状态。
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
void poll()
|
|
56
|
-
const timer = setInterval(() => {
|
|
57
|
-
void poll()
|
|
58
|
-
}, POLL_MS)
|
|
59
|
-
|
|
60
|
-
let source: EventSource | undefined
|
|
61
|
-
try {
|
|
62
|
-
source = new EventSource(`/session-guard/events?session=${encodeURIComponent(sessionId)}`)
|
|
63
|
-
source.onmessage = (ev: MessageEvent<string>) => {
|
|
64
|
-
try {
|
|
65
|
-
const msg = JSON.parse(ev.data) as { type?: string; state?: unknown }
|
|
66
|
-
if (cancelled || msg?.type !== 'step') return
|
|
67
|
-
setState(readStepSnapshot(msg.state))
|
|
68
|
-
} catch {
|
|
69
|
-
/* 非法负载:忽略,等下一次推送或轮询 */
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
// onerror:EventSource 会自行重连;轮询同时兜底。
|
|
73
|
-
source.onerror = () => undefined
|
|
74
|
-
} catch {
|
|
75
|
-
// 浏览器不支持 EventSource → 只靠轮询。
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
return () => {
|
|
79
|
-
cancelled = true
|
|
80
|
-
clearInterval(timer)
|
|
81
|
-
try {
|
|
82
|
-
source?.close()
|
|
83
|
-
} catch {
|
|
84
|
-
/* ignore */
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
}, [sessionId])
|
|
88
|
-
|
|
89
|
-
if (sessionId === undefined || sessionId === '') return null
|
|
90
|
-
|
|
91
|
-
const paused = state.paused === true
|
|
92
|
-
const onClick = async (): Promise<void> => {
|
|
93
|
-
if (busy) return
|
|
94
|
-
setBusy(true)
|
|
95
|
-
try {
|
|
96
|
-
const action = paused ? 'stepResume' : 'stepPause'
|
|
97
|
-
const res = await fetch('/session-guard/rpc', {
|
|
98
|
-
method: 'POST',
|
|
99
|
-
headers: { 'content-type': 'application/json' },
|
|
100
|
-
body: JSON.stringify({ sessionId, action }),
|
|
101
|
-
})
|
|
102
|
-
const body = await res.json().catch(() => null)
|
|
103
|
-
if (body?.ok === true) {
|
|
104
|
-
// 立即乐观更新;SSE / 轮询随后校正。
|
|
105
|
-
setState(paused ? IDLE : { paused: true, heldSince: null, manual: true })
|
|
106
|
-
}
|
|
107
|
-
} catch {
|
|
108
|
-
// fail-open:下一轮轮询会纠正显示。
|
|
109
|
-
} finally {
|
|
110
|
-
setBusy(false)
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
return (
|
|
115
|
-
<button
|
|
116
|
-
type="button"
|
|
117
|
-
className="sg-pause"
|
|
118
|
-
disabled={busy}
|
|
119
|
-
aria-pressed={paused || undefined}
|
|
120
|
-
title={pauseButtonTitle(state)}
|
|
121
|
-
data-sg-step-paused={paused ? 'on' : 'off'}
|
|
122
|
-
onClick={() => {
|
|
123
|
-
void onClick()
|
|
124
|
-
}}
|
|
125
|
-
>
|
|
126
|
-
{pauseButtonLabel(state)}
|
|
127
|
-
</button>
|
|
128
|
-
)
|
|
129
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* dsh-session-guard — 「暂停会话 / 继续会话」按钮(`conversation.input.right`,order 20)。
|
|
3
|
+
*
|
|
4
|
+
* 交互(v0.2.0 修订):
|
|
5
|
+
* - 未暂停 → 「暂停会话」,**可点**:点击 POST `{action:'stepPause'}`,在**下一次 step 边界**暂停
|
|
6
|
+
* (不打断当前 step;step 1 也拦,不受峰谷 / provider 限制);
|
|
7
|
+
* - 已暂停(高峰自动拉门 **或** 手动请求已登记)→ 「继续会话」,点击 POST `{action:'stepResume'}`;
|
|
8
|
+
* - 状态更新双通道:① SSE `/session-guard/events?session=<id>` 即时推送;② 10s 轮询兜底
|
|
9
|
+
* (SSE 不可用 / 断线时仍能收敛)。全部 fail-open,绝不抛错。
|
|
10
|
+
*
|
|
11
|
+
* 与 input-traffic 的「❄ 冻结追加 / 恢复追加」并列(order 30 在其右侧),互不取代:
|
|
12
|
+
* 本按钮控 step 门,冻结按钮控回合级冻结 + 队列摘除。
|
|
13
|
+
*/
|
|
14
|
+
import { useEffect, useState } from 'react'
|
|
15
|
+
import {
|
|
16
|
+
pauseButtonLabel,
|
|
17
|
+
pauseButtonTitle,
|
|
18
|
+
readPauseState,
|
|
19
|
+
readStepSnapshot,
|
|
20
|
+
type PauseButtonState,
|
|
21
|
+
} from './pause-button-text'
|
|
22
|
+
import { injectClientCss } from './styles'
|
|
23
|
+
|
|
24
|
+
/** slot 运行时注入的会话级 props。 */
|
|
25
|
+
export interface PauseButtonProps {
|
|
26
|
+
sessionId?: string
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** SSE 不可用时的兜底轮询间隔(正常路径由事件驱动,几乎不触发)。 */
|
|
30
|
+
const POLL_MS = 10_000
|
|
31
|
+
|
|
32
|
+
const IDLE: PauseButtonState = { paused: false, heldSince: null, manual: false }
|
|
33
|
+
|
|
34
|
+
/** 暂停 / 继续会话按钮。 */
|
|
35
|
+
export function PauseButton({ sessionId }: PauseButtonProps) {
|
|
36
|
+
const [state, setState] = useState<PauseButtonState>(IDLE)
|
|
37
|
+
const [busy, setBusy] = useState(false)
|
|
38
|
+
|
|
39
|
+
injectClientCss()
|
|
40
|
+
|
|
41
|
+
useEffect(() => {
|
|
42
|
+
if (sessionId === undefined || sessionId === '') return
|
|
43
|
+
let cancelled = false
|
|
44
|
+
|
|
45
|
+
const poll = async (): Promise<void> => {
|
|
46
|
+
try {
|
|
47
|
+
const res = await fetch(`/session-guard/state?session=${encodeURIComponent(sessionId)}`)
|
|
48
|
+
const body = await res.json().catch(() => null)
|
|
49
|
+
if (cancelled) return
|
|
50
|
+
setState(readPauseState(body))
|
|
51
|
+
} catch {
|
|
52
|
+
// fail-open:路由不存在 / 网络失败 → 保持上一次状态。
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
void poll()
|
|
56
|
+
const timer = setInterval(() => {
|
|
57
|
+
void poll()
|
|
58
|
+
}, POLL_MS)
|
|
59
|
+
|
|
60
|
+
let source: EventSource | undefined
|
|
61
|
+
try {
|
|
62
|
+
source = new EventSource(`/session-guard/events?session=${encodeURIComponent(sessionId)}`)
|
|
63
|
+
source.onmessage = (ev: MessageEvent<string>) => {
|
|
64
|
+
try {
|
|
65
|
+
const msg = JSON.parse(ev.data) as { type?: string; state?: unknown }
|
|
66
|
+
if (cancelled || msg?.type !== 'step') return
|
|
67
|
+
setState(readStepSnapshot(msg.state))
|
|
68
|
+
} catch {
|
|
69
|
+
/* 非法负载:忽略,等下一次推送或轮询 */
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
// onerror:EventSource 会自行重连;轮询同时兜底。
|
|
73
|
+
source.onerror = () => undefined
|
|
74
|
+
} catch {
|
|
75
|
+
// 浏览器不支持 EventSource → 只靠轮询。
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return () => {
|
|
79
|
+
cancelled = true
|
|
80
|
+
clearInterval(timer)
|
|
81
|
+
try {
|
|
82
|
+
source?.close()
|
|
83
|
+
} catch {
|
|
84
|
+
/* ignore */
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}, [sessionId])
|
|
88
|
+
|
|
89
|
+
if (sessionId === undefined || sessionId === '') return null
|
|
90
|
+
|
|
91
|
+
const paused = state.paused === true
|
|
92
|
+
const onClick = async (): Promise<void> => {
|
|
93
|
+
if (busy) return
|
|
94
|
+
setBusy(true)
|
|
95
|
+
try {
|
|
96
|
+
const action = paused ? 'stepResume' : 'stepPause'
|
|
97
|
+
const res = await fetch('/session-guard/rpc', {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'content-type': 'application/json' },
|
|
100
|
+
body: JSON.stringify({ sessionId, action }),
|
|
101
|
+
})
|
|
102
|
+
const body = await res.json().catch(() => null)
|
|
103
|
+
if (body?.ok === true) {
|
|
104
|
+
// 立即乐观更新;SSE / 轮询随后校正。
|
|
105
|
+
setState(paused ? IDLE : { paused: true, heldSince: null, manual: true })
|
|
106
|
+
}
|
|
107
|
+
} catch {
|
|
108
|
+
// fail-open:下一轮轮询会纠正显示。
|
|
109
|
+
} finally {
|
|
110
|
+
setBusy(false)
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return (
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
className="sg-pause"
|
|
118
|
+
disabled={busy}
|
|
119
|
+
aria-pressed={paused || undefined}
|
|
120
|
+
title={pauseButtonTitle(state)}
|
|
121
|
+
data-sg-step-paused={paused ? 'on' : 'off'}
|
|
122
|
+
onClick={() => {
|
|
123
|
+
void onClick()
|
|
124
|
+
}}
|
|
125
|
+
>
|
|
126
|
+
{pauseButtonLabel(state)}
|
|
127
|
+
</button>
|
|
128
|
+
)
|
|
129
|
+
}
|
package/src/client/scope-face.ts
CHANGED
|
@@ -1,20 +1,20 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 0.1.7-rc.2 停止从 `@deepseek-ai/dsh-client-ui-settings/client` 导出
|
|
3
|
-
* `SettingsScope`;卡片消费的结构化面移到此处,字段逐一镜像
|
|
4
|
-
* `configForms.get(entryId)` 返回的 ConfigForm。
|
|
5
|
-
*/
|
|
6
|
-
export interface SettingsScope<T> {
|
|
7
|
-
getSnapshot(): {
|
|
8
|
-
status: 'loading' | 'ready' | 'unavailable'
|
|
9
|
-
value: T | undefined
|
|
10
|
-
revision: number | undefined
|
|
11
|
-
writable: boolean
|
|
12
|
-
base: unknown
|
|
13
|
-
user: unknown
|
|
14
|
-
mode: 'host' | 'memory'
|
|
15
|
-
}
|
|
16
|
-
subscribe(listener: () => void): () => void
|
|
17
|
-
set(field: string, value: unknown): Promise<boolean>
|
|
18
|
-
unset(field: string): Promise<boolean>
|
|
19
|
-
mutate?(ops: readonly { path: readonly string[]; op: string; value?: unknown }[]): Promise<boolean>
|
|
20
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* 0.1.7-rc.2 停止从 `@deepseek-ai/dsh-client-ui-settings/client` 导出
|
|
3
|
+
* `SettingsScope`;卡片消费的结构化面移到此处,字段逐一镜像
|
|
4
|
+
* `configForms.get(entryId)` 返回的 ConfigForm。
|
|
5
|
+
*/
|
|
6
|
+
export interface SettingsScope<T> {
|
|
7
|
+
getSnapshot(): {
|
|
8
|
+
status: 'loading' | 'ready' | 'unavailable'
|
|
9
|
+
value: T | undefined
|
|
10
|
+
revision: number | undefined
|
|
11
|
+
writable: boolean
|
|
12
|
+
base: unknown
|
|
13
|
+
user: unknown
|
|
14
|
+
mode: 'host' | 'memory'
|
|
15
|
+
}
|
|
16
|
+
subscribe(listener: () => void): () => void
|
|
17
|
+
set(field: string, value: unknown): Promise<boolean>
|
|
18
|
+
unset(field: string): Promise<boolean>
|
|
19
|
+
mutate?(ops: readonly { path: readonly string[]; op: string; value?: unknown }[]): Promise<boolean>
|
|
20
|
+
}
|