dsh-session-guard 4.0.0 → 4.0.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/CHANGELOG.ja.md +5 -0
- package/CHANGELOG.ko.md +5 -0
- package/CHANGELOG.md +5 -0
- package/INSTALL.ja.md +5 -0
- package/INSTALL.ko.md +5 -0
- package/INSTALL.md +5 -0
- package/INSTALL.zh.md +5 -0
- 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 +16 -1
- package/README.ru.md +374 -0
- package/lib/client.js +5 -0
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +21 -0
package/README.es.md
ADDED
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<strong>Compuerta automática de sesión en horas punta: modo fin de semana + pausa automática en punta + veredicto bidimensional de fuentes oficiales + congelación a nivel de sesión + reintento automático del backend</strong>
|
|
3
|
+
</p>
|
|
4
|
+
<img width="832" height="182" alt="00c4b89a-b026-4bf1-a358-a068e80d2da7" src="https://github.com/user-attachments/assets/31a8836f-0fe0-4043-948a-f0865bb1b3bb" />
|
|
5
|
+
|
|
6
|
+
<p align="center">
|
|
7
|
+
<a href="README.en.md">English</a> · <a href="README.md">中文</a> · <a href="README.ja.md">日本語</a> · <a href="README.ko.md">한국어</a> · <a href="README.fr.md">Français</a> · <a href="README.de.md">Deutsch</a> · <a href="README.it.md">Italiano</a> · <a href="README.ru.md">Русский</a> · <strong>Español</strong>
|
|
8
|
+
</p>
|
|
9
|
+
<p align="center">
|
|
10
|
+
<a href="LICENSE"><img alt="MIT License" src="https://img.shields.io/badge/license-MIT-263146?style=flat-square"></a>
|
|
11
|
+
<img src="https://camo.githubusercontent.com/2c11fb2e0e14bb9985c5acbe61123a7441c5ee63aa27fa6e04e2a707ebfd6022/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f6473682d2d706c7567696e2d72656164792d3437384342463f6c6f676f3d646565707365656b266c6f676f436f6c6f723d7768697465" alt="dsh-plugin" data-canonical-src="https://img.shields.io/badge/dsh--plugin-ready-478CBF?logo=deepseek&logoColor=white" style="max-width: 100%;">
|
|
12
|
+
<img alt="Public beta" src="https://img.shields.io/badge/status-public%20beta-7da1de?style=flat-square">
|
|
13
|
+
</p>
|
|
14
|
+
|
|
15
|
+
# dsh-session-guard
|
|
16
|
+
|
|
17
|
+
- [English README](./README.en.md)
|
|
18
|
+
- [中文 README](./README.md)
|
|
19
|
+
- [日本語 README](./README.ja.md)
|
|
20
|
+
- [한국어 README](./README.ko.md)
|
|
21
|
+
- [README en français](./README.fr.md)
|
|
22
|
+
- [README auf Deutsch](./README.de.md)
|
|
23
|
+
- [README in italiano](./README.it.md)
|
|
24
|
+
- [README en ruso](./README.ru.md)
|
|
25
|
+
- [README en español](./README.es.md)
|
|
26
|
+
- [Installation guide](./INSTALL.md)
|
|
27
|
+
- [中文安装指南](./INSTALL.zh.md)
|
|
28
|
+
- [日本語インストールガイド](./INSTALL.ja.md)
|
|
29
|
+
- [한국어 설치 안내](./INSTALL.ko.md)
|
|
30
|
+
- [Guide d'installation en français](./INSTALL.fr.md)
|
|
31
|
+
- [Installationsanleitung auf Deutsch](./INSTALL.de.md)
|
|
32
|
+
- [Guida all'installazione in italiano](./INSTALL.it.md)
|
|
33
|
+
- [Руководство по установке на русском](./INSTALL.ru.md)
|
|
34
|
+
- [Guía de instalación en español](./INSTALL.es.md)
|
|
35
|
+
- [Changelog](./CHANGELOG.md)
|
|
36
|
+
- [日本語 changelog](./CHANGELOG.ja.md)
|
|
37
|
+
- [한국어 changelog](./CHANGELOG.ko.md)
|
|
38
|
+
- [Changelog en français](./CHANGELOG.fr.md)
|
|
39
|
+
- [Changelog auf Deutsch](./CHANGELOG.de.md)
|
|
40
|
+
- [Changelog in italiano](./CHANGELOG.it.md)
|
|
41
|
+
- [Changelog en ruso](./CHANGELOG.ru.md)
|
|
42
|
+
- [Changelog en español](./CHANGELOG.es.md)
|
|
43
|
+
|
|
44
|
+
> **Nota de compatibilidad:** la v0.1.1 ya incluye diccionarios de japonés (`ja`) y coreano (`ko`), pero las versiones oficiales actuales de DSH solo exponen `zh` y `en` a través de `LocaleRuntime`. En un DSH sin modificar, seleccionar `ja` o `ko` falla con `locale "<id>" is not registered`. Habrá que esperar a que el DSH oficial añada los identificadores de configuración regional correspondientes. Los usuarios avanzados pueden mantener un fork de DSH para ampliarlo.
|
|
45
|
+
|
|
46
|
+
> **▼ Compatibilidad de versiones de DSH**
|
|
47
|
+
>
|
|
48
|
+
> | Versión de DSH | Carga | Registro de ajustes | Eventos de sesión / compuerta | Mitad cliente |
|
|
49
|
+
> | --- | --- | --- | --- | --- |
|
|
50
|
+
> | 0.1.0-rc.7 ~ 0.1.1-rc.x | ➖ fuera de esta línea (versiones históricas anteriores a `legacy/0.1.2`) | `ctx.settings.register(ns, schema, { base })` | ✅ misma forma | ✅ sin importar valores de plataforma |
|
|
51
|
+
> | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ➖ fuera de esta línea → usar `legacy/0.1.2` (npm `@dsh-0.1.2`) | `register` se mantiene (se añade `installSection`) | ✅ misma forma | ✅ sin importar valores de plataforma |
|
|
52
|
+
> | **0.2.0-rc.1+** | ✅ (**esta línea**, dist-tag `dsh-0.2.0`, npm `4.0.0`) | declarativo: los campos `.volatile()` del Config los proyecta el host como formularios, `register` eliminado | ✅ eventos leídos por la doble vía `snapshotEvents()` | ✅ tarjeta de ajustes del cliente migrada a `configForms` |
|
|
53
|
+
> | 0.1.7-rc.1+ | ✅ (rama `compat/0.1.7`, dist-tag `dsh-0.1.7`) | declarativo: los campos `.volatile()` del Config los proyecta el host como formularios, `register` eliminado | ✅ eventos leídos por la doble vía `snapshotEvents()` | ✅ tarjeta de ajustes del cliente migrada a `configForms` |
|
|
54
|
+
> | 0.1.5-rc.2 | ✅ (rama `compat/0.1.5`, dist-tag `dsh-0.1.5`) | `register` sigue presente (espacios de nombres como cadenas) | ✅ eventos leídos por la doble vía `snapshotEvents()` | ✅ |
|
|
55
|
+
>
|
|
56
|
+
> **Identidad de esta línea**: rama `compat/0.2.0`, número de versión npm **`4.0.0`** (semver), dist-tag **`dsh-0.2.0`**.
|
|
57
|
+
> Los `engines.dsh` de `package.json` y `dsh.plugin.json` y los cuatro pares `@deepseek-ai/dsh-client-*`
|
|
58
|
+
> se unifican en `>=0.2.0-rc.1 <0.2.1-0`; el límite inferior del par `@deepseek-ai/cordis` se alinea con el `^4.0.4` de la línea del host.
|
|
59
|
+
> Históricamente el README usó «2.x / 3.x» como **apodos de línea**: era una costumbre narrativa, **no números de versión
|
|
60
|
+
> descargables del registro** — guíate siempre por las versiones npm `0.3.1` (línea 0.1.2), `3.0.0`/`3.0.1` (línea 0.1.5),
|
|
61
|
+
> `3.2.4` (línea 0.1.7) y `4.0.0` (línea 0.2.0).
|
|
62
|
+
>
|
|
63
|
+
> **Adaptación a 0.2.0 (rama compat/0.2.0, npm 4.0.0)**:
|
|
64
|
+
> la API de plugins que usa este complemento (manifest/settings/HMR/slot/sesión V4) es en 0.2.0-rc.1 totalmente compatible con 0.1.7;
|
|
65
|
+
> la adaptación es un simple relevo de metadatos (peer/engines/dist-tag/versión 4.0.0). Correcciones de ingeniería alineadas junto con la base:
|
|
66
|
+
> ① `src/client/family-section.tsx` entra en el control de versiones (antes existía solo en el árbol de trabajo local, un checkout limpio no podía compilar);
|
|
67
|
+
> ② la configuración de eslint elimina el `no-unused-vars` del núcleo, que marcaba erróneamente parámetros de firmas de tipos TS; la línea base de lint vuelve a estar toda en verde;
|
|
68
|
+
> ③ las compuertas estáticas de firma de las rutas de escritura v4 (`tests/source-kind.test.mjs`) entran en esta línea junto con la base;
|
|
69
|
+
> ④ el par `@deepseek-ai/cordis` se alinea con `^4.0.4` (los paquetes de UI del host 0.2.0-rc.1 declaran `~4.0.4`; el anterior `^4.0.1` no entraba en conflicto, pero su límite inferior era demasiado antiguo).
|
|
70
|
+
>
|
|
71
|
+
> **Dónde quedan las otras líneas**: para hosts DSH `0.1.2-rc.x` usa la rama **`legacy/0.1.2`** (dist-tag npm
|
|
72
|
+
> `dsh-0.1.2`, versión `0.3.1`). **`main` está congelado en `0.2.0-beta.1` y ya no es la rama de publicaciones de la línea 0.1.2.**
|
|
73
|
+
> Para hosts DSH `0.1.0-rc.7` ~ `0.1.1-rc.x` usa versiones históricas ≤ `0.1.2`.
|
|
74
|
+
> Definiciones de las fuentes de los campos:
|
|
75
|
+
> `mine-dsh-plugins/improve-dsh-plugins/DSH-PLUGIN-VERSION-DISTRIBUTION-STRATEGY.md` §2.2.
|
|
76
|
+
>
|
|
77
|
+
> **Adaptación a 0.1.5 (rama compat/0.1.5, npm 3.0.0)**:
|
|
78
|
+
> ① doble vía para `agent.followup` — 0.1.5 convierte el Inbox en una proyección de solo lectura del bucle del agente; si `followup`
|
|
79
|
+
> ya no existe, se recurre a `agent.send`, y si no existe ninguno de los dos se degrada a un warn sin lanzar errores (red de seguridad para la reanudación);
|
|
80
|
+
> ② los mensajes de reanudación añaden a `source` el campo `form: 'instructions'` (contrato ContextFormed de 0.1.5, las versiones anteriores lo ignoran);
|
|
81
|
+
> ③ `webServer.register` se envuelve en try/catch: un fallo de registro solo queda en el registro y no tumba la carga de plugins del host;
|
|
82
|
+
> ④ 0.1.5 elimina el acceso de matriz a `session.events`; la lectura de eventos pasa por `snapshotEvents()` como vía principal +
|
|
83
|
+
> respaldo en la matriz antigua (afecta solo a las dos rutas auxiliares `findToolOutcome` / `lastUserPrompt`, la comparación por tipo de evento no cambia).
|
|
84
|
+
> Verificado que el contrato `WebRoute` de 0.1.5 (exact/prefix + SSE) no cambia: el cliente
|
|
85
|
+
> `fetch('/session-guard/...')` no necesita el prefijo `/api`.
|
|
86
|
+
> La tabla de compatibilidad de abajo abarca dos generaciones de API (0.1.1 / 0.1.2); esta línea solo reclama la fila 0.1.5.
|
|
87
|
+
> `session/event`, `agent.cancel`, `goals.pause`,
|
|
88
|
+
> `agent.followup`, `commands.register`, `timer.interval`, `webServer.register`,
|
|
89
|
+
> `agent/request`, `llm.listConfigurableProviders`, `settings.register/get` tienen firmas
|
|
90
|
+
> idénticas entre `dsh-v0.1.1-rc.2` y `dsh-v0.1.2-rc.1` (esta línea está verificada hasta `dsh-v0.1.5-rc.2`);
|
|
91
|
+
> lo único que exige doble lectura es la forma del id de llamada que registra `tool/result` (prioridad de `content[].toolCallId`,
|
|
92
|
+
> respaldo en `source.callId`), extraída a `src/tool-call-id.js` con pruebas unitarias — en los registros de reproducción de ambas versiones pueden aparecer ambas formas.
|
|
93
|
+
> Desde 0.1.5 el acceso de matriz a `session.events` está eliminado; esta línea lee por `snapshotEvents()` (conservando el respaldo de la matriz antigua),
|
|
94
|
+
> afectando solo a las dos rutas auxiliares `findToolOutcome` / `lastUserPrompt`.
|
|
95
|
+
> El evento `model/selection` existe **solo desde 0.1.2**, sirve únicamente para acelerar el cambio de modelo y exige siempre detección de capacidad; la superficie de ajustes usa solo
|
|
96
|
+
> la intersección de `register` + `get` (nunca `installSection` / el `installSettingsSection` eliminado).
|
|
97
|
+
> Script centinela contra la deriva: `tools/check-api-drift.ps1` (esta línea verifica por defecto contra `dsh-v0.1.5-rc.2` la existencia de las interfaces necesarias).
|
|
98
|
+
|
|
99
|
+
> Pausa automáticamente las sesiones en ejecución durante las horas punta y las reanuda automáticamente fuera de punta y los fines de semana; junto con el botón de congelación de input-traffic consigue un bloqueo **a nivel de sesión**; el **reintento automático** del backend cede el paso durante la congelación/la compuerta. El núcleo se basa en una **compuerta de sesión propia** (`agent.cancel keepInbox + goals.pause + session/event frontera segura + reanudación por followup`) y ya no depende de dsh-task-control.
|
|
100
|
+
|
|
101
|
+
Plugin de cordis montado con el comando `dsh plugin` y un parche del bundle — sin modificar el código fuente de dsh y sin PR.
|
|
102
|
+
|
|
103
|
+
> 💡 **Por qué se recomienda**: DeepSeek aplica desde el 2026-08-17 una **facturación por horas punta y valle** — en las horas punta (hora de Pekín 9:00-12:00, 14:00-18:00) el precio unitario es el **doble** que en horas valle (incluidas las de mediodía, noche, fines de semana y festivos). Este complemento detiene automáticamente las sesiones en ejecución durante la punta y las reanuda automáticamente al salir de ella; las ejecuciones largas escalonadas pueden ahorrar hasta un **50 %**; la congelación manual (con el botón de input-traffic) permite un control aún más preciso, sesión a sesión.
|
|
104
|
+
|
|
105
|
+
## Resumen de funciones
|
|
106
|
+
|
|
107
|
+
- **Modo fin de semana**: identifica los fines de semana (con `Intl.DateTimeFormat` según la zona horaria configurada, evitando el clásico fallo de 8 horas en el límite UTC+8 del `getUTCDay()` desnudo para la hora de Pekín) → los fines de semana se ignoran punta y valle y se ejecuta con libertad.
|
|
108
|
+
- **Pausa automática en punta (global)**: al entrar en punta (y si no es fin de semana), pausa automáticamente todas las sesiones raíz `running`; al salir de punta las reanuda todas automáticamente — **interruptor global, sin gestión manual**.
|
|
109
|
+
- **Veredicto bidimensional de fuentes oficiales (providerGuard)**: en las horas punta solo se bloquea **si el destino de la solicitud es una fuente oficial de DeepSeek**; con proveedores locales/externos (como `local-35b`) la ejecución sigue con normalidad, sin que la afecte la compuerta de punta. Criterio de veredicto = lista explícita de id → endpoint de `baseURL` → endpoint por defecto del catalog → id integrado.
|
|
110
|
+
- **Red de seguridad a nivel de solicitud + cola diferida**: las sesiones iniciadas tras el inicio de la punta, o cambiadas a mitad de camino a una fuente oficial, las captura la guardia a nivel de solicitud `agent/request` (por defecto `hold`: la solicitud queda suspendida sin error y se libera automáticamente al salir de punta).
|
|
111
|
+
- **Congelación / reanudación a nivel de sesión**: puerto redundante `sessionGuard` + `POST /session-guard/rpc`, conectado sesión a sesión mediante el botón de congelación de input-traffic; también ofrece los comandos manuales `/pause /resume /cancel`.
|
|
112
|
+
- **Reintento automático del backend (D9)**: los fallos transitorios de turn/end (error/429/max-tokens) se reanudan automáticamente con retroceso adaptativo; los fallos permanentes detienen; **cede el paso durante congelación/compuerta**, nunca la rodea.
|
|
113
|
+
- **fail-open**: compuerta de sesión propia no disponible, session-guard no instalado, servicio de ajustes ausente — en todos los casos degradación silenciosa, nunca un fallo por causa de una dependencia.
|
|
114
|
+
|
|
115
|
+
## Vista previa de la interfaz
|
|
116
|
+
|
|
117
|
+
Captura de una ejecución real (Windows, dsh web) — modo fin de semana activo:
|
|
118
|
+
|
|
119
|
+
<figure>
|
|
120
|
+
<img style="max-width:100%" alt="Barra de control de estado del área de entrada: el botón «Fin de semana» activo aparece resaltado (con el modo fin de semana activo las sesiones corren libres, sin tener en cuenta punta y valle), junto a él el botón «Congelar sesión» (con input-traffic), el nivel de razonamiento DeepSeek-V4-Flash y los controles de envío, y abajo barras de estado con turnos/pasos, tiempo del LLM, aciertos de caché, etc." src="assets/高峰低峰周末提醒-周末状态.png" />
|
|
121
|
+
<figcaption>Modo fin de semana activo: la insignia de «fin de semana» del área de entrada aparece resaltada, junto a «Congelar sesión»; los fines de semana se ignoran punta y valle y las sesiones corren libres.</figcaption>
|
|
122
|
+
</figure>
|
|
123
|
+
|
|
124
|
+
## Instalación
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Hosts DSH 0.1.5-rc.x (esta línea, dist-tag dsh-0.1.5)
|
|
128
|
+
dsh plugin --profile web add dsh-session-guard@dsh-0.1.5
|
|
129
|
+
|
|
130
|
+
# o directamente desde la rama git
|
|
131
|
+
dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
|
|
132
|
+
|
|
133
|
+
# Los hosts DSH 0.1.2-rc.x deben usar la línea 0.1.2
|
|
134
|
+
dsh plugin --profile web add dsh-session-guard@dsh-0.1.2
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
`compat/0.1.5` es la línea dedicada a DSH `0.1.5-rc.x`, número de versión npm **`3.0.0`**; los hosts DSH `0.1.2-rc.x` deben usar la
|
|
138
|
+
rama **`legacy/0.1.2`** (dist-tag npm `dsh-0.1.2`, versión `0.3.1`). **`main` está congelado en `0.2.0-beta.1`,
|
|
139
|
+
ya no es la rama de publicaciones de la línea 0.1.2.**
|
|
140
|
+
|
|
141
|
+
> ⚠️ No confíes en el nombre de paquete desnudo `dsh-session-guard`: la etiqueta `latest` de npm no puede servir a la vez dos líneas de
|
|
142
|
+
> versión mutuamente excluyentes (sus `engines.dsh` son excluyentes según las reglas de semver para prerelease) — hay que indicar siempre el dist-tag de forma explícita.
|
|
143
|
+
|
|
144
|
+
Tras instalar, reinicia dsh web y actualiza la página.
|
|
145
|
+
|
|
146
|
+
## Ajustes (Ajustes → Plugins → session-guard, interruptores simples)
|
|
147
|
+
|
|
148
|
+
| Interruptor | Por defecto | Descripción |
|
|
149
|
+
|---|---|---|
|
|
150
|
+
| `enabled` | on | **Pausa/congelación automática de sesiones en punta**: pausa automáticamente las sesiones en ejecución durante las horas punta |
|
|
151
|
+
| `stepLevelPause` | on | **Compuerta a nivel de paso**: en punta cierra la compuerta **antes** de la solicitud al modelo del siguiente paso (antes y con más ahorro que la pausa a nivel de turno); off vuelve a la pausa a nivel de turno |
|
|
152
|
+
| `providerGuard` | on | **Veredicto bidimensional de fuentes oficiales**: en punta solo se bloquean las fuentes oficiales de DeepSeek, los proveedores locales/externos siguen con normalidad |
|
|
153
|
+
| `guardSubagents` | on | **Incluir solicitudes de subagentes**: las solicitudes de subagentes también se facturan, por defecto se bloquean igualmente |
|
|
154
|
+
| `offPeakAutoResume` | on | **Reanudación automática en horas valle**: las sesiones pausadas se reanudan automáticamente en horas valle; off no hay reanudación automática al salir de punta (hace falta acción manual) |
|
|
155
|
+
| `weekendMode` | on | **Modo fin de semana**: identifica los fines de semana → sin pausa automática los fines de semana (no hay punta, se ejecuta con libertad) |
|
|
156
|
+
| `deferredResume` | on | **Reanudación automática tras la punta**: off las solicitudes/sesiones diferidas no se reanudan solas, hace falta un `/resume` manual |
|
|
157
|
+
| `queueFallback` | on | Respaldo en la cola de espera bajo bloqueo cuando la compuerta de sesión propia no está disponible (fail-open) |
|
|
158
|
+
| `retryEnabled` | off | **Reintento automático (backend)**: reanudación automática tras fallos transitorios (desactivado por defecto, conservador) |
|
|
159
|
+
|
|
160
|
+
Configuración adicional:
|
|
161
|
+
|
|
162
|
+
- `timezone` (por defecto Asia/Shanghai) — zona usada para la **detección del fin de semana** y la visualización de la insignia; **no afecta a la detección de punta/valle** (siempre hora de Pekín);
|
|
163
|
+
- `peakWindows` (por defecto 09:00–12:00 / 14:00–18:00) — ventanas de punta/valle en hora de Pekín (UTC+8), en línea con la facturación oficial de DeepSeek;
|
|
164
|
+
- `pauseMode` (`safe`/`force`), `pauseReason` (`wait`/`stop`) — forma de efectuar la pausa;
|
|
165
|
+
- `stepGateTimeoutMs` (por defecto 300000) — tiempo máximo de espera de la compuerta de paso; al vencer, se libera la compuerta y **se escala a una pausa a nivel de turno** (antibloqueo, sin «un paso cada 5 minutos» y el goteo de tokens que supone);
|
|
166
|
+
- Veredicto de fuentes oficiales: `officialProviders` (id de proveedores oficiales adicionales, separados por comas, prioridad máxima), `officialBaseURLs` (lista de hosts de endpoints oficiales, por defecto `api.deepseek.com`);
|
|
167
|
+
- Cola diferida: `deferredMode` (`hold` mantiene en espera / `error` da error y difiere), `deferredResumeText` (texto de reanudación al salir de punta), `deferredMaxHoldMs` (límite de espera, 6 horas por defecto, al vencer pasa a error);
|
|
168
|
+
- Parámetros de reintento: `retryText`, `retryGraceMs`, `retryCooldownMs`, `retryBackoffFactor`, `retryBackoffMaxMs`, `retryMaxConsecutive`.
|
|
169
|
+
|
|
170
|
+
## Comportamiento
|
|
171
|
+
|
|
172
|
+
### Compuerta automática de punta (global)
|
|
173
|
+
|
|
174
|
+
- **Entrada en punta** (y no fin de semana): con `stepLevelPause` activado el turno **ya no se interrumpe de inmediato** — la sesión avanza con naturalidad hasta el siguiente límite `agent/pre-step`, donde se cierra la compuerta de paso (ver la sección siguiente); desactivado, se llama a `gate.stopNextTurn` sobre todas las sesiones raíz `running` (pausa real mediante la compuerta de sesión propia, o respaldo en la cola de espera bajo bloqueo según `queueFallback`);
|
|
175
|
+
- **Salida de punta / fin de semana**: primero `releaseAll` libera los pasos retenidos (el turno continúa en su sitio), después `gate.resume` sobre **todas** las sesiones — controlado por el interruptor `offPeakAutoResume`, desactivado no hay reanudación automática al salir de punta;
|
|
176
|
+
- **Zona horaria de punta/valle**: siempre hora de Pekín (`Asia/Shanghai`), en línea con la base de facturación oficial de DeepSeek, sin verse afectada por el ajuste `timezone`;
|
|
177
|
+
- Máquina de estados: instancia única `NORMAL ↔ PAUSED_PEAK` (`scheduler.js`), impulsada por un único tick de 30 s.
|
|
178
|
+
|
|
179
|
+
### Compuerta a nivel de paso (v0.2.0, la clave del ahorro de tokens)
|
|
180
|
+
|
|
181
|
+
Enganchada a la cascada `agent/pre-step`: el turno se suspende **antes de que ocurra la solicitud al modelo del siguiente paso**.
|
|
182
|
+
|
|
183
|
+
- **Condiciones de cierre** (todas necesarias): `enabled` + `stepLevelPause` + `step > 1` + punta (hora de Pekín, no fin de semana) + proveedor de destino oficial (`providerGuard`, si está desactivado se bloquean todos) + sesión no retenida ya a nivel de solicitud + no saltada manualmente en esta punta;
|
|
184
|
+
- **Por qué `step > 1`**: el primer paso de un turno lo cubre la guardia a nivel de solicitud, para que las dos compuertas no se solapen;
|
|
185
|
+
- **Vías de liberación**: ① botón «⏸ en pausa (continuar)» / `POST /session-guard/rpc {action:'stepResume'}` / `/resume` → deja pasar el paso actual y **deja de bloquear esta sesión por el resto de la punta**; ② salida de punta → todo se libera, el turno continúa en su sitio (**no hace falta followup**); ③ botón de congelación / `/pause` / `/cancel` → se libera la compuerta y se pasa a la pausa a nivel de turno; ④ abort de la `signal` (cancelación del usuario) → liberación;
|
|
186
|
+
- **Escalado por tiempo**: una suspensión mayor que `stepGateTimeoutMs` (5 minutos por defecto) → se libera la compuerta y **se escala a una pausa force a nivel de turno**, con reanudación uniforme al salir de punta (sin bloqueos definitivos ni goteo de tokens durante la punta);
|
|
187
|
+
- **Estado**: `GET /session-guard/state?session=<id>` devuelve `paused: { step, turn }` y `stepGate: { held, since, bypass }`; el `paused` del puerto de servicio `state()` **sigue siendo booleano** (compatibilidad hacia atrás), el estado del paso va en `pausedStep`;
|
|
188
|
+
- **No se persiste**: la suspensión es una Promise en el proceso, se pierde al reiniciar (evita estados fantasma).
|
|
189
|
+
|
|
190
|
+
#### Botones «Pausar sesión / Reanudar sesión» (proporcionados por session-guard)
|
|
191
|
+
|
|
192
|
+
El botón «Pausar sesión» a la derecha del área de entrada (slot `conversation.input.right`, id `session-guard-pause`, order 20, a la izquierda del «❄ Congelar y añadir» de input-traffic):
|
|
193
|
+
|
|
194
|
+
- sin pausar → «Pausar sesión», **pulsable**: el clic llama a `stepPause` y pausa la sesión **antes de la solicitud al modelo del siguiente paso** (sin interrumpir el paso actual; el paso 1 también se retiene, sin condiciones de punta/valle ni de proveedor);
|
|
195
|
+
- pausado → «Reanudar sesión», el clic llama a `stepResume`: deja pasar el paso actual y deja de bloquear esta sesión por el resto de la punta;
|
|
196
|
+
- **Empuje de eventos**: `GET /session-guard/events?session=<id>` (SSE) notifica **al instante** los cambios de estado de la compuerta de paso — cuando la punta cierra la compuerta automáticamente el botón pasa enseguida a «Reanudar sesión», sin esperar al sondeo; además, un sondeo de `/session-guard/state` cada 10 segundos actúa de red de seguridad (converge incluso con el SSE no disponible/desconectado);
|
|
197
|
+
- estilo alineado con los botones de input-traffic de la misma fila (24 px de alto / 6 px de radio / fuente de 12 px / los mismos tokens CSS), con respuesta visual al pasar el ratón y en estado de pausa.
|
|
198
|
+
|
|
199
|
+
### Bloqueo de sesión (congelación)
|
|
200
|
+
|
|
201
|
+
- **Puerto redundante**: `ctx.provide('sessionGuard', service)` — `stopNextTurn(sessionId)` / `resume(sessionId)` / `lockQueue(sessionId)` / `unlockQueue(sessionId)` / `state(sessionId)`;
|
|
202
|
+
- **Puente RPC**: `POST /session-guard/rpc { action, sessionId }` — el botón de congelación de input-traffic llama a `stopNextTurn` / `resume` **sesión a sesión** según `sessionId`; se omite silenciosamente si este complemento no está instalado (fail-open D8);
|
|
203
|
+
- **Comandos manuales**: `/pause [force|safe] [stop|wait]`, `/resume [confirm] [rerun|skip]`, `/cancel` — actúan sobre la sesión que los invoca (tomando `invocation.agent.id`).
|
|
204
|
+
|
|
205
|
+
### Reintento automático del backend (D9)
|
|
206
|
+
|
|
207
|
+
Escucha `turn/end` y clasifica los fallos:
|
|
208
|
+
|
|
209
|
+
- **Fallos transitorios** (error/429/max-tokens, etc.) → reanudación automática `followup(retryText)` con retroceso adaptativo;
|
|
210
|
+
- **Fallos permanentes** (autenticación/saldo/modelo/límite de contexto) → parada;
|
|
211
|
+
- **Cede el paso durante congelación/compuerta**: no reintenta cuando `isFrozen(sessionId)` es verdadero (queueLocked / paused / taskControl paused);
|
|
212
|
+
- La intervención del usuario o un turno con éxito reinician el contador de fallos consecutivos.
|
|
213
|
+
|
|
214
|
+
### Insignia de estado (visualización en el frontend)
|
|
215
|
+
|
|
216
|
+
A la derecha del área de entrada aparece una insignia de estado **puramente informativa** que refleja en tiempo real la fase actual:
|
|
217
|
+
|
|
218
|
+
| Fase | Texto de la insignia | Clase CSS | Significado |
|
|
219
|
+
|---|---|---|---|
|
|
220
|
+
| `peak` (veredicto bidimensional activo) | 高峰·拦官方 | `sg-peak` | Horas punta, solo se bloquean las solicitudes a fuentes oficiales de DeepSeek |
|
|
221
|
+
| `peak` (veredicto bidimensional inactivo) | 高峰·全部暂停 | `sg-peak` | Horas punta, todas las sesiones en pausa |
|
|
222
|
+
| `off-peak` | 谷时 | `sg-off` | Fuera de punta, las sesiones funcionan con normalidad |
|
|
223
|
+
| `weekend` | 周末 | `sg-weekend` | Fin de semana (con el modo fin de semana activo), se ignoran punta y valle |
|
|
224
|
+
|
|
225
|
+
- **Sondeo**: cada 15 segundos se solicita `GET /session-guard/status`, para obtener el `phase` global, `providerGuard`, `held`, `deferred`, `stepHeld`;
|
|
226
|
+
- **fail-open**: ruta inaccesible, error de red o `enabled` desactivado → la insignia se oculta en silencio, sin afectar a ninguna sesión;
|
|
227
|
+
- **Independiente de input-traffic**: la insignia la dibuja únicamente el cliente de session-guard, se muestra **sin necesidad de instalar el complemento input-traffic**. input-traffic solo aporta el botón de congelación, sin relación de dependencia con la insignia;
|
|
228
|
+
- **Tooltip**: al pasar el ratón muestra `fase · zona horaria · modo fin de semana · criterio de veredicto · números de retenciones/diferidos/retenciones de paso`.
|
|
229
|
+
|
|
230
|
+
### Criterio de veredicto de fuentes oficiales (providerGuard)
|
|
231
|
+
|
|
232
|
+
En punta no se detienen las sesiones sin más: primero se comprueba si «la ruta por la que realmente irá esta solicitud es una fuente oficial de DeepSeek»:
|
|
233
|
+
|
|
234
|
+
| Prioridad | Base | `matchedBy` | Ejemplo |
|
|
235
|
+
|---|---|---|---|
|
|
236
|
+
| 1 | lista explícita de id en `officialProviders` | `explicit` | el usuario declara oficial su propia pasarela |
|
|
237
|
+
| 2 | host de la `baseURL` normalizada en tiempo real | `endpoint` | `deepseek-official` apuntado a un relé → **no se bloquea** |
|
|
238
|
+
| 3 | endpoint por defecto integrado en el catalog | `endpoint-default` | la ruta `deepseek` de pi-ai apunta por defecto a la API oficial → **se bloquea** |
|
|
239
|
+
| 4 | lista de id integrada (`deepseek-official`) | `route-id` | respaldo cuando el endpoint no es legible |
|
|
240
|
+
| 5 | cualquier otra cosa | `unknown` | no oficial, se deja pasar |
|
|
241
|
+
|
|
242
|
+
- **El endpoint pesa más que el id**: una configuración llamada `deepseek-official` pero con `baseURL` apuntando a un relé **no** se bloquea por error; al revés, la ruta `deepseek` integrada de pi-ai tiene por defecto la API oficial como endpoint y **no** escapa del bloqueo.
|
|
243
|
+
- **Origen de los endpoints**: `ctx.get('llm').listConfigurableProviders()` localiza la entrada del catálogo → `ctx.settings.get(settingsNs)` lee la `baseURL` vía `settingsPath` (solo se leen campos no secretos, el valor de `apiKeyEnv` jamás se lee). Se recalcula en cada solicitud y no se cachea → los cambios en caliente de la configuración de un proveedor surten efecto al instante.
|
|
244
|
+
- **Cambiar a no oficial reanuda la sesión**: si tras la pausa de punta la sesión se cambia a un proveedor local/externo (evento `model/selection` desde 0.1.2+) → esa sesión se reanuda automáticamente (sujeto a `deferredResume`); solo se tocan las sesiones que este complemento pausó al entrar en punta, las pausadas manualmente con `/pause` **jamás** se tocan. Sin ese evento en 0.1.1 → se degrada a «la siguiente solicitud o un `/resume` manual».
|
|
245
|
+
- **Endpoint ilegible**: servicio `llm` ausente, estructura del espacio de nombres cambiada, campo no cadena — siempre se degrada al veredicto por id / endpoint integrado registrando el `matchedBy`, **nunca se lanza una excepción**.
|
|
246
|
+
- **Investigar un veredicto erróneo**: `GET /session-guard/provider?provider=<id>` devuelve `{ official, matchedBy, endpoint }`.
|
|
247
|
+
|
|
248
|
+
### Guardia a nivel de solicitud y cola diferida
|
|
249
|
+
|
|
250
|
+
- **Por qué a nivel de solicitud**: el tick de 30 s solo atiende, en el salto `NORMAL → PAUSED_PEAK`, a las sesiones que estaban `running` en ese momento; las sesiones iniciadas tras el inicio de la punta o cambiadas a mitad de camino a una fuente oficial se le escapan. La cascada `agent/request` es la red que pasa **con cada solicitud**.
|
|
251
|
+
- **El veredicto se basa en el valor devuelto por `next()`**: el middleware de selección de modelo sobrescribe provider/model dentro de la cascada con los valores elegidos en la interfaz, así que hay que hacer primero `await next()` y juzgar después.
|
|
252
|
+
- **Modo hold (por defecto)**: la solicitud queda suspendida, **sin enviarse ni dar error**, y se libera en el instante exacto de salida de punta (`msUntilOffPeak` con temporización precisa, tick de 30 s como red de seguridad); la cancelación del usuario (abort) interrumpe con normalidad.
|
|
253
|
+
- **Modo error**: lanza un error reconocible `PEAK_DEFERRED` + apunta en la cola diferida, con reanudación al salir de punta mediante `deferredResumeText` (sin reanudación automática si `deferredResume` está desactivado).
|
|
254
|
+
- **Protección de tope**: si `deferredMaxHoldMs` (6 horas por defecto) vence sin que acabe la punta → pasa a error, para evitar suspensiones infinitas.
|
|
255
|
+
- **Regla férrea de exclusión mutua**: durante un hold **jamás** se pide además la pausa por la compuerta de sesión (la pausa espera una frontera segura a la que una solicitud retenida nunca llegará → se esperarían mutuamente). Al entrar en punta se saltan las sesiones ya retenidas.
|
|
256
|
+
- **Sin persistencia**: la cola diferida es una promise en el proceso, desaparece al reiniciar.
|
|
257
|
+
|
|
258
|
+
### Límites (explícitamente fuera de alcance)
|
|
259
|
+
|
|
260
|
+
- **Sin cambio de proveedor / sin redirección**: solo bloquea, no enruta;
|
|
261
|
+
- **La compaction no pasa por `agent/request`**: no puede ocurrir mientras una sesión esté en pausa; una compresión disparada manualmente durante la punta puede alcanzar la fuente oficial (este complemento no intercepta la capa `ctx.llm.stream`);
|
|
262
|
+
- **0.1.1 no tiene el evento `model/selection`**: la reanudación automática tras cambiar a una fuente no oficial se degrada a «esperar a la siguiente solicitud o un `/resume` manual» (inmediato desde 0.1.2);
|
|
263
|
+
- **Sin nuevas dependencias npm**, sin lectura ni escritura de credenciales, y los reintentos 429 / de capa de transporte de `dsh-llm-retry` no se tocan.
|
|
264
|
+
|
|
265
|
+
### Manejo y validación de zonas horarias
|
|
266
|
+
|
|
267
|
+
- La detección de zona se basa en **nombres de zona IANA** (como `Asia/Shanghai`, `Asia/Tokyo`, `Asia/Seoul`), proyectados mediante `Intl.DateTimeFormat` sobre el reloj de pared de la zona configurada, **sin depender del `getUTCDay()` desnudo** — evitando el clásico fallo de 8 horas en el límite UTC+8 de la hora de Pekín (sábado 00:30 hora de Pekín: en UTC aún es viernes);
|
|
268
|
+
- `Intl.DateTimeFormat` es a la vez la capa de validación: un nombre de zona no válido (como `Foo/Bar`) lanza una `RangeError`, que un try-catch externo degrada en silencio a la zona por defecto `Asia/Shanghai` (fail-open);
|
|
269
|
+
- Las ventanas de punta son **cerradas por la izquierda y abiertas por la derecha** `[start, end)`, con soporte de ventanas que cruzan la medianoche (como `22:00–06:00`);
|
|
270
|
+
- El ajuste `timezone` se comporta igual en todos los idiomas (zh/en/ja/ko) — los nombres de zona IANA de `Intl.DateTimeFormat` no dependen de la configuración regional: con interfaz japonesa/coreana el comportamiento de la zona es idéntico al del chino.
|
|
271
|
+
|
|
272
|
+
### Reparto de tareas con input-traffic: uno «detiene», el otro «hace cola»
|
|
273
|
+
|
|
274
|
+
Ambos actúan sobre **eslabones distintos de la misma cadena**, y la frontera la fija el propio modelo de inbox de DSH:
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
Entrada del usuario ──(input-traffic elige el tramo)──▶ colas de espera next-step / next-turn
|
|
278
|
+
│
|
|
279
|
+
agent/pre-step ──(compuerta de paso de este complemento)──▶ dejar pasar / retener
|
|
280
|
+
│
|
|
281
|
+
agent/request ──(retención a nivel de solicitud de este complemento)──▶ dejar pasar / retener
|
|
282
|
+
│
|
|
283
|
+
llamada al modelo
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
**Semántica de las colas de DSH (dos colas, no confundirlas)**
|
|
287
|
+
|
|
288
|
+
| Cola | Significado | Cuándo se consume |
|
|
289
|
+
|---|---|---|
|
|
290
|
+
| `next-step` | «Entrada a la espera del siguiente límite de paso» | En el siguiente `agent/pre-step`: **al mismo nivel que un resultado de herramienta**, otro paso dentro del mismo turno |
|
|
291
|
+
| `next-turn` | «Prompt a la espera de un turno independiente» | Al cerrarse el turno actual, arrancado como **turno nuevo** |
|
|
292
|
+
|
|
293
|
+
`Inbox.claim()` **vacía siempre primero `next-step`** y solo toma además **1** elemento de `next-turn` cuando ese límite abre un turno nuevo; el primer paso de un turno lee next-turn, todos los siguientes leen next-step.
|
|
294
|
+
|
|
295
|
+
**Reparto de responsabilidades**
|
|
296
|
+
|
|
297
|
+
- **session-guard = detener**: solo decide «cuándo se puede avanzar» y **jamás toca el contenido ni el orden de las colas**.
|
|
298
|
+
- compuerta de paso (`agent/pre-step`): retiene **antes** de la solicitud al modelo del siguiente paso;
|
|
299
|
+
- pausa a nivel de turno (`agent.cancel({keepInbox:true})` + `goals.pause` + frontera segura): detiene el turno actual, **las colas quedan tal cual**;
|
|
300
|
+
- guardia a nivel de solicitud (hold en `agent/request`): retiene **esta única solicitud al modelo**.
|
|
301
|
+
- **input-traffic = hacer cola**: solo decide «a qué cola va la entrada del usuario, con qué tramo y cuándo se consume».
|
|
302
|
+
- tres tramos = a qué cola va: rojo «interrumpir» hace primero `cancel()` y luego `steer`; amarillo «intercalar» hace `steer` (→ `next-step`, el siguiente paso del mismo turno); verde «a la cola» se queda en `next-turn`;
|
|
303
|
+
- congelación = extraer las filas `queued` + `steering` (conservando el tramo) + bloqueo del composer + llamada a `sessionGuard.stopNextTurn`; reanudación = quitar el bloqueo → primero `sessionGuard.resume` → reenvío según el tramo.
|
|
304
|
+
|
|
305
|
+
**Dos reglas férreas en el punto de encuentro**
|
|
306
|
+
|
|
307
|
+
1. **La congelación debe dejar que este complemento libere primero la compuerta de paso**: la compuerta de paso está enganchada a `agent/pre-step`, mientras que la pausa a nivel de turno espera un evento en la frontera segura — se esperarían mutuamente (`pauseTask` / `cancelTask` de este complemento hacen primero `release`);
|
|
308
|
+
2. **Con la compuerta de paso retenida, los mensajes ya fueron tomados**: `preStep()` hace `inbox.claim()` antes de despachar la cascada, así que las entradas nuevas hacen cola detrás del lote ya tomado; `keepInbox` solo aplica a la pausa a nivel de turno.
|
|
309
|
+
|
|
310
|
+
**Ninguna invasión mutua**: input-traffic no escucha `agent/pre-step` / `agent/request` (única excepción el `cancel()` explícito del tramo «interrumpir», pedido por el propio usuario); este complemento nunca reescribe el contenido ni el orden de `next-step` / `next-turn`.
|
|
311
|
+
|
|
312
|
+
En cuanto a los botones: los botones «Pausar sesión / Reanudar sesión» de este complemento (order 20) y los de input-traffic «❄ Congelar y añadir / Reanudar y añadir» (order 30) aparecen uno al lado del otro sin sustituirse — los primeros gobiernan la compuerta de paso, los segundos la extracción de la cola + la congelación a nivel de turno.
|
|
313
|
+
|
|
314
|
+
## Puerto redundante `sessionGuard`
|
|
315
|
+
|
|
316
|
+
```js
|
|
317
|
+
{
|
|
318
|
+
stopNextTurn(sessionId, opts), // detiene el próximo turno de la sesión (compuerta de sesión propia / respaldo en cola bajo bloqueo)
|
|
319
|
+
resume(sessionId, opts), // reanuda (confirm + choice: rerun|skip)
|
|
320
|
+
lockQueue(sessionId, reason), // bloquea explícitamente la cola
|
|
321
|
+
unlockQueue(sessionId), // desbloquea explícitamente
|
|
322
|
+
stepPause(sessionId), // solicita manualmente una pausa a nivel de paso (cierre en el siguiente límite pre-step, también el paso 1)
|
|
323
|
+
stepResume(sessionId, opts), // abre la compuerta de paso (v0.2.0); con opts.bypass=false no se salta esta punta
|
|
324
|
+
state(sessionId), // { queueLocked, lockReason, paused, pausedStep, stepHeldSince, stepBypass, taskControlAvailable, taskControl }
|
|
325
|
+
}
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
## Rutas HTTP
|
|
329
|
+
|
|
330
|
+
- `GET /session-guard/state?session=<id>` — estado de la sesión (con `paused: { step, turn, manual }` / `stepGate` / último destino / en retención o no / diferida o no)
|
|
331
|
+
- `GET /session-guard/events?session=<id>` — **SSE**: notificación inmediata de cambios de estado de la compuerta de paso (el botón se actualiza gracias a esto)
|
|
332
|
+
- `GET /session-guard/settings` — ajustes + disponibilidad de taskControl
|
|
333
|
+
- `GET /session-guard/status` — fase global actual (sondeo de la insignia de estado; incluye `stepHeld`)
|
|
334
|
+
- `GET /session-guard/provider?provider=<id>` — diagnóstico del veredicto de fuente oficial (`official` / `matchedBy` / `endpoint`)
|
|
335
|
+
- `GET /session-guard/diag` — diagnóstico en tiempo de ejecución (incluye `stepGate`)
|
|
336
|
+
- `POST /session-guard/rpc` — `{ action: stopNextTurn|resume|lockQueue|unlockQueue|stepPause|stepResume|state, sessionId }`
|
|
337
|
+
|
|
338
|
+
## Almacenamiento del estado
|
|
339
|
+
|
|
340
|
+
JSON por sesión: `$DSH_HOME/.dsh/session-guard/<sessionId>.json` (escritura atómica; se puede anular con `DSH_SESSION_GUARD_STATE_DIR`).
|
|
341
|
+
|
|
342
|
+
## Pruebas
|
|
343
|
+
|
|
344
|
+
```bash
|
|
345
|
+
npm test # node --test tests/*.test.mjs (zonas horarias/fin de semana/máquina de estados/compuerta de sesión/puente/reintento)
|
|
346
|
+
```
|
|
347
|
+
|
|
348
|
+
## Módulos
|
|
349
|
+
|
|
350
|
+
| Archivo | Responsabilidad |
|
|
351
|
+
|---|---|
|
|
352
|
+
| `src/time.js` | Detección de punta/fin de semana (con zona horaria correcta) + `msUntilOffPeak` (temporización precisa de la salida de punta) |
|
|
353
|
+
| `src/scheduler.js` | Máquina de estados pura NORMAL ↔ PAUSED_PEAK |
|
|
354
|
+
| `src/provider.js` | Veredicto de fuentes oficiales a cinco niveles (función pura: normalización de endpoint + matriz de decisión) |
|
|
355
|
+
| `src/provider-directory.js` | Catálogo de endpoints (`llm.listConfigurableProviders` + `settings.get`, degradación en toda la cadena) |
|
|
356
|
+
| `src/deferrals.js` | Registro de diferidos (retención / liberación / superación del límite / `PeakDeferredError`) |
|
|
357
|
+
| `src/request-guard.js` | Guardia a nivel de solicitud en `agent/request` (dos modos hold / error) |
|
|
358
|
+
| `src/step-gate.js` | **Compuerta a nivel de paso en `agent/pre-step`** (v0.2.0: cierre / liberación / escalado por tiempo / bypass, el `decideStepHold` puro es unitestable) |
|
|
359
|
+
| `src/targets.js` | Seguimiento del «último destino real» de la sesión (`request/header` + `model/selection`) |
|
|
360
|
+
| `src/wiring.js` | Orquestación del cableado (filtro de entrada en punta / cableado de la compuerta de paso / liberación al salir de punta / temporización precisa / limpieza al descargar) |
|
|
361
|
+
| `src/pause-gate.js` | Motor de la compuerta de sesión propia (agent.cancel keepInbox + goals.pause + frontera segura + reanudación por followup; libera antes la compuerta de paso antes de pausar) |
|
|
362
|
+
| `src/pause-store.js` | Persistencia del estado de pausa propio |
|
|
363
|
+
| `src/gate.js` | Controlador de la compuerta de sesión (pausa real propia / respaldo en cola bajo bloqueo, fail-open) |
|
|
364
|
+
| `src/bridge.js` | Puerto redundante `sessionGuard` |
|
|
365
|
+
| `src/retry.js` | Reintento automático del backend (clasificación de fallos/retroceso/cesión durante congelación; cortocircuito solo con el código exacto `PEAK_DEFERRED`) |
|
|
366
|
+
| `src/detect.js` | Detección automática (taskControl del host / puente cliente input-traffic) |
|
|
367
|
+
| `src/store.js` | Estado persistente por sesión |
|
|
368
|
+
| `src/settings.js` | Subsección de ajustes (esquema schemastery + registro fail-open) |
|
|
369
|
+
| `src/index.js` | apply en el host (ajustes/rutas/tick/provisión del servicio/cableado del reintento/guardia de solicitudes) |
|
|
370
|
+
| `src/client/` | Mitad de navegador (**botón de pausa de sesión** + insignia de estado + tarjeta de ajustes) |
|
|
371
|
+
|
|
372
|
+
## Licencia
|
|
373
|
+
|
|
374
|
+
MIT — véase [LICENSE](LICENSE).
|