dsh-session-guard 4.0.2 → 4.1.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/LICENSE +21 -21
- package/README.de.md +375 -375
- package/README.es.md +374 -374
- package/README.fr.md +373 -373
- package/README.it.md +374 -374
- package/README.ru.md +374 -374
- package/cordis.patch.yml +10 -10
- package/dsh.plugin.json +1 -1
- package/lib/client.js +222 -44
- package/lib/client.js.map +1 -1
- package/package.json +10 -10
- package/src/bridge.js +136 -136
- package/src/client/family-section.tsx +26 -6
- package/src/client/index.ts +176 -53
- 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/shell-overlay.tsx +69 -0
- package/src/client/status-badge.tsx +17 -0
- package/src/client/styles.ts +30 -30
- package/src/detect.js +27 -27
- package/src/gate.js +93 -93
- package/src/index.js +36 -1
- 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/README.de.md
CHANGED
|
@@ -1,375 +1,375 @@
|
|
|
1
|
-
<p align="center">
|
|
2
|
-
<strong>Automatisches Session-Gate zu Spitzenzeiten: Wochenendmodus + automatische Spitzenzeiten-Pause + zweidimensionale Prüfung offizieller Quellen + Session-Freeze + automatische Backend-Wiederholung</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> · <strong>Deutsch</strong> · <a href="README.it.md">Italiano</a> · <a href="README.ru.md">Русский</a> · <a href="README.es.md">Español</a>
|
|
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 auf Russisch](./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 auf Russisch](./CHANGELOG.ru.md)
|
|
42
|
-
- [Changelog en español](./CHANGELOG.es.md)
|
|
43
|
-
|
|
44
|
-
> **Kompatibilitätshinweis:** v0.1.1 enthält bereits japanische (`ja`) und koreanische (`ko`) Wörterbücher, aber die aktuellen offiziellen DSH-Versionen stellen über `LocaleRuntime` nur `zh` und `en` bereit. In einem unveränderten DSH schlägt die Wahl von `ja` oder `ko` mit `locale "<id>" is not registered` fehl. Es muss abgewartet werden, bis das offizielle DSH die entsprechenden Locale-IDs ergänzt. Fortgeschrittene Benutzer können einen DSH-Fork pflegen, um dies zu erweitern.
|
|
45
|
-
|
|
46
|
-
> **▼ DSH-Versionskompatibilität**
|
|
47
|
-
>
|
|
48
|
-
> | DSH-Version | Laden | Einstellungsregistrierung | Session-Events / Gate | Client-Seite |
|
|
49
|
-
> | --- | --- | --- | --- | --- |
|
|
50
|
-
> | 0.1.0-rc.7 ~ 0.1.1-rc.x | ➖ nicht in dieser Linie (historische Versionen vor `legacy/0.1.2`) | `ctx.settings.register(ns, schema, { base })` | ✅ gleiche Form | ✅ keine Plattformwert-Imports |
|
|
51
|
-
> | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ➖ nicht in dieser Linie → `legacy/0.1.2` verwenden (npm `@dsh-0.1.2`) | `register` bleibt erhalten (zusätzlich `installSection`) | ✅ gleiche Form | ✅ keine Plattformwert-Imports |
|
|
52
|
-
> | **0.2.0-rc.1+** | ✅ (**diese Linie**, dist-tag `dsh-0.2.0`, npm `4.0.0`) | deklarativ: `.volatile()`-Felder des Config werden vom Host zu Formularen projiziert, `register` entfernt | ✅ Events über den Dual-Pfad `snapshotEvents()` gelesen | ✅ Client-Einstellungskarte auf `configForms` umgestellt |
|
|
53
|
-
> | 0.1.7-rc.1+ | ✅ (Zweig `compat/0.1.7`, dist-tag `dsh-0.1.7`) | deklarativ: `.volatile()`-Felder des Config werden vom Host zu Formularen projiziert, `register` entfernt | ✅ Events über den Dual-Pfad `snapshotEvents()` gelesen | ✅ Client-Einstellungskarte auf `configForms` umgestellt |
|
|
54
|
-
> | 0.1.5-rc.2 | ✅ (Zweig `compat/0.1.5`, dist-tag `dsh-0.1.5`) | `register` weiterhin vorhanden (String-Namespaces) | ✅ Events über den Dual-Pfad `snapshotEvents()` gelesen | ✅ |
|
|
55
|
-
>
|
|
56
|
-
> **Identität dieser Linie**: Zweig `compat/0.2.0`, npm-Versionsnummer **`4.0.0`** (semver), dist-tag **`dsh-0.2.0`**.
|
|
57
|
-
> Die `engines.dsh` in `package.json` und `dsh.plugin.json` sowie die vier `@deepseek-ai/dsh-client-*`-Peers
|
|
58
|
-
> sind einheitlich auf `>=0.2.0-rc.1 <0.2.1-0` gesetzt; die Untergrenze des `@deepseek-ai/cordis`-Peers entspricht dem `^4.0.4` der Host-Linie.
|
|
59
|
-
> Früher benutzte das README „2.x / 3.x" als **Liniencoden** — das war eine Erzählkonvention, **keine aus der Registry
|
|
60
|
-
> pullbare Versionsnummer** — maßgeblich sind ausschließlich die npm-Versionen `0.3.1` (0.1.2-Linie), `3.0.0`/`3.0.1` (0.1.5-Linie),
|
|
61
|
-
> `3.2.4` (0.1.7-Linie) und `4.0.0` (0.2.0-Linie).
|
|
62
|
-
>
|
|
63
|
-
> **0.2.0-Anpassung (Zweig compat/0.2.0, npm 4.0.0)**:
|
|
64
|
-
> Die von diesem Plugin genutzte Plugin-API (Manifest/Settings/HMR/Slot/Session V4) ist in 0.2.0-rc.1 vollständig kompatibel mit 0.1.7;
|
|
65
|
-
> die Anpassung ist ein reiner Metadaten-Generationswechsel (Peer/Engines/dist-tag/Version 4.0.0). Mit der Basis gemeinsam übernommene
|
|
66
|
-
> Engineering-Fixes:
|
|
67
|
-
> ① `src/client/family-section.tsx` ist in die Versionskontrolle aufgenommen (vorher nur im lokalen Arbeitsbereich vorhanden, ein sauberer Checkout konnte nicht bauen);
|
|
68
|
-
> ② die eslint-Konfiguration verzichtet auf das Core-`no-unused-vars`, das TS-Typsignatur-Parameter fälschlich meldete — die Lint-Baseline ist wieder komplett grün;
|
|
69
|
-
> ③ die statischen v4-Gates für die Signaturen der Schreibpfade (`tests/source-kind.test.mjs`) kommen mit der Basis in diese Linie;
|
|
70
|
-
> ④ der `@deepseek-ai/cordis`-Peer ist auf `^4.0.4` ausgerichtet (die Host-UI-Pakete von 0.2.0-rc.1 deklarieren `~4.0.4`; das frühere `^4.0.1` kollidierte nicht, lag aber mit seiner Untergrenze zu tief).
|
|
71
|
-
>
|
|
72
|
-
> **Verbleib anderer Linien**: DSH-`0.1.2-rc.x`-Hosts verwenden bitte den Zweig **`legacy/0.1.2`** (npm-dist-tag
|
|
73
|
-
> `dsh-0.1.2`, Version `0.3.1`). **`main` ist bei `0.2.0-beta.1` eingefroren und ist nicht der Release-Zweig der 0.1.2-Linie.**
|
|
74
|
-
> DSH-`0.1.0-rc.7` ~ `0.1.1-rc.x`-Hosts verwenden bitte historische Versionen ≤ `0.1.2`.
|
|
75
|
-
> Feldursprungs-Definitionen siehe
|
|
76
|
-
> `mine-dsh-plugins/improve-dsh-plugins/DSH-PLUGIN-VERSION-DISTRIBUTION-STRATEGY.md` §2.2.
|
|
77
|
-
>
|
|
78
|
-
> **0.1.5-Anpassung (Zweig compat/0.1.5, npm 3.0.0)**:
|
|
79
|
-
> ① Dual-Pfad für `agent.followup` — 0.1.5 macht die Inbox zu einer schreibgeschützten Projektion der Agent-Loop; existiert `followup`
|
|
80
|
-
> nicht mehr, wird auf `agent.send` zurückgefallen, ist beides nicht vorhanden, folgt eine Warnung statt eines Fehlers (Absicherung für die Fortsetzung);
|
|
81
|
-
> ② Fortsetzungsnachrichten erhalten `source` mit `form: 'instructions'` (0.1.5-ContextFormed-Vertrag, ältere Versionen ignorieren das);
|
|
82
|
-
> ③ `webServer.register` ist in try/catch gepackt; eine fehlgeschlagene Registrierung wird nur geloggt und bringt das Host-Plugin-Loading nicht zum Absturz;
|
|
83
|
-
> ④ 0.1.5 entfernt den Array-Accessor `session.events`; Events werden über `snapshotEvents()` als Hauptpfad +
|
|
84
|
-
> Rückfall auf das alte Array gelesen (betrifft nur die beiden Hilfswege `findToolOutcome` / `lastUserPrompt`, das Event-Typ-Matching bleibt unverändert).
|
|
85
|
-
> Verifiziert: Der `WebRoute`-Vertrag (exact/prefix + SSE) von 0.1.5 ist unverändert; der Client-
|
|
86
|
-
> `fetch('/session-guard/...')` braucht kein `/api`-Präfix.
|
|
87
|
-
> Die folgende Kompatibilitätstabelle überspannt zwei API-Generationen (0.1.1 / 0.1.2); diese Linie beansprucht nur die 0.1.5-Zeile.
|
|
88
|
-
> `session/event`, `agent.cancel`, `goals.pause`,
|
|
89
|
-
> `agent.followup`, `commands.register`, `timer.interval`, `webServer.register`,
|
|
90
|
-
> `agent/request`, `llm.listConfigurableProviders`, `settings.register/get` haben zwischen
|
|
91
|
-
> `dsh-v0.1.1-rc.2` und `dsh-v0.1.2-rc.1` identische Signaturen (diese Linie ist bis `dsh-v0.1.5-rc.2` verifiziert);
|
|
92
|
-
> einzig die Form der von `tool/result` aufgezeichneten Call-ID erfordert eine Doppellektüre (`content[].toolCallId` zuerst,
|
|
93
|
-
> `source.callId` als Rückfallebene), ausgelagert in `src/tool-call-id.js` mit Unit-Tests — in Replay-Logs beider Versionen können beide Formen auftreten.
|
|
94
|
-
> Ab 0.1.5 ist der Array-Accessor `session.events` entfernt; diese Linie liest über `snapshotEvents()` (alter Array als Rückfallebene erhalten),
|
|
95
|
-
> was nur die beiden Hilfswege `findToolOutcome` / `lastUserPrompt` betrifft.
|
|
96
|
-
> Das `model/selection`-Event gibt es **nur ab 0.1.2**, dient nur der Modellwechsel-Beschleunigung und muss per Feature-Erkennung abgefragt werden; die Einstellungsoberfläche nutzt nur
|
|
97
|
-
> die `register` + `get`-Schnittmenge (niemals `installSection` / das entfernte `installSettingsSection`).
|
|
98
|
-
> Drift-Wächter-Skript: `tools/check-api-drift.ps1` (diese Linie prüft standardmäßig gegen `dsh-v0.1.5-rc.2`, ob die benötigten Schnittstellen existieren).
|
|
99
|
-
|
|
100
|
-
> Pausiert laufende Sessions automatisch während der Spitzenzeiten, setzt sie automatisch außerhalb der Spitzenzeiten/am Wochenende fort; zusammen mit dem Freeze-Button von input-traffic für **Session-weise** Sperren; die **automatische Backend-Wiederholung** weicht während Freeze/Gate aus. Der Kern basiert auf einem **eigenen Session-Gate** (`agent.cancel keepInbox + goals.pause + session/event sichere Grenze + followup-Fortsetzung`) und hängt nicht mehr von dsh-task-control ab.
|
|
101
|
-
|
|
102
|
-
Ein cordis-Plugin, zusammengesetzt über den `dsh plugin`-Befehl und einen Bundle-Patch — ohne dsh-Quellcode-Änderungen, ohne PR.
|
|
103
|
-
|
|
104
|
-
> 💡 **Warum empfohlen**: DeepSeek hat am 2026-08-17 die **Peak-/Off-Peak-Abrechnung** eingeführt — in Spitzenzeiten (Pekinger Zeit 9:00-12:00, 14:00-18:00) kostet die Einheit das **Doppelte** der Schwachlastzeit (einschließlich Mittag, Nacht, Wochenende und Feiertage). Dieses Plugin pausiert laufende Sessions während der Spitzenzeiten automatisch und setzt sie außerhalb automatisch fort; zeitversetzte Langläufe sparen bis zu **50 %**; manuelles Einfrieren (mit dem input-traffic-Button) erlaubt zusätzlich eine präzise, sessiongenaue Steuerung.
|
|
105
|
-
|
|
106
|
-
## Funktionsübersicht
|
|
107
|
-
|
|
108
|
-
- **Wochenendmodus**: erkennt Wochenenden (zeitzonengenau via `Intl.DateTimeFormat`, ohne den 8-Stunden-Grenzfehler des nackten `getUTCDay()` bei der Pekinger Zeitzone) → am Wochenende gelten keine Peak-/Off-Peak-Zeiten, freies Laufen.
|
|
109
|
-
- **Automatische Spitzenzeiten-Pause (global)**: beim Eintritt in die Spitzenzeit (und nicht am Wochenende) werden alle laufenden Root-Sessions automatisch pausiert; nach der Spitzenzeit werden alle automatisch fortgesetzt — **globaler Schalter, keine manuelle Arbeit**.
|
|
110
|
-
- **Zweidimensionale Prüfung offizieller Quellen (providerGuard)**: in Spitzenzeiten wird **nur blockiert, wenn das Ziel der Anfrage eine offizielle DeepSeek-Quelle ist**; mit lokalen/Drittanbieter-Providern (z. B. `local-35b`) läuft alles normal, unberührt vom Peak-Gate. Prüfreihenfolge = explizite ID-Liste → `baseURL`-Endpunkt → Katalog-Standardendpunkt → eingebaute ID.
|
|
111
|
-
- **Anfragegenaues Sicherheitsnetz + verzögerte Warteschlange**: Sessions, die nach dem Spitzenbeginn starten oder unterwegs auf eine offizielle Quelle umgeschaltet werden, greift der Anfrage-Wächter `agent/request` auf (Standard `hold`: die Anfrage wird ohne Fehler angehalten und nach der Spitzenzeit automatisch freigegeben).
|
|
112
|
-
- **Session-weise Einfrieren / Fortsetzen**: redundanter Port `sessionGuard` + `POST /session-guard/rpc`, sessiongenau angeschlossen über den input-traffic-Freeze-Button; zusätzlich die manuellen Befehle `/pause /resume /cancel`.
|
|
113
|
-
- **Automatische Backend-Wiederholung (D9)**: transiente turn/end-Fehler (error/429/max-tokens) werden mit adaptivem Backoff automatisch fortgesetzt; permanente Fehler stoppen; **weicht während Freeze/Gate aus**, umgeht nie das Session-Gate.
|
|
114
|
-
- **fail-open**: eigenes Session-Gate nicht verfügbar, session-guard nicht installiert, Settings-Dienst fehlt — in allen Fällen stille Degradierung, niemals ein Absturz wegen einer Abhängigkeit.
|
|
115
|
-
|
|
116
|
-
## Vorschau der Oberfläche
|
|
117
|
-
|
|
118
|
-
Echter Lauf-Screenshot (Windows, dsh web) — aktivierter Wochenendmodus:
|
|
119
|
-
|
|
120
|
-
<figure>
|
|
121
|
-
<img style="max-width:100%" alt="Statusleiste im Eingabebereich: der aktivierte „Wochenende"-Button ist hervorgehoben (bei aktivem Wochenendmodus laufen Sessions frei, ohne Peak-/Off-Peak-Rücksicht), daneben der Button „Session einfrieren" (mit input-traffic), die DeepSeek-V4-Flash-Denkstufe und Sendesteuerelemente, unten Statusleisten mit Runden/Schritten, LLM-Zeit, Cache-Trefferquote u. a." src="assets/高峰低峰周末提醒-周末状态.png" />
|
|
122
|
-
<figcaption>Wochenendmodus aktiv: das „Wochenende"-Badge im Eingabebereich ist hervorgehoben, neben „Session einfrieren"; am Wochenende gibt es keine Peak-/Off-Peak-Zeiten, Sessions laufen frei.</figcaption>
|
|
123
|
-
</figure>
|
|
124
|
-
|
|
125
|
-
## Installation
|
|
126
|
-
|
|
127
|
-
```bash
|
|
128
|
-
# DSH-0.1.5-rc.x-Hosts (diese Linie, dist-tag dsh-0.1.5)
|
|
129
|
-
dsh plugin --profile web add dsh-session-guard@dsh-0.1.5
|
|
130
|
-
|
|
131
|
-
# oder direkt über den git-Zweig
|
|
132
|
-
dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
|
|
133
|
-
|
|
134
|
-
# DSH-0.1.2-rc.x-Hosts müssen die 0.1.2-Linie verwenden
|
|
135
|
-
dsh plugin --profile web add dsh-session-guard@dsh-0.1.2
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
`compat/0.1.5` ist die DSH-`0.1.5-rc.x`-Speziallinie, npm-Versionsnummer **`3.0.0`**; DSH-`0.1.2-rc.x`-Hosts verwenden bitte den
|
|
139
|
-
Zweig **`legacy/0.1.2`** (npm-dist-tag `dsh-0.1.2`, Version `0.3.1`). **`main` ist bei `0.2.0-beta.1` eingefroren,
|
|
140
|
-
nicht der Release-Zweig der 0.1.2-Linie.**
|
|
141
|
-
|
|
142
|
-
> ⚠️ Verlassen Sie sich nicht auf den nackten Paketnamen `dsh-session-guard`: npm's `latest`-Tag kann zwei sich gegenseitig ausschließende
|
|
143
|
-
> Versionslinien nicht gleichzeitig bedienen (ihre `engines.dsh`-Bereiche sind nach den semver-Prerelease-Regeln exklusiv) — der dist-tag muss explizit angegeben werden.
|
|
144
|
-
|
|
145
|
-
Nach der Installation dsh web neu starten und die Seite neu laden.
|
|
146
|
-
|
|
147
|
-
## Einstellungen (Einstellungen → Plugins → session-guard, einfache Schalter)
|
|
148
|
-
|
|
149
|
-
| Schalter | Standard | Beschreibung |
|
|
150
|
-
|---|---|---|
|
|
151
|
-
| `enabled` | on | **Automatische Pausierung/Einfrieren von Sessions in Spitzenzeiten**: pausiert laufende Sessions während der Spitzenzeiten automatisch |
|
|
152
|
-
| `stepLevelPause` | on | **Gate auf Step-Ebene**: in Spitzenzeiten wird das Gate **vor** der Modellanfrage des nächsten Steps geschlossen (früher und sparsamer als eine Pausierung auf Rundenebene); aus = Rückfall auf Runden-Pause |
|
|
153
|
-
| `providerGuard` | on | **Zweidimensionale Prüfung offizieller Quellen**: in Spitzenzeiten werden nur offizielle DeepSeek-Quellen blockiert, lokale/Drittanbieter-Provider laufen normal |
|
|
154
|
-
| `guardSubagents` | on | **Subagenten-Anfragen einbeziehen**: auch Subagenten-Anfragen werden abgerechnet, standardmäßig mit blockiert |
|
|
155
|
-
| `offPeakAutoResume` | on | **Automatische Fortsetzung in Schwachlastzeiten**: pausierte Sessions werden außerhalb der Spitzenzeiten automatisch fortgesetzt; aus = keine automatische Fortsetzung nach der Spitzenzeit (manuell nötig) |
|
|
156
|
-
| `weekendMode` | on | **Wochenendmodus**: erkennt Wochenenden → am Wochenende keine automatische Pausierung (am Wochenende gibt es keine Spitzenzeit, freies Laufen) |
|
|
157
|
-
| `deferredResume` | on | **Automatische Fortsetzung nach der Spitzenzeit**: aus = verzögerte Anfragen/Sessions laufen nicht automatisch weiter, `/resume` nötig |
|
|
158
|
-
| `queueFallback` | on | Rückfall auf die Lock-Wait-Warteschlange, wenn das eigene Session-Gate nicht verfügbar ist (fail-open) |
|
|
159
|
-
| `retryEnabled` | off | **Automatische Wiederholung (Backend)**: transiente Fehler werden automatisch fortgesetzt (Standard aus, konservativ) |
|
|
160
|
-
|
|
161
|
-
Zusätzliche Konfiguration:
|
|
162
|
-
|
|
163
|
-
- `timezone` (Standard Asia/Shanghai) — Zeitzone für die **Wochenenderkennung** und die Badge-Anzeige; **beeinflusst die Peak-/Off-Peak-Erkennung nicht** (immer Pekinger Zeit);
|
|
164
|
-
- `peakWindows` (Standard 09:00–12:00 / 14:00–18:00) — Peak-/Off-Peak-Fenster in Pekinger Zeit (UTC+8), identisch mit DeepSeeks offizieller Abrechnung;
|
|
165
|
-
- `pauseMode` (`safe`/`force`), `pauseReason` (`wait`/`stop`) — Art der Pausierung;
|
|
166
|
-
- `stepGateTimeoutMs` (Standard 300000) — Halte-Timeout des Step-Gates; bei Ablauf wird das Gate freigegeben und zu einer **Pausierung auf Rundenebene eskaliert** (Anti-Deadlock, kein „alle 5 Minuten ein Step"-Token-Tropf);
|
|
167
|
-
- Prüfung offizieller Quellen: `officialProviders` (zusätzliche offizielle Provider-IDs, kommasepariert, höchste Priorität), `officialBaseURLs` (Liste offizieller Endpoint-Hosts, Standard `api.deepseek.com`);
|
|
168
|
-
- Verzögerte Warteschlange: `deferredMode` (`hold` anhalten / `error` Fehler und verzögern), `deferredResumeText` (Text der Fortsetzung nach der Spitzenzeit), `deferredMaxHoldMs` (Haltelimit, Standard 6 h, bei Ablauf Umwandlung in error);
|
|
169
|
-
- Retry-Parameter: `retryText`, `retryGraceMs`, `retryCooldownMs`, `retryBackoffFactor`, `retryBackoffMaxMs`, `retryMaxConsecutive`.
|
|
170
|
-
|
|
171
|
-
## Verhalten
|
|
172
|
-
|
|
173
|
-
### Automatisches Peak-Gate (global)
|
|
174
|
-
|
|
175
|
-
- **Spitzenbeginn** (und nicht Wochenende): mit aktiviertem `stepLevelPause` wird die laufende Runde **nicht mehr sofort unterbrochen** — die Session läuft bis zur nächsten `agent/pre-step`-Grenze, wo das Step-Gate schließt (siehe nächster Abschnitt); ausgeschaltet wird für alle laufenden Root-Sessions `gate.stopNextTurn` aufgerufen (echte Pause über das eigene Session-Gate, oder je nach `queueFallback` Rückfall auf die Lock-Wait-Warteschlange);
|
|
176
|
-
- **Spitzenende / Wochenende**: zuerst `releaseAll` für die gehaltenen Steps (die Runde läuft an Ort und Stelle weiter), dann `gate.resume` **aller** Sessions — gesteuert durch den Schalter `offPeakAutoResume`, ausgeschaltet keine automatische Fortsetzung nach der Spitzenzeit;
|
|
177
|
-
- **Peak-Zeitzone**: immer Pekinger Zeit (`Asia/Shanghai`), identisch mit der Abrechnungsbasis von DeepSeek, unbeeinflusst von der `timezone`-Einstellung;
|
|
178
|
-
- Zustandsmaschine: einzelne Instanz `NORMAL ↔ PAUSED_PEAK` (`scheduler.js`), angetrieben von einem einzigen 30-s-Tick.
|
|
179
|
-
|
|
180
|
-
### Step-Gate (v0.2.0, der Schlüssel zur Token-Ersparnis)
|
|
181
|
-
|
|
182
|
-
Hängt an der `agent/pre-step`-Waterfall: die Runde wird angehalten, **bevor die Modellanfrage des nächsten Steps passiert**.
|
|
183
|
-
|
|
184
|
-
- **Haltebedingungen** (alle erforderlich): `enabled` + `stepLevelPause` + `step > 1` + Spitzenzeit (Pekinger Zeit, nicht Wochenende) + offizieller Ziel-Provider (`providerGuard`, bei „aus" werden alle blockiert) + Session nicht anfragegehalten + in diesem Peak-Fenster nicht manuell umgangen;
|
|
185
|
-
- **Warum `step > 1`**: der erste Step einer Runde wird vom Anfrage-Wächter abgedeckt, damit sich die beiden Gates nicht überlappen;
|
|
186
|
-
- **Freigabewege**: ① Button „⏸ pausiert (fortsetzen)" / `POST /session-guard/rpc {action:'stepResume'}` / `/resume` → lässt den aktuellen Step durch und **blockiert diese Session für den Rest der Spitzenzeit nicht mehr**; ② Spitzenende → alles freigeben, die Runde läuft an Ort und Stelle weiter (**kein followup nötig**); ③ Freeze-Button / `/pause` / `/cancel` → Gate freigeben und zur Runden-Pause übergehen; ④ `signal`-abort (Benutzerabbruch) → freigeben;
|
|
187
|
-
- **Timeout-Eskalation**: Haltung über `stepGateTimeoutMs` (Standard 5 Minuten) hinaus → Gate freigeben und **Eskalation zu einer force-Pause auf Rundenebene**, nach der Spitzenzeit einheitlich fortgesetzt (kein Stillstand, kein Token-Tropfen während der Spitzenzeit);
|
|
188
|
-
- **Zustand**: `GET /session-guard/state?session=<id>` liefert `paused: { step, turn }` und `stepGate: { held, since, bypass }`; das `paused` des Service-Ports `state()` **bleibt boolesch** (Rückwärtskompatibilität), der Step-Zustand steckt in `pausedStep`;
|
|
189
|
-
- **Keine Persistenz**: der Halt ist eine Promise im Prozess, ein Neustart verwirft sie (keine Geisterzustände).
|
|
190
|
-
|
|
191
|
-
#### Buttons „Session pausieren / Session fortsetzen" (von session-guard bereitgestellt)
|
|
192
|
-
|
|
193
|
-
Der Button „Session pausieren" rechts im Eingabebereich (slot `conversation.input.right`, id `session-guard-pause`, order 20, links vom input-traffic-Button „❄ Einfrieren & Anhang"):
|
|
194
|
-
|
|
195
|
-
- nicht pausiert → „Session pausieren", **klickbar**: der Klick ruft `stepPause` auf und pausiert die Session **vor der Modellanfrage des nächsten Steps** (der aktuelle Step wird nicht unterbrochen; auch Step 1 wird gehalten, unabhängig von Peak-Zeiten / Provider);
|
|
196
|
-
- pausiert → „Session fortsetzen", der Klick ruft `stepResume` auf: lässt den aktuellen Step durch und blockiert diese Session für den Rest der Spitzenzeit nicht mehr;
|
|
197
|
-
- **Event-Push**: `GET /session-guard/events?session=<id>` (SSE) pusht Step-Gate-Zustandsänderungen **sofort** — schließt die Spitzenzeit das Gate automatisch, wechselt der Button augenblicklich zu „Session fortsetzen", ohne auf einen Poll zu warten; zusätzlich dient ein 10-Sekunden-Poll von `/session-guard/state` als Sicherheitsnetz (konvergiert auch, wenn SSE nicht verfügbar / getrennt ist);
|
|
198
|
-
- Stil an den input-traffic-Buttons derselben Zeile ausgerichtet (24 px Höhe / 6 px Radius / 12 px Schrift / dieselben CSS-Tokens), mit Hover- und Pausen-Zustandsrückmeldung.
|
|
199
|
-
|
|
200
|
-
### Session-Sperre (Einfrieren)
|
|
201
|
-
|
|
202
|
-
- **Redundanter Port**: `ctx.provide('sessionGuard', service)` — `stopNextTurn(sessionId)` / `resume(sessionId)` / `lockQueue(sessionId)` / `unlockQueue(sessionId)` / `state(sessionId)`;
|
|
203
|
-
- **RPC-Brücke**: `POST /session-guard/rpc { action, sessionId }` — der input-traffic-Freeze-Button ruft `stopNextTurn` / `resume` **sessionweise** nach `sessionId` auf; wird still übersprungen, wenn dieses Plugin nicht installiert ist (fail-open D8);
|
|
204
|
-
- **Manuelle Befehle**: `/pause [force|safe] [stop|wait]`, `/resume [confirm] [rerun|skip]`, `/cancel` — wirken auf die aufrufende Session (`invocation.agent.id`).
|
|
205
|
-
|
|
206
|
-
### Automatische Backend-Wiederholung (D9)
|
|
207
|
-
|
|
208
|
-
Hört auf `turn/end` und klassifiziert Fehler:
|
|
209
|
-
|
|
210
|
-
- **Transiente Fehler** (error/429/max-tokens usw.) → automatische `followup(retryText)`-Fortsetzung mit adaptivem Backoff;
|
|
211
|
-
- **Permanente Fehler** (Authentifizierung/Guthaben/Modell/Kontextlimit) → Stopp;
|
|
212
|
-
- **Ausweichen während Freeze/Gate**: keine Wiederholung, solange `isFrozen(sessionId)` wahr ist (queueLocked / paused / taskControl paused);
|
|
213
|
-
- Benutzereingriff oder erfolgreiche Runde setzt den Zähler aufeinanderfolgender Fehler zurück.
|
|
214
|
-
|
|
215
|
-
### Status-Badge (Frontend-Anzeige)
|
|
216
|
-
|
|
217
|
-
Rechts im Eingabebereich erscheint ein **rein informatives** Status-Badge, das die aktuelle Phase in Echtzeit widerspiegelt:
|
|
218
|
-
|
|
219
|
-
| Phase | Badge-Text | CSS-Klasse | Bedeutung |
|
|
220
|
-
|---|---|---|---|
|
|
221
|
-
| `peak` (Prüfung an) | 高峰·拦官方 | `sg-peak` | Spitzenzeit, nur Anfragen an offizielle DeepSeek-Quellen werden blockiert |
|
|
222
|
-
| `peak` (Prüfung aus) | 高峰·全部暂停 | `sg-peak` | Spitzenzeit, alle Sessions pausiert |
|
|
223
|
-
| `off-peak` | 谷时 | `sg-off` | Außerhalb der Spitzenzeit, Sessions laufen normal |
|
|
224
|
-
| `weekend` | 周末 | `sg-weekend` | Wochenende (bei aktivem Wochenendmodus), ohne Peak-/Off-Peak-Rücksicht |
|
|
225
|
-
|
|
226
|
-
- **Polling**: alle 15 Sekunden wird `GET /session-guard/status` abgefragt, für globales `phase`, `providerGuard`, `held`, `deferred`, `stepHeld`;
|
|
227
|
-
- **fail-open**: Route nicht erreichbar, Netzwerkfehler oder `enabled` aus → Badge wird still ausgeblendet, keine Session betroffen;
|
|
228
|
-
- **Unabhängig von input-traffic**: das Badge rendert der session-guard-Client allein, es erscheint **ohne Installation des input-traffic-Plugins**. input-traffic liefert nur den Freeze-Button, keine Abhängigkeit zum Badge;
|
|
229
|
-
- **Tooltip**: beim Hover zeigt es `Phase · Zeitzone · Wochenendmodus · Prüfbasis · Anzahl gehaltener/verzögerter/Step-gehaltener`.
|
|
230
|
-
|
|
231
|
-
### Prüfbasis für offizielle Quellen (providerGuard)
|
|
232
|
-
|
|
233
|
-
In Spitzenzeiten werden Sessions nicht pauschal gestoppt: Zuerst wird geprüft, ob „die Route, die diese Anfrage wirklich nimmt, eine offizielle DeepSeek-Quelle ist":
|
|
234
|
-
|
|
235
|
-
| Priorität | Grundlage | `matchedBy` | Beispiel |
|
|
236
|
-
|---|---|---|---|
|
|
237
|
-
| 1 | explizite ID-Liste `officialProviders` | `explicit` | Benutzer deklariert ein selbst betriebenes Gateway als offiziell |
|
|
238
|
-
| 2 | Host der live normalisierten `baseURL` | `endpoint` | `deepseek-official` auf ein Relay umgestellt → **nicht blockiert** |
|
|
239
|
-
| 3 | Katalog-Standardendpunkt | `endpoint-default` | pi-ais `deepseek`-Route zielt standardmäßig auf die offizielle API → **blockiert** |
|
|
240
|
-
| 4 | eingebaute ID-Liste (`deepseek-official`) | `route-id` | Rückfallebene, wenn der Endpunkt nicht lesbar ist |
|
|
241
|
-
| 5 | alles andere | `unknown` | nicht offiziell, durchlassen |
|
|
242
|
-
|
|
243
|
-
- **Endpunkt schlägt ID**: eine Konfiguration namens `deepseek-official`, deren `baseURL` auf ein Relay zeigt, wird **nicht** fälschlich blockiert; umgekehrt entgeht pi-ais eingebaute `deepseek`-Route mit Standardendpunkt auf die offizielle API der Blockierung **nicht**.
|
|
244
|
-
- **Endpunktquelle**: `ctx.get('llm').listConfigurableProviders()` findet den Katalogeintrag → `ctx.settings.get(settingsNs)` liest die `baseURL` über `settingsPath` (nur nicht-sensible Felder, der Wert von `apiKeyEnv` wird nie gelesen). Bei jeder Anfrage neu berechnet, nie gecached → Provider-Konfigurationsänderungen greifen sofort.
|
|
245
|
-
- **Umschaltung weg von offiziell löst Fortsetzung aus**: wird eine beim Spitzenbeginn pausierte Session auf einen lokalen/Drittanbieter-Provider umgestellt (`model/selection`-Event ab 0.1.2) → automatische Fortsetzung dieser Session (unter dem Vorbehalt von `deferredResume`); nur Sessions, die dieses Plugin beim Spitzenbeginn pausiert hat, werden berührt, manuell per `/pause` pausierte **niemals**. Ohne dieses Event unter 0.1.1 → Rückfall auf „nächste Anfrage oder manuelles `/resume`".
|
|
246
|
-
- **Endpunkt nicht lesbar**: `llm`-Dienst fehlt, Namespace-Struktur geändert, Feld kein String — durchweg Degradierung zur ID-/eingebauten-Endpunkt-Prüfung mit protokolliertem `matchedBy`, **niemals wird eine Exception geworfen**.
|
|
247
|
-
- **Fehlurteil untersuchen**: `GET /session-guard/provider?provider=<id>` liefert `{ official, matchedBy, endpoint }`.
|
|
248
|
-
|
|
249
|
-
### Anfragegenauer Wächter und verzögerte Warteschlange
|
|
250
|
-
|
|
251
|
-
- **Warum Anfrageebene**: der 30-s-Tick verarbeitet beim Sprung `NORMAL → PAUSED_PEAK` nur die dann `running` Sessions; danach gestartete oder unterwegs auf offizielle Quellen umgeschaltete Sessions würden durchrutschen. Die `agent/request`-Waterfall läuft bei **jeder Anfrage**.
|
|
252
|
-
- **Die Prüfung nutzt den Rückgabewert von `next()`**: die Modellauswahl-Middleware überschreibt provider/model innerhalb der Waterfall mit den in der UI gewählten Werten, daher muss erst `await next()` abgewartet werden, bevor geprüft wird.
|
|
253
|
-
- **Hold-Modus (Standard)**: die Anfrage wird angehalten, **weder gesendet noch mit Fehler**, und exakt zum Spitzenende freigegeben (`msUntilOffPeak`-Präzisionszeitgeber, 30-s-Tick als Rückfallebene); ein Benutzerabbruch (abort) unterbricht normal.
|
|
254
|
-
- **Error-Modus**: wirft einen erkennbaren `PEAK_DEFERRED`-Fehler + Eintrag in die verzögerte Warteschlange, Fortsetzung nach der Spitzenzeit mit `deferredResumeText` (keine automatische Fortsetzung, wenn `deferredResume` aus ist).
|
|
255
|
-
- **Obergrenze**: läuft `deferredMaxHoldMs` (Standard 6 h) ab, ohne dass die Spitzenzeit endet → Umwandlung in error, um endloses Anhalten zu vermeiden.
|
|
256
|
-
- **Eiserne Ausschließungsregel**: während eines Holds wird **nie** zusätzlich das Session-Gate zum Pausieren aufgerufen (eine Pause wartet auf die sichere Grenze, die eine gehaltene Anfrage nie erreicht → beide warten aufeinander). Beim Spitzenbeginn werden bereits gehaltene Sessions übersprungen.
|
|
257
|
-
- **Keine Persistenz**: die verzögerte Warteschlange ist eine In-Prozess-Promise, ein Neustart löscht sie.
|
|
258
|
-
|
|
259
|
-
### Grenzen (ausdrücklich nicht im Umfang)
|
|
260
|
-
|
|
261
|
-
- **Kein Provider-Wechsel / kein Umleiten**: nur blockieren, nicht routen;
|
|
262
|
-
- **Compaction läuft nicht durch `agent/request`**: während eine Session pausiert ist, findet sie nicht statt; eine manuell ausgelöste Komprimierung während der Spitzenzeit kann weiterhin die offizielle Quelle treffen (dieses Plugin greift nicht in die `ctx.llm.stream`-Schicht ein);
|
|
263
|
-
- **0.1.1 hat kein `model/selection`-Event**: die automatische Fortsetzung nach Umschaltung auf eine nicht-offizielle Quelle fällt auf „nächste Anfrage oder manuelles `/resume`" zurück (ab 0.1.2 sofort);
|
|
264
|
-
- **Keine neuen npm-Abhängigkeiten**, kein Lesen/Schreiben von Zugangsdaten, keine Berührung der 429-/Transport-Retries von `dsh-llm-retry`.
|
|
265
|
-
|
|
266
|
-
### Zeitzonenbehandlung und -validierung
|
|
267
|
-
|
|
268
|
-
- Die Zeitzonenerkennung basiert auf **IANA-Zeitzonennamen** (z. B. `Asia/Shanghai`, `Asia/Tokyo`, `Asia/Seoul`), projiziert über `Intl.DateTimeFormat` auf die Wanduhr der konfigurierten Zeitzone, **ohne nacktes `getUTCDay()`** — vermeidet den klassischen 8-Stunden-Grenzfehler der UTC+8-Zone Pekings (Samstag 00:30 Pekinger Zeit ist in UTC noch Freitag);
|
|
269
|
-
- `Intl.DateTimeFormat` ist selbst die Validierungsschicht: ein ungültiger Zeitzonenname (z. B. `Foo/Bar`) wirft eine `RangeError`, die von einem äußeren try-catch still auf die Standardzeitzone `Asia/Shanghai` zurückfällt (fail-open);
|
|
270
|
-
- Peak-Fenster sind **links geschlossen, rechts offen** `[start, end)`, mit Unterstützung für Fenster über Mitternacht (z. B. `22:00–06:00`);
|
|
271
|
-
- Die `timezone`-Einstellung verhält sich in allen Sprachen (zh/en/ja/ko) identisch — die IANA-Zeitzonennamen von `Intl.DateTimeFormat` sind locale-unabhängig, das Zeitzonenverhalten unter japanischer/koreanischer Oberfläche ist exakt wie im Chinesischen.
|
|
272
|
-
|
|
273
|
-
### Arbeitsteilung mit input-traffic: das eine „stoppt", das andere „reiht"
|
|
274
|
-
|
|
275
|
-
Beide wirken auf **unterschiedlichen Gliedern derselben Kette**, die Grenze zieht DSHs eigenes Inbox-Modell:
|
|
276
|
-
|
|
277
|
-
```
|
|
278
|
-
Benutzereingabe ──(input-traffic wählt die Stufe)──▶ next-step / next-turn Warteschlangen
|
|
279
|
-
│
|
|
280
|
-
agent/pre-step ──(Step-Gate dieses Plugins)──▶ durchlassen / anhalten
|
|
281
|
-
│
|
|
282
|
-
agent/request ──(Anfrage-Hold dieses Plugins)──▶ durchlassen / anhalten
|
|
283
|
-
│
|
|
284
|
-
Modellanruf
|
|
285
|
-
```
|
|
286
|
-
|
|
287
|
-
**DSHs Warteschlangen-Semantik (zwei Warteschlangen, nicht verwechseln)**
|
|
288
|
-
|
|
289
|
-
| Warteschlange | Bedeutung | Verbrauch |
|
|
290
|
-
|---|---|---|
|
|
291
|
-
| `next-step` | „Eingabe, die auf die nächste Step-Grenze wartet" | Am nächsten `agent/pre-step`: **auf gleicher Ebene wie ein Tool-Ergebnis**, ein weiterer Step in derselben Runde |
|
|
292
|
-
| `next-turn` | „Prompt, der auf eine eigene Runde wartet" | Nach Abschluss der aktuellen Runde, gestartet als **neue Runde** |
|
|
293
|
-
|
|
294
|
-
`Inbox.claim()` **räumt immer zuerst `next-step` ab** und nimmt nur dann zusätzlich **1** `next-turn`, wenn die Grenze eine neue Runde eröffnet; der erste Step einer Runde liest next-turn, alle folgenden lesen next-step.
|
|
295
|
-
|
|
296
|
-
**Zuständigkeiten**
|
|
297
|
-
|
|
298
|
-
- **session-guard = stoppen**: entscheidet nur, „wann fortgeschritten werden darf", **fasst Inhalt und Reihenfolge der Warteschlangen nie an**.
|
|
299
|
-
- Step-Gate (`agent/pre-step`): hält **vor** der Modellanfrage des nächsten Steps;
|
|
300
|
-
- Runden-Pause (`agent.cancel({keepInbox:true})` + `goals.pause` + sichere Grenze): stoppt die aktuelle Runde, **Warteschlangen bleiben unverändert**;
|
|
301
|
-
- Anfragegenauer Wächter (Hold auf `agent/request`): hält **genau diese eine Modellanfrage** an.
|
|
302
|
-
- **input-traffic = reihen**: entscheidet nur, „in welche Warteschlange die Benutzereingabe kommt, in welcher Stufe, und wann sie verbraucht wird".
|
|
303
|
-
- drei Stufen = welche Warteschlange: rot „unterbrechen" ruft erst `cancel()` auf, dann `steer`; gelb „einfügen" ruft `steer` auf (→ `next-step`, der nächste Step derselben Runde); grün „einreihen" bleibt in `next-turn`;
|
|
304
|
-
- Einfrieren = alle `queued` + `steering`-Zeilen herauslösen (Stufen bleiben erhalten) + Composer-Sperre + Aufruf von `sessionGuard.stopNextTurn`; Fortsetzen = Sperre aufheben → zuerst `sessionGuard.resume` → erneute Einreichung nach Stufe.
|
|
305
|
-
|
|
306
|
-
**Zwei eiserne Regeln am Treffpunkt**
|
|
307
|
-
|
|
308
|
-
1. **Einfrieren muss dieses Plugin zuerst die Step-Gate freigeben lassen**: das Step-Gate hängt an `agent/pre-step`, während die Runden-Pause auf ein Ereignis an der sicheren Grenze wartet — beide würden aufeinander warten (`pauseTask` / `cancelTask` dieses Plugins machen zuerst `release`);
|
|
309
|
-
2. **Beim Step-Gate-Halt sind die Nachrichten bereits entnommen**: `preStep()` macht `inbox.claim()`, bevor es die Waterfall dispatcht, neue Eingaben reihen sich also hinter dem bereits entnommenen Stapel an; `keepInbox` gilt nur für die Runden-Pause.
|
|
310
|
-
|
|
311
|
-
**Kein gegenseitiges Übergreifen**: input-traffic hört weder `agent/pre-step` noch `agent/request` (einzige Ausnahme ist das explizite `cancel()` der Stufe „unterbrechen", das vom Benutzer verlangt wurde); dieses Plugin schreibt niemals Inhalt oder Reihenfolge von `next-step` / `next-turn` um.
|
|
312
|
-
|
|
313
|
-
Zu den Buttons: die Buttons „Session pausieren / Session fortsetzen" dieses Plugins (order 20) und die input-traffic-Buttons „❄ Einfrieren & Anhang / Fortsetzen & Anhang" (order 30) stehen nebeneinander und ersetzen sich nicht — erstere steuern das Step-Gate, letztere das Herauslösen aus der Warteschlange + das Einfrieren auf Rundenebene.
|
|
314
|
-
|
|
315
|
-
## Redundanter Port `sessionGuard`
|
|
316
|
-
|
|
317
|
-
```js
|
|
318
|
-
{
|
|
319
|
-
stopNextTurn(sessionId, opts), // stoppt die nächste Runde der Session (eigenes Session-Gate / Rückfall Lock-Warteschlange)
|
|
320
|
-
resume(sessionId, opts), // fortsetzen (confirm + choice: rerun|skip)
|
|
321
|
-
lockQueue(sessionId, reason), // Warteschlange explizit sperren
|
|
322
|
-
unlockQueue(sessionId), // Warteschlange explizit entsperren
|
|
323
|
-
stepPause(sessionId), // manuell eine Step-Pause anfordern (Gate schließt an der nächsten pre-step-Grenze, Step 1 inklusive)
|
|
324
|
-
stepResume(sessionId, opts), // Step-Gate öffnen (v0.2.0); mit opts.bypass=false kein Peak-Umgehen
|
|
325
|
-
state(sessionId), // { queueLocked, lockReason, paused, pausedStep, stepHeldSince, stepBypass, taskControlAvailable, taskControl }
|
|
326
|
-
}
|
|
327
|
-
```
|
|
328
|
-
|
|
329
|
-
## HTTP-Routen
|
|
330
|
-
|
|
331
|
-
- `GET /session-guard/state?session=<id>` — Session-Zustand (mit `paused: { step, turn, manual }` / `stepGate` / letztes Ziel / gehalten? / verzögert?)
|
|
332
|
-
- `GET /session-guard/events?session=<id>` — **SSE**: sofortiger Push von Step-Gate-Zustandsänderungen (der Button aktualisiert sich danach)
|
|
333
|
-
- `GET /session-guard/settings` — Einstellungen + taskControl-Verfügbarkeit
|
|
334
|
-
- `GET /session-guard/status` — aktuelle globale Phase (Polling des Status-Badges; enthält `stepHeld`)
|
|
335
|
-
- `GET /session-guard/provider?provider=<id>` — Diagnose des Offiziell-Quellen-Urteils (`official` / `matchedBy` / `endpoint`)
|
|
336
|
-
- `GET /session-guard/diag` — Laufzeitdiagnose (enthält `stepGate`)
|
|
337
|
-
- `POST /session-guard/rpc` — `{ action: stopNextTurn|resume|lockQueue|unlockQueue|stepPause|stepResume|state, sessionId }`
|
|
338
|
-
|
|
339
|
-
## Zustandsspeicher
|
|
340
|
-
|
|
341
|
-
JSON pro Session: `$DSH_HOME/.dsh/session-guard/<sessionId>.json` (atomares Schreiben; überschreibbar mit `DSH_SESSION_GUARD_STATE_DIR`).
|
|
342
|
-
|
|
343
|
-
## Tests
|
|
344
|
-
|
|
345
|
-
```bash
|
|
346
|
-
npm test # node --test tests/*.test.mjs (Zeitzonen/Wochenende/Zustandsmaschine/Session-Gate/Brücke/Retry)
|
|
347
|
-
```
|
|
348
|
-
|
|
349
|
-
## Module
|
|
350
|
-
|
|
351
|
-
| Datei | Verantwortung |
|
|
352
|
-
|---|---|
|
|
353
|
-
| `src/time.js` | Peak-/Wochenenderkennung (zeitzonengenau) + `msUntilOffPeak` (präzises Timing des Spitzenendes) |
|
|
354
|
-
| `src/scheduler.js` | Reine Zustandsmaschine NORMAL ↔ PAUSED_PEAK |
|
|
355
|
-
| `src/provider.js` | Fünfstufige Prüfung offizieller Quellen (pure Funktion: Endpunkt-Normalisierung + Entscheidungsmatrix) |
|
|
356
|
-
| `src/provider-directory.js` | Endpunktverzeichnis (`llm.listConfigurableProviders` + `settings.get`, Degradierung auf der ganzen Kette) |
|
|
357
|
-
| `src/deferrals.js` | Register der Verzögerungen (Halten / Freigabe / Limitüberschreitung / `PeakDeferredError`) |
|
|
358
|
-
| `src/request-guard.js` | Anfragegenauer Wächter auf `agent/request` (zwei Modi hold / error) |
|
|
359
|
-
| `src/step-gate.js` | **Step-Gate auf `agent/pre-step`** (v0.2.0: Schließen / Freigeben / Timeout-Eskalation / Bypass, das reine `decideStepHold` ist unit-testbar) |
|
|
360
|
-
| `src/targets.js` | Nachverfolgung des „letzten echten Ziels" einer Session (`request/header` + `model/selection`) |
|
|
361
|
-
| `src/wiring.js` | Verdrahtungs-Orchestrierung (Spitzenbeginn-Filter / Step-Gate-Verdrahtung / Freigabe nach der Spitzenzeit / präziser Zeitgeber / Aufräumen beim Entladen) |
|
|
362
|
-
| `src/pause-gate.js` | Engine des eigenen Session-Gates (agent.cancel keepInbox + goals.pause + sichere Grenze + followup-Fortsetzung; gibt vor dem Pausieren zuerst das Step-Gate frei) |
|
|
363
|
-
| `src/pause-store.js` | Persistenz des eigenen Pausenzustands |
|
|
364
|
-
| `src/gate.js` | Treiber des Session-Gates (echte eigene Pause / Rückfall Lock-Warteschlange, fail-open) |
|
|
365
|
-
| `src/bridge.js` | Redundanter Port `sessionGuard` |
|
|
366
|
-
| `src/retry.js` | Automatische Backend-Wiederholung (Fehlerklassifikation/Backoff/Ausweichen bei Freeze; Kurzschluss nur beim exakten Code `PEAK_DEFERRED`) |
|
|
367
|
-
| `src/detect.js` | Automatische Erkennung (Host-taskControl / Client-input-traffic-Brücke) |
|
|
368
|
-
| `src/store.js` | Persistenter Zustand pro Session |
|
|
369
|
-
| `src/settings.js` | Einstellungsunterbereich (schemastery-Schema + fail-open-Registrierung) |
|
|
370
|
-
| `src/index.js` | Host-apply (Einstellungen/Routen/Tick/Dienst bereitstellen/Retry-Verdrahtung/Anfrage-Wächter) |
|
|
371
|
-
| `src/client/` | Browser-Hälfte (**Session-Pausieren-Button** + Status-Badge + Einstellungskarte) |
|
|
372
|
-
|
|
373
|
-
## Lizenz
|
|
374
|
-
|
|
375
|
-
MIT — siehe [LICENSE](LICENSE).
|
|
1
|
+
<p align="center">
|
|
2
|
+
<strong>Automatisches Session-Gate zu Spitzenzeiten: Wochenendmodus + automatische Spitzenzeiten-Pause + zweidimensionale Prüfung offizieller Quellen + Session-Freeze + automatische Backend-Wiederholung</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> · <strong>Deutsch</strong> · <a href="README.it.md">Italiano</a> · <a href="README.ru.md">Русский</a> · <a href="README.es.md">Español</a>
|
|
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 auf Russisch](./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 auf Russisch](./CHANGELOG.ru.md)
|
|
42
|
+
- [Changelog en español](./CHANGELOG.es.md)
|
|
43
|
+
|
|
44
|
+
> **Kompatibilitätshinweis:** v0.1.1 enthält bereits japanische (`ja`) und koreanische (`ko`) Wörterbücher, aber die aktuellen offiziellen DSH-Versionen stellen über `LocaleRuntime` nur `zh` und `en` bereit. In einem unveränderten DSH schlägt die Wahl von `ja` oder `ko` mit `locale "<id>" is not registered` fehl. Es muss abgewartet werden, bis das offizielle DSH die entsprechenden Locale-IDs ergänzt. Fortgeschrittene Benutzer können einen DSH-Fork pflegen, um dies zu erweitern.
|
|
45
|
+
|
|
46
|
+
> **▼ DSH-Versionskompatibilität**
|
|
47
|
+
>
|
|
48
|
+
> | DSH-Version | Laden | Einstellungsregistrierung | Session-Events / Gate | Client-Seite |
|
|
49
|
+
> | --- | --- | --- | --- | --- |
|
|
50
|
+
> | 0.1.0-rc.7 ~ 0.1.1-rc.x | ➖ nicht in dieser Linie (historische Versionen vor `legacy/0.1.2`) | `ctx.settings.register(ns, schema, { base })` | ✅ gleiche Form | ✅ keine Plattformwert-Imports |
|
|
51
|
+
> | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ➖ nicht in dieser Linie → `legacy/0.1.2` verwenden (npm `@dsh-0.1.2`) | `register` bleibt erhalten (zusätzlich `installSection`) | ✅ gleiche Form | ✅ keine Plattformwert-Imports |
|
|
52
|
+
> | **0.2.0-rc.1+** | ✅ (**diese Linie**, dist-tag `dsh-0.2.0`, npm `4.0.0`) | deklarativ: `.volatile()`-Felder des Config werden vom Host zu Formularen projiziert, `register` entfernt | ✅ Events über den Dual-Pfad `snapshotEvents()` gelesen | ✅ Client-Einstellungskarte auf `configForms` umgestellt |
|
|
53
|
+
> | 0.1.7-rc.1+ | ✅ (Zweig `compat/0.1.7`, dist-tag `dsh-0.1.7`) | deklarativ: `.volatile()`-Felder des Config werden vom Host zu Formularen projiziert, `register` entfernt | ✅ Events über den Dual-Pfad `snapshotEvents()` gelesen | ✅ Client-Einstellungskarte auf `configForms` umgestellt |
|
|
54
|
+
> | 0.1.5-rc.2 | ✅ (Zweig `compat/0.1.5`, dist-tag `dsh-0.1.5`) | `register` weiterhin vorhanden (String-Namespaces) | ✅ Events über den Dual-Pfad `snapshotEvents()` gelesen | ✅ |
|
|
55
|
+
>
|
|
56
|
+
> **Identität dieser Linie**: Zweig `compat/0.2.0`, npm-Versionsnummer **`4.0.0`** (semver), dist-tag **`dsh-0.2.0`**.
|
|
57
|
+
> Die `engines.dsh` in `package.json` und `dsh.plugin.json` sowie die vier `@deepseek-ai/dsh-client-*`-Peers
|
|
58
|
+
> sind einheitlich auf `>=0.2.0-rc.1 <0.2.1-0` gesetzt; die Untergrenze des `@deepseek-ai/cordis`-Peers entspricht dem `^4.0.4` der Host-Linie.
|
|
59
|
+
> Früher benutzte das README „2.x / 3.x" als **Liniencoden** — das war eine Erzählkonvention, **keine aus der Registry
|
|
60
|
+
> pullbare Versionsnummer** — maßgeblich sind ausschließlich die npm-Versionen `0.3.1` (0.1.2-Linie), `3.0.0`/`3.0.1` (0.1.5-Linie),
|
|
61
|
+
> `3.2.4` (0.1.7-Linie) und `4.0.0` (0.2.0-Linie).
|
|
62
|
+
>
|
|
63
|
+
> **0.2.0-Anpassung (Zweig compat/0.2.0, npm 4.0.0)**:
|
|
64
|
+
> Die von diesem Plugin genutzte Plugin-API (Manifest/Settings/HMR/Slot/Session V4) ist in 0.2.0-rc.1 vollständig kompatibel mit 0.1.7;
|
|
65
|
+
> die Anpassung ist ein reiner Metadaten-Generationswechsel (Peer/Engines/dist-tag/Version 4.0.0). Mit der Basis gemeinsam übernommene
|
|
66
|
+
> Engineering-Fixes:
|
|
67
|
+
> ① `src/client/family-section.tsx` ist in die Versionskontrolle aufgenommen (vorher nur im lokalen Arbeitsbereich vorhanden, ein sauberer Checkout konnte nicht bauen);
|
|
68
|
+
> ② die eslint-Konfiguration verzichtet auf das Core-`no-unused-vars`, das TS-Typsignatur-Parameter fälschlich meldete — die Lint-Baseline ist wieder komplett grün;
|
|
69
|
+
> ③ die statischen v4-Gates für die Signaturen der Schreibpfade (`tests/source-kind.test.mjs`) kommen mit der Basis in diese Linie;
|
|
70
|
+
> ④ der `@deepseek-ai/cordis`-Peer ist auf `^4.0.4` ausgerichtet (die Host-UI-Pakete von 0.2.0-rc.1 deklarieren `~4.0.4`; das frühere `^4.0.1` kollidierte nicht, lag aber mit seiner Untergrenze zu tief).
|
|
71
|
+
>
|
|
72
|
+
> **Verbleib anderer Linien**: DSH-`0.1.2-rc.x`-Hosts verwenden bitte den Zweig **`legacy/0.1.2`** (npm-dist-tag
|
|
73
|
+
> `dsh-0.1.2`, Version `0.3.1`). **`main` ist bei `0.2.0-beta.1` eingefroren und ist nicht der Release-Zweig der 0.1.2-Linie.**
|
|
74
|
+
> DSH-`0.1.0-rc.7` ~ `0.1.1-rc.x`-Hosts verwenden bitte historische Versionen ≤ `0.1.2`.
|
|
75
|
+
> Feldursprungs-Definitionen siehe
|
|
76
|
+
> `mine-dsh-plugins/improve-dsh-plugins/DSH-PLUGIN-VERSION-DISTRIBUTION-STRATEGY.md` §2.2.
|
|
77
|
+
>
|
|
78
|
+
> **0.1.5-Anpassung (Zweig compat/0.1.5, npm 3.0.0)**:
|
|
79
|
+
> ① Dual-Pfad für `agent.followup` — 0.1.5 macht die Inbox zu einer schreibgeschützten Projektion der Agent-Loop; existiert `followup`
|
|
80
|
+
> nicht mehr, wird auf `agent.send` zurückgefallen, ist beides nicht vorhanden, folgt eine Warnung statt eines Fehlers (Absicherung für die Fortsetzung);
|
|
81
|
+
> ② Fortsetzungsnachrichten erhalten `source` mit `form: 'instructions'` (0.1.5-ContextFormed-Vertrag, ältere Versionen ignorieren das);
|
|
82
|
+
> ③ `webServer.register` ist in try/catch gepackt; eine fehlgeschlagene Registrierung wird nur geloggt und bringt das Host-Plugin-Loading nicht zum Absturz;
|
|
83
|
+
> ④ 0.1.5 entfernt den Array-Accessor `session.events`; Events werden über `snapshotEvents()` als Hauptpfad +
|
|
84
|
+
> Rückfall auf das alte Array gelesen (betrifft nur die beiden Hilfswege `findToolOutcome` / `lastUserPrompt`, das Event-Typ-Matching bleibt unverändert).
|
|
85
|
+
> Verifiziert: Der `WebRoute`-Vertrag (exact/prefix + SSE) von 0.1.5 ist unverändert; der Client-
|
|
86
|
+
> `fetch('/session-guard/...')` braucht kein `/api`-Präfix.
|
|
87
|
+
> Die folgende Kompatibilitätstabelle überspannt zwei API-Generationen (0.1.1 / 0.1.2); diese Linie beansprucht nur die 0.1.5-Zeile.
|
|
88
|
+
> `session/event`, `agent.cancel`, `goals.pause`,
|
|
89
|
+
> `agent.followup`, `commands.register`, `timer.interval`, `webServer.register`,
|
|
90
|
+
> `agent/request`, `llm.listConfigurableProviders`, `settings.register/get` haben zwischen
|
|
91
|
+
> `dsh-v0.1.1-rc.2` und `dsh-v0.1.2-rc.1` identische Signaturen (diese Linie ist bis `dsh-v0.1.5-rc.2` verifiziert);
|
|
92
|
+
> einzig die Form der von `tool/result` aufgezeichneten Call-ID erfordert eine Doppellektüre (`content[].toolCallId` zuerst,
|
|
93
|
+
> `source.callId` als Rückfallebene), ausgelagert in `src/tool-call-id.js` mit Unit-Tests — in Replay-Logs beider Versionen können beide Formen auftreten.
|
|
94
|
+
> Ab 0.1.5 ist der Array-Accessor `session.events` entfernt; diese Linie liest über `snapshotEvents()` (alter Array als Rückfallebene erhalten),
|
|
95
|
+
> was nur die beiden Hilfswege `findToolOutcome` / `lastUserPrompt` betrifft.
|
|
96
|
+
> Das `model/selection`-Event gibt es **nur ab 0.1.2**, dient nur der Modellwechsel-Beschleunigung und muss per Feature-Erkennung abgefragt werden; die Einstellungsoberfläche nutzt nur
|
|
97
|
+
> die `register` + `get`-Schnittmenge (niemals `installSection` / das entfernte `installSettingsSection`).
|
|
98
|
+
> Drift-Wächter-Skript: `tools/check-api-drift.ps1` (diese Linie prüft standardmäßig gegen `dsh-v0.1.5-rc.2`, ob die benötigten Schnittstellen existieren).
|
|
99
|
+
|
|
100
|
+
> Pausiert laufende Sessions automatisch während der Spitzenzeiten, setzt sie automatisch außerhalb der Spitzenzeiten/am Wochenende fort; zusammen mit dem Freeze-Button von input-traffic für **Session-weise** Sperren; die **automatische Backend-Wiederholung** weicht während Freeze/Gate aus. Der Kern basiert auf einem **eigenen Session-Gate** (`agent.cancel keepInbox + goals.pause + session/event sichere Grenze + followup-Fortsetzung`) und hängt nicht mehr von dsh-task-control ab.
|
|
101
|
+
|
|
102
|
+
Ein cordis-Plugin, zusammengesetzt über den `dsh plugin`-Befehl und einen Bundle-Patch — ohne dsh-Quellcode-Änderungen, ohne PR.
|
|
103
|
+
|
|
104
|
+
> 💡 **Warum empfohlen**: DeepSeek hat am 2026-08-17 die **Peak-/Off-Peak-Abrechnung** eingeführt — in Spitzenzeiten (Pekinger Zeit 9:00-12:00, 14:00-18:00) kostet die Einheit das **Doppelte** der Schwachlastzeit (einschließlich Mittag, Nacht, Wochenende und Feiertage). Dieses Plugin pausiert laufende Sessions während der Spitzenzeiten automatisch und setzt sie außerhalb automatisch fort; zeitversetzte Langläufe sparen bis zu **50 %**; manuelles Einfrieren (mit dem input-traffic-Button) erlaubt zusätzlich eine präzise, sessiongenaue Steuerung.
|
|
105
|
+
|
|
106
|
+
## Funktionsübersicht
|
|
107
|
+
|
|
108
|
+
- **Wochenendmodus**: erkennt Wochenenden (zeitzonengenau via `Intl.DateTimeFormat`, ohne den 8-Stunden-Grenzfehler des nackten `getUTCDay()` bei der Pekinger Zeitzone) → am Wochenende gelten keine Peak-/Off-Peak-Zeiten, freies Laufen.
|
|
109
|
+
- **Automatische Spitzenzeiten-Pause (global)**: beim Eintritt in die Spitzenzeit (und nicht am Wochenende) werden alle laufenden Root-Sessions automatisch pausiert; nach der Spitzenzeit werden alle automatisch fortgesetzt — **globaler Schalter, keine manuelle Arbeit**.
|
|
110
|
+
- **Zweidimensionale Prüfung offizieller Quellen (providerGuard)**: in Spitzenzeiten wird **nur blockiert, wenn das Ziel der Anfrage eine offizielle DeepSeek-Quelle ist**; mit lokalen/Drittanbieter-Providern (z. B. `local-35b`) läuft alles normal, unberührt vom Peak-Gate. Prüfreihenfolge = explizite ID-Liste → `baseURL`-Endpunkt → Katalog-Standardendpunkt → eingebaute ID.
|
|
111
|
+
- **Anfragegenaues Sicherheitsnetz + verzögerte Warteschlange**: Sessions, die nach dem Spitzenbeginn starten oder unterwegs auf eine offizielle Quelle umgeschaltet werden, greift der Anfrage-Wächter `agent/request` auf (Standard `hold`: die Anfrage wird ohne Fehler angehalten und nach der Spitzenzeit automatisch freigegeben).
|
|
112
|
+
- **Session-weise Einfrieren / Fortsetzen**: redundanter Port `sessionGuard` + `POST /session-guard/rpc`, sessiongenau angeschlossen über den input-traffic-Freeze-Button; zusätzlich die manuellen Befehle `/pause /resume /cancel`.
|
|
113
|
+
- **Automatische Backend-Wiederholung (D9)**: transiente turn/end-Fehler (error/429/max-tokens) werden mit adaptivem Backoff automatisch fortgesetzt; permanente Fehler stoppen; **weicht während Freeze/Gate aus**, umgeht nie das Session-Gate.
|
|
114
|
+
- **fail-open**: eigenes Session-Gate nicht verfügbar, session-guard nicht installiert, Settings-Dienst fehlt — in allen Fällen stille Degradierung, niemals ein Absturz wegen einer Abhängigkeit.
|
|
115
|
+
|
|
116
|
+
## Vorschau der Oberfläche
|
|
117
|
+
|
|
118
|
+
Echter Lauf-Screenshot (Windows, dsh web) — aktivierter Wochenendmodus:
|
|
119
|
+
|
|
120
|
+
<figure>
|
|
121
|
+
<img style="max-width:100%" alt="Statusleiste im Eingabebereich: der aktivierte „Wochenende"-Button ist hervorgehoben (bei aktivem Wochenendmodus laufen Sessions frei, ohne Peak-/Off-Peak-Rücksicht), daneben der Button „Session einfrieren" (mit input-traffic), die DeepSeek-V4-Flash-Denkstufe und Sendesteuerelemente, unten Statusleisten mit Runden/Schritten, LLM-Zeit, Cache-Trefferquote u. a." src="assets/高峰低峰周末提醒-周末状态.png" />
|
|
122
|
+
<figcaption>Wochenendmodus aktiv: das „Wochenende"-Badge im Eingabebereich ist hervorgehoben, neben „Session einfrieren"; am Wochenende gibt es keine Peak-/Off-Peak-Zeiten, Sessions laufen frei.</figcaption>
|
|
123
|
+
</figure>
|
|
124
|
+
|
|
125
|
+
## Installation
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
# DSH-0.1.5-rc.x-Hosts (diese Linie, dist-tag dsh-0.1.5)
|
|
129
|
+
dsh plugin --profile web add dsh-session-guard@dsh-0.1.5
|
|
130
|
+
|
|
131
|
+
# oder direkt über den git-Zweig
|
|
132
|
+
dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
|
|
133
|
+
|
|
134
|
+
# DSH-0.1.2-rc.x-Hosts müssen die 0.1.2-Linie verwenden
|
|
135
|
+
dsh plugin --profile web add dsh-session-guard@dsh-0.1.2
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`compat/0.1.5` ist die DSH-`0.1.5-rc.x`-Speziallinie, npm-Versionsnummer **`3.0.0`**; DSH-`0.1.2-rc.x`-Hosts verwenden bitte den
|
|
139
|
+
Zweig **`legacy/0.1.2`** (npm-dist-tag `dsh-0.1.2`, Version `0.3.1`). **`main` ist bei `0.2.0-beta.1` eingefroren,
|
|
140
|
+
nicht der Release-Zweig der 0.1.2-Linie.**
|
|
141
|
+
|
|
142
|
+
> ⚠️ Verlassen Sie sich nicht auf den nackten Paketnamen `dsh-session-guard`: npm's `latest`-Tag kann zwei sich gegenseitig ausschließende
|
|
143
|
+
> Versionslinien nicht gleichzeitig bedienen (ihre `engines.dsh`-Bereiche sind nach den semver-Prerelease-Regeln exklusiv) — der dist-tag muss explizit angegeben werden.
|
|
144
|
+
|
|
145
|
+
Nach der Installation dsh web neu starten und die Seite neu laden.
|
|
146
|
+
|
|
147
|
+
## Einstellungen (Einstellungen → Plugins → session-guard, einfache Schalter)
|
|
148
|
+
|
|
149
|
+
| Schalter | Standard | Beschreibung |
|
|
150
|
+
|---|---|---|
|
|
151
|
+
| `enabled` | on | **Automatische Pausierung/Einfrieren von Sessions in Spitzenzeiten**: pausiert laufende Sessions während der Spitzenzeiten automatisch |
|
|
152
|
+
| `stepLevelPause` | on | **Gate auf Step-Ebene**: in Spitzenzeiten wird das Gate **vor** der Modellanfrage des nächsten Steps geschlossen (früher und sparsamer als eine Pausierung auf Rundenebene); aus = Rückfall auf Runden-Pause |
|
|
153
|
+
| `providerGuard` | on | **Zweidimensionale Prüfung offizieller Quellen**: in Spitzenzeiten werden nur offizielle DeepSeek-Quellen blockiert, lokale/Drittanbieter-Provider laufen normal |
|
|
154
|
+
| `guardSubagents` | on | **Subagenten-Anfragen einbeziehen**: auch Subagenten-Anfragen werden abgerechnet, standardmäßig mit blockiert |
|
|
155
|
+
| `offPeakAutoResume` | on | **Automatische Fortsetzung in Schwachlastzeiten**: pausierte Sessions werden außerhalb der Spitzenzeiten automatisch fortgesetzt; aus = keine automatische Fortsetzung nach der Spitzenzeit (manuell nötig) |
|
|
156
|
+
| `weekendMode` | on | **Wochenendmodus**: erkennt Wochenenden → am Wochenende keine automatische Pausierung (am Wochenende gibt es keine Spitzenzeit, freies Laufen) |
|
|
157
|
+
| `deferredResume` | on | **Automatische Fortsetzung nach der Spitzenzeit**: aus = verzögerte Anfragen/Sessions laufen nicht automatisch weiter, `/resume` nötig |
|
|
158
|
+
| `queueFallback` | on | Rückfall auf die Lock-Wait-Warteschlange, wenn das eigene Session-Gate nicht verfügbar ist (fail-open) |
|
|
159
|
+
| `retryEnabled` | off | **Automatische Wiederholung (Backend)**: transiente Fehler werden automatisch fortgesetzt (Standard aus, konservativ) |
|
|
160
|
+
|
|
161
|
+
Zusätzliche Konfiguration:
|
|
162
|
+
|
|
163
|
+
- `timezone` (Standard Asia/Shanghai) — Zeitzone für die **Wochenenderkennung** und die Badge-Anzeige; **beeinflusst die Peak-/Off-Peak-Erkennung nicht** (immer Pekinger Zeit);
|
|
164
|
+
- `peakWindows` (Standard 09:00–12:00 / 14:00–18:00) — Peak-/Off-Peak-Fenster in Pekinger Zeit (UTC+8), identisch mit DeepSeeks offizieller Abrechnung;
|
|
165
|
+
- `pauseMode` (`safe`/`force`), `pauseReason` (`wait`/`stop`) — Art der Pausierung;
|
|
166
|
+
- `stepGateTimeoutMs` (Standard 300000) — Halte-Timeout des Step-Gates; bei Ablauf wird das Gate freigegeben und zu einer **Pausierung auf Rundenebene eskaliert** (Anti-Deadlock, kein „alle 5 Minuten ein Step"-Token-Tropf);
|
|
167
|
+
- Prüfung offizieller Quellen: `officialProviders` (zusätzliche offizielle Provider-IDs, kommasepariert, höchste Priorität), `officialBaseURLs` (Liste offizieller Endpoint-Hosts, Standard `api.deepseek.com`);
|
|
168
|
+
- Verzögerte Warteschlange: `deferredMode` (`hold` anhalten / `error` Fehler und verzögern), `deferredResumeText` (Text der Fortsetzung nach der Spitzenzeit), `deferredMaxHoldMs` (Haltelimit, Standard 6 h, bei Ablauf Umwandlung in error);
|
|
169
|
+
- Retry-Parameter: `retryText`, `retryGraceMs`, `retryCooldownMs`, `retryBackoffFactor`, `retryBackoffMaxMs`, `retryMaxConsecutive`.
|
|
170
|
+
|
|
171
|
+
## Verhalten
|
|
172
|
+
|
|
173
|
+
### Automatisches Peak-Gate (global)
|
|
174
|
+
|
|
175
|
+
- **Spitzenbeginn** (und nicht Wochenende): mit aktiviertem `stepLevelPause` wird die laufende Runde **nicht mehr sofort unterbrochen** — die Session läuft bis zur nächsten `agent/pre-step`-Grenze, wo das Step-Gate schließt (siehe nächster Abschnitt); ausgeschaltet wird für alle laufenden Root-Sessions `gate.stopNextTurn` aufgerufen (echte Pause über das eigene Session-Gate, oder je nach `queueFallback` Rückfall auf die Lock-Wait-Warteschlange);
|
|
176
|
+
- **Spitzenende / Wochenende**: zuerst `releaseAll` für die gehaltenen Steps (die Runde läuft an Ort und Stelle weiter), dann `gate.resume` **aller** Sessions — gesteuert durch den Schalter `offPeakAutoResume`, ausgeschaltet keine automatische Fortsetzung nach der Spitzenzeit;
|
|
177
|
+
- **Peak-Zeitzone**: immer Pekinger Zeit (`Asia/Shanghai`), identisch mit der Abrechnungsbasis von DeepSeek, unbeeinflusst von der `timezone`-Einstellung;
|
|
178
|
+
- Zustandsmaschine: einzelne Instanz `NORMAL ↔ PAUSED_PEAK` (`scheduler.js`), angetrieben von einem einzigen 30-s-Tick.
|
|
179
|
+
|
|
180
|
+
### Step-Gate (v0.2.0, der Schlüssel zur Token-Ersparnis)
|
|
181
|
+
|
|
182
|
+
Hängt an der `agent/pre-step`-Waterfall: die Runde wird angehalten, **bevor die Modellanfrage des nächsten Steps passiert**.
|
|
183
|
+
|
|
184
|
+
- **Haltebedingungen** (alle erforderlich): `enabled` + `stepLevelPause` + `step > 1` + Spitzenzeit (Pekinger Zeit, nicht Wochenende) + offizieller Ziel-Provider (`providerGuard`, bei „aus" werden alle blockiert) + Session nicht anfragegehalten + in diesem Peak-Fenster nicht manuell umgangen;
|
|
185
|
+
- **Warum `step > 1`**: der erste Step einer Runde wird vom Anfrage-Wächter abgedeckt, damit sich die beiden Gates nicht überlappen;
|
|
186
|
+
- **Freigabewege**: ① Button „⏸ pausiert (fortsetzen)" / `POST /session-guard/rpc {action:'stepResume'}` / `/resume` → lässt den aktuellen Step durch und **blockiert diese Session für den Rest der Spitzenzeit nicht mehr**; ② Spitzenende → alles freigeben, die Runde läuft an Ort und Stelle weiter (**kein followup nötig**); ③ Freeze-Button / `/pause` / `/cancel` → Gate freigeben und zur Runden-Pause übergehen; ④ `signal`-abort (Benutzerabbruch) → freigeben;
|
|
187
|
+
- **Timeout-Eskalation**: Haltung über `stepGateTimeoutMs` (Standard 5 Minuten) hinaus → Gate freigeben und **Eskalation zu einer force-Pause auf Rundenebene**, nach der Spitzenzeit einheitlich fortgesetzt (kein Stillstand, kein Token-Tropfen während der Spitzenzeit);
|
|
188
|
+
- **Zustand**: `GET /session-guard/state?session=<id>` liefert `paused: { step, turn }` und `stepGate: { held, since, bypass }`; das `paused` des Service-Ports `state()` **bleibt boolesch** (Rückwärtskompatibilität), der Step-Zustand steckt in `pausedStep`;
|
|
189
|
+
- **Keine Persistenz**: der Halt ist eine Promise im Prozess, ein Neustart verwirft sie (keine Geisterzustände).
|
|
190
|
+
|
|
191
|
+
#### Buttons „Session pausieren / Session fortsetzen" (von session-guard bereitgestellt)
|
|
192
|
+
|
|
193
|
+
Der Button „Session pausieren" rechts im Eingabebereich (slot `conversation.input.right`, id `session-guard-pause`, order 20, links vom input-traffic-Button „❄ Einfrieren & Anhang"):
|
|
194
|
+
|
|
195
|
+
- nicht pausiert → „Session pausieren", **klickbar**: der Klick ruft `stepPause` auf und pausiert die Session **vor der Modellanfrage des nächsten Steps** (der aktuelle Step wird nicht unterbrochen; auch Step 1 wird gehalten, unabhängig von Peak-Zeiten / Provider);
|
|
196
|
+
- pausiert → „Session fortsetzen", der Klick ruft `stepResume` auf: lässt den aktuellen Step durch und blockiert diese Session für den Rest der Spitzenzeit nicht mehr;
|
|
197
|
+
- **Event-Push**: `GET /session-guard/events?session=<id>` (SSE) pusht Step-Gate-Zustandsänderungen **sofort** — schließt die Spitzenzeit das Gate automatisch, wechselt der Button augenblicklich zu „Session fortsetzen", ohne auf einen Poll zu warten; zusätzlich dient ein 10-Sekunden-Poll von `/session-guard/state` als Sicherheitsnetz (konvergiert auch, wenn SSE nicht verfügbar / getrennt ist);
|
|
198
|
+
- Stil an den input-traffic-Buttons derselben Zeile ausgerichtet (24 px Höhe / 6 px Radius / 12 px Schrift / dieselben CSS-Tokens), mit Hover- und Pausen-Zustandsrückmeldung.
|
|
199
|
+
|
|
200
|
+
### Session-Sperre (Einfrieren)
|
|
201
|
+
|
|
202
|
+
- **Redundanter Port**: `ctx.provide('sessionGuard', service)` — `stopNextTurn(sessionId)` / `resume(sessionId)` / `lockQueue(sessionId)` / `unlockQueue(sessionId)` / `state(sessionId)`;
|
|
203
|
+
- **RPC-Brücke**: `POST /session-guard/rpc { action, sessionId }` — der input-traffic-Freeze-Button ruft `stopNextTurn` / `resume` **sessionweise** nach `sessionId` auf; wird still übersprungen, wenn dieses Plugin nicht installiert ist (fail-open D8);
|
|
204
|
+
- **Manuelle Befehle**: `/pause [force|safe] [stop|wait]`, `/resume [confirm] [rerun|skip]`, `/cancel` — wirken auf die aufrufende Session (`invocation.agent.id`).
|
|
205
|
+
|
|
206
|
+
### Automatische Backend-Wiederholung (D9)
|
|
207
|
+
|
|
208
|
+
Hört auf `turn/end` und klassifiziert Fehler:
|
|
209
|
+
|
|
210
|
+
- **Transiente Fehler** (error/429/max-tokens usw.) → automatische `followup(retryText)`-Fortsetzung mit adaptivem Backoff;
|
|
211
|
+
- **Permanente Fehler** (Authentifizierung/Guthaben/Modell/Kontextlimit) → Stopp;
|
|
212
|
+
- **Ausweichen während Freeze/Gate**: keine Wiederholung, solange `isFrozen(sessionId)` wahr ist (queueLocked / paused / taskControl paused);
|
|
213
|
+
- Benutzereingriff oder erfolgreiche Runde setzt den Zähler aufeinanderfolgender Fehler zurück.
|
|
214
|
+
|
|
215
|
+
### Status-Badge (Frontend-Anzeige)
|
|
216
|
+
|
|
217
|
+
Rechts im Eingabebereich erscheint ein **rein informatives** Status-Badge, das die aktuelle Phase in Echtzeit widerspiegelt:
|
|
218
|
+
|
|
219
|
+
| Phase | Badge-Text | CSS-Klasse | Bedeutung |
|
|
220
|
+
|---|---|---|---|
|
|
221
|
+
| `peak` (Prüfung an) | 高峰·拦官方 | `sg-peak` | Spitzenzeit, nur Anfragen an offizielle DeepSeek-Quellen werden blockiert |
|
|
222
|
+
| `peak` (Prüfung aus) | 高峰·全部暂停 | `sg-peak` | Spitzenzeit, alle Sessions pausiert |
|
|
223
|
+
| `off-peak` | 谷时 | `sg-off` | Außerhalb der Spitzenzeit, Sessions laufen normal |
|
|
224
|
+
| `weekend` | 周末 | `sg-weekend` | Wochenende (bei aktivem Wochenendmodus), ohne Peak-/Off-Peak-Rücksicht |
|
|
225
|
+
|
|
226
|
+
- **Polling**: alle 15 Sekunden wird `GET /session-guard/status` abgefragt, für globales `phase`, `providerGuard`, `held`, `deferred`, `stepHeld`;
|
|
227
|
+
- **fail-open**: Route nicht erreichbar, Netzwerkfehler oder `enabled` aus → Badge wird still ausgeblendet, keine Session betroffen;
|
|
228
|
+
- **Unabhängig von input-traffic**: das Badge rendert der session-guard-Client allein, es erscheint **ohne Installation des input-traffic-Plugins**. input-traffic liefert nur den Freeze-Button, keine Abhängigkeit zum Badge;
|
|
229
|
+
- **Tooltip**: beim Hover zeigt es `Phase · Zeitzone · Wochenendmodus · Prüfbasis · Anzahl gehaltener/verzögerter/Step-gehaltener`.
|
|
230
|
+
|
|
231
|
+
### Prüfbasis für offizielle Quellen (providerGuard)
|
|
232
|
+
|
|
233
|
+
In Spitzenzeiten werden Sessions nicht pauschal gestoppt: Zuerst wird geprüft, ob „die Route, die diese Anfrage wirklich nimmt, eine offizielle DeepSeek-Quelle ist":
|
|
234
|
+
|
|
235
|
+
| Priorität | Grundlage | `matchedBy` | Beispiel |
|
|
236
|
+
|---|---|---|---|
|
|
237
|
+
| 1 | explizite ID-Liste `officialProviders` | `explicit` | Benutzer deklariert ein selbst betriebenes Gateway als offiziell |
|
|
238
|
+
| 2 | Host der live normalisierten `baseURL` | `endpoint` | `deepseek-official` auf ein Relay umgestellt → **nicht blockiert** |
|
|
239
|
+
| 3 | Katalog-Standardendpunkt | `endpoint-default` | pi-ais `deepseek`-Route zielt standardmäßig auf die offizielle API → **blockiert** |
|
|
240
|
+
| 4 | eingebaute ID-Liste (`deepseek-official`) | `route-id` | Rückfallebene, wenn der Endpunkt nicht lesbar ist |
|
|
241
|
+
| 5 | alles andere | `unknown` | nicht offiziell, durchlassen |
|
|
242
|
+
|
|
243
|
+
- **Endpunkt schlägt ID**: eine Konfiguration namens `deepseek-official`, deren `baseURL` auf ein Relay zeigt, wird **nicht** fälschlich blockiert; umgekehrt entgeht pi-ais eingebaute `deepseek`-Route mit Standardendpunkt auf die offizielle API der Blockierung **nicht**.
|
|
244
|
+
- **Endpunktquelle**: `ctx.get('llm').listConfigurableProviders()` findet den Katalogeintrag → `ctx.settings.get(settingsNs)` liest die `baseURL` über `settingsPath` (nur nicht-sensible Felder, der Wert von `apiKeyEnv` wird nie gelesen). Bei jeder Anfrage neu berechnet, nie gecached → Provider-Konfigurationsänderungen greifen sofort.
|
|
245
|
+
- **Umschaltung weg von offiziell löst Fortsetzung aus**: wird eine beim Spitzenbeginn pausierte Session auf einen lokalen/Drittanbieter-Provider umgestellt (`model/selection`-Event ab 0.1.2) → automatische Fortsetzung dieser Session (unter dem Vorbehalt von `deferredResume`); nur Sessions, die dieses Plugin beim Spitzenbeginn pausiert hat, werden berührt, manuell per `/pause` pausierte **niemals**. Ohne dieses Event unter 0.1.1 → Rückfall auf „nächste Anfrage oder manuelles `/resume`".
|
|
246
|
+
- **Endpunkt nicht lesbar**: `llm`-Dienst fehlt, Namespace-Struktur geändert, Feld kein String — durchweg Degradierung zur ID-/eingebauten-Endpunkt-Prüfung mit protokolliertem `matchedBy`, **niemals wird eine Exception geworfen**.
|
|
247
|
+
- **Fehlurteil untersuchen**: `GET /session-guard/provider?provider=<id>` liefert `{ official, matchedBy, endpoint }`.
|
|
248
|
+
|
|
249
|
+
### Anfragegenauer Wächter und verzögerte Warteschlange
|
|
250
|
+
|
|
251
|
+
- **Warum Anfrageebene**: der 30-s-Tick verarbeitet beim Sprung `NORMAL → PAUSED_PEAK` nur die dann `running` Sessions; danach gestartete oder unterwegs auf offizielle Quellen umgeschaltete Sessions würden durchrutschen. Die `agent/request`-Waterfall läuft bei **jeder Anfrage**.
|
|
252
|
+
- **Die Prüfung nutzt den Rückgabewert von `next()`**: die Modellauswahl-Middleware überschreibt provider/model innerhalb der Waterfall mit den in der UI gewählten Werten, daher muss erst `await next()` abgewartet werden, bevor geprüft wird.
|
|
253
|
+
- **Hold-Modus (Standard)**: die Anfrage wird angehalten, **weder gesendet noch mit Fehler**, und exakt zum Spitzenende freigegeben (`msUntilOffPeak`-Präzisionszeitgeber, 30-s-Tick als Rückfallebene); ein Benutzerabbruch (abort) unterbricht normal.
|
|
254
|
+
- **Error-Modus**: wirft einen erkennbaren `PEAK_DEFERRED`-Fehler + Eintrag in die verzögerte Warteschlange, Fortsetzung nach der Spitzenzeit mit `deferredResumeText` (keine automatische Fortsetzung, wenn `deferredResume` aus ist).
|
|
255
|
+
- **Obergrenze**: läuft `deferredMaxHoldMs` (Standard 6 h) ab, ohne dass die Spitzenzeit endet → Umwandlung in error, um endloses Anhalten zu vermeiden.
|
|
256
|
+
- **Eiserne Ausschließungsregel**: während eines Holds wird **nie** zusätzlich das Session-Gate zum Pausieren aufgerufen (eine Pause wartet auf die sichere Grenze, die eine gehaltene Anfrage nie erreicht → beide warten aufeinander). Beim Spitzenbeginn werden bereits gehaltene Sessions übersprungen.
|
|
257
|
+
- **Keine Persistenz**: die verzögerte Warteschlange ist eine In-Prozess-Promise, ein Neustart löscht sie.
|
|
258
|
+
|
|
259
|
+
### Grenzen (ausdrücklich nicht im Umfang)
|
|
260
|
+
|
|
261
|
+
- **Kein Provider-Wechsel / kein Umleiten**: nur blockieren, nicht routen;
|
|
262
|
+
- **Compaction läuft nicht durch `agent/request`**: während eine Session pausiert ist, findet sie nicht statt; eine manuell ausgelöste Komprimierung während der Spitzenzeit kann weiterhin die offizielle Quelle treffen (dieses Plugin greift nicht in die `ctx.llm.stream`-Schicht ein);
|
|
263
|
+
- **0.1.1 hat kein `model/selection`-Event**: die automatische Fortsetzung nach Umschaltung auf eine nicht-offizielle Quelle fällt auf „nächste Anfrage oder manuelles `/resume`" zurück (ab 0.1.2 sofort);
|
|
264
|
+
- **Keine neuen npm-Abhängigkeiten**, kein Lesen/Schreiben von Zugangsdaten, keine Berührung der 429-/Transport-Retries von `dsh-llm-retry`.
|
|
265
|
+
|
|
266
|
+
### Zeitzonenbehandlung und -validierung
|
|
267
|
+
|
|
268
|
+
- Die Zeitzonenerkennung basiert auf **IANA-Zeitzonennamen** (z. B. `Asia/Shanghai`, `Asia/Tokyo`, `Asia/Seoul`), projiziert über `Intl.DateTimeFormat` auf die Wanduhr der konfigurierten Zeitzone, **ohne nacktes `getUTCDay()`** — vermeidet den klassischen 8-Stunden-Grenzfehler der UTC+8-Zone Pekings (Samstag 00:30 Pekinger Zeit ist in UTC noch Freitag);
|
|
269
|
+
- `Intl.DateTimeFormat` ist selbst die Validierungsschicht: ein ungültiger Zeitzonenname (z. B. `Foo/Bar`) wirft eine `RangeError`, die von einem äußeren try-catch still auf die Standardzeitzone `Asia/Shanghai` zurückfällt (fail-open);
|
|
270
|
+
- Peak-Fenster sind **links geschlossen, rechts offen** `[start, end)`, mit Unterstützung für Fenster über Mitternacht (z. B. `22:00–06:00`);
|
|
271
|
+
- Die `timezone`-Einstellung verhält sich in allen Sprachen (zh/en/ja/ko) identisch — die IANA-Zeitzonennamen von `Intl.DateTimeFormat` sind locale-unabhängig, das Zeitzonenverhalten unter japanischer/koreanischer Oberfläche ist exakt wie im Chinesischen.
|
|
272
|
+
|
|
273
|
+
### Arbeitsteilung mit input-traffic: das eine „stoppt", das andere „reiht"
|
|
274
|
+
|
|
275
|
+
Beide wirken auf **unterschiedlichen Gliedern derselben Kette**, die Grenze zieht DSHs eigenes Inbox-Modell:
|
|
276
|
+
|
|
277
|
+
```
|
|
278
|
+
Benutzereingabe ──(input-traffic wählt die Stufe)──▶ next-step / next-turn Warteschlangen
|
|
279
|
+
│
|
|
280
|
+
agent/pre-step ──(Step-Gate dieses Plugins)──▶ durchlassen / anhalten
|
|
281
|
+
│
|
|
282
|
+
agent/request ──(Anfrage-Hold dieses Plugins)──▶ durchlassen / anhalten
|
|
283
|
+
│
|
|
284
|
+
Modellanruf
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
**DSHs Warteschlangen-Semantik (zwei Warteschlangen, nicht verwechseln)**
|
|
288
|
+
|
|
289
|
+
| Warteschlange | Bedeutung | Verbrauch |
|
|
290
|
+
|---|---|---|
|
|
291
|
+
| `next-step` | „Eingabe, die auf die nächste Step-Grenze wartet" | Am nächsten `agent/pre-step`: **auf gleicher Ebene wie ein Tool-Ergebnis**, ein weiterer Step in derselben Runde |
|
|
292
|
+
| `next-turn` | „Prompt, der auf eine eigene Runde wartet" | Nach Abschluss der aktuellen Runde, gestartet als **neue Runde** |
|
|
293
|
+
|
|
294
|
+
`Inbox.claim()` **räumt immer zuerst `next-step` ab** und nimmt nur dann zusätzlich **1** `next-turn`, wenn die Grenze eine neue Runde eröffnet; der erste Step einer Runde liest next-turn, alle folgenden lesen next-step.
|
|
295
|
+
|
|
296
|
+
**Zuständigkeiten**
|
|
297
|
+
|
|
298
|
+
- **session-guard = stoppen**: entscheidet nur, „wann fortgeschritten werden darf", **fasst Inhalt und Reihenfolge der Warteschlangen nie an**.
|
|
299
|
+
- Step-Gate (`agent/pre-step`): hält **vor** der Modellanfrage des nächsten Steps;
|
|
300
|
+
- Runden-Pause (`agent.cancel({keepInbox:true})` + `goals.pause` + sichere Grenze): stoppt die aktuelle Runde, **Warteschlangen bleiben unverändert**;
|
|
301
|
+
- Anfragegenauer Wächter (Hold auf `agent/request`): hält **genau diese eine Modellanfrage** an.
|
|
302
|
+
- **input-traffic = reihen**: entscheidet nur, „in welche Warteschlange die Benutzereingabe kommt, in welcher Stufe, und wann sie verbraucht wird".
|
|
303
|
+
- drei Stufen = welche Warteschlange: rot „unterbrechen" ruft erst `cancel()` auf, dann `steer`; gelb „einfügen" ruft `steer` auf (→ `next-step`, der nächste Step derselben Runde); grün „einreihen" bleibt in `next-turn`;
|
|
304
|
+
- Einfrieren = alle `queued` + `steering`-Zeilen herauslösen (Stufen bleiben erhalten) + Composer-Sperre + Aufruf von `sessionGuard.stopNextTurn`; Fortsetzen = Sperre aufheben → zuerst `sessionGuard.resume` → erneute Einreichung nach Stufe.
|
|
305
|
+
|
|
306
|
+
**Zwei eiserne Regeln am Treffpunkt**
|
|
307
|
+
|
|
308
|
+
1. **Einfrieren muss dieses Plugin zuerst die Step-Gate freigeben lassen**: das Step-Gate hängt an `agent/pre-step`, während die Runden-Pause auf ein Ereignis an der sicheren Grenze wartet — beide würden aufeinander warten (`pauseTask` / `cancelTask` dieses Plugins machen zuerst `release`);
|
|
309
|
+
2. **Beim Step-Gate-Halt sind die Nachrichten bereits entnommen**: `preStep()` macht `inbox.claim()`, bevor es die Waterfall dispatcht, neue Eingaben reihen sich also hinter dem bereits entnommenen Stapel an; `keepInbox` gilt nur für die Runden-Pause.
|
|
310
|
+
|
|
311
|
+
**Kein gegenseitiges Übergreifen**: input-traffic hört weder `agent/pre-step` noch `agent/request` (einzige Ausnahme ist das explizite `cancel()` der Stufe „unterbrechen", das vom Benutzer verlangt wurde); dieses Plugin schreibt niemals Inhalt oder Reihenfolge von `next-step` / `next-turn` um.
|
|
312
|
+
|
|
313
|
+
Zu den Buttons: die Buttons „Session pausieren / Session fortsetzen" dieses Plugins (order 20) und die input-traffic-Buttons „❄ Einfrieren & Anhang / Fortsetzen & Anhang" (order 30) stehen nebeneinander und ersetzen sich nicht — erstere steuern das Step-Gate, letztere das Herauslösen aus der Warteschlange + das Einfrieren auf Rundenebene.
|
|
314
|
+
|
|
315
|
+
## Redundanter Port `sessionGuard`
|
|
316
|
+
|
|
317
|
+
```js
|
|
318
|
+
{
|
|
319
|
+
stopNextTurn(sessionId, opts), // stoppt die nächste Runde der Session (eigenes Session-Gate / Rückfall Lock-Warteschlange)
|
|
320
|
+
resume(sessionId, opts), // fortsetzen (confirm + choice: rerun|skip)
|
|
321
|
+
lockQueue(sessionId, reason), // Warteschlange explizit sperren
|
|
322
|
+
unlockQueue(sessionId), // Warteschlange explizit entsperren
|
|
323
|
+
stepPause(sessionId), // manuell eine Step-Pause anfordern (Gate schließt an der nächsten pre-step-Grenze, Step 1 inklusive)
|
|
324
|
+
stepResume(sessionId, opts), // Step-Gate öffnen (v0.2.0); mit opts.bypass=false kein Peak-Umgehen
|
|
325
|
+
state(sessionId), // { queueLocked, lockReason, paused, pausedStep, stepHeldSince, stepBypass, taskControlAvailable, taskControl }
|
|
326
|
+
}
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## HTTP-Routen
|
|
330
|
+
|
|
331
|
+
- `GET /session-guard/state?session=<id>` — Session-Zustand (mit `paused: { step, turn, manual }` / `stepGate` / letztes Ziel / gehalten? / verzögert?)
|
|
332
|
+
- `GET /session-guard/events?session=<id>` — **SSE**: sofortiger Push von Step-Gate-Zustandsänderungen (der Button aktualisiert sich danach)
|
|
333
|
+
- `GET /session-guard/settings` — Einstellungen + taskControl-Verfügbarkeit
|
|
334
|
+
- `GET /session-guard/status` — aktuelle globale Phase (Polling des Status-Badges; enthält `stepHeld`)
|
|
335
|
+
- `GET /session-guard/provider?provider=<id>` — Diagnose des Offiziell-Quellen-Urteils (`official` / `matchedBy` / `endpoint`)
|
|
336
|
+
- `GET /session-guard/diag` — Laufzeitdiagnose (enthält `stepGate`)
|
|
337
|
+
- `POST /session-guard/rpc` — `{ action: stopNextTurn|resume|lockQueue|unlockQueue|stepPause|stepResume|state, sessionId }`
|
|
338
|
+
|
|
339
|
+
## Zustandsspeicher
|
|
340
|
+
|
|
341
|
+
JSON pro Session: `$DSH_HOME/.dsh/session-guard/<sessionId>.json` (atomares Schreiben; überschreibbar mit `DSH_SESSION_GUARD_STATE_DIR`).
|
|
342
|
+
|
|
343
|
+
## Tests
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
npm test # node --test tests/*.test.mjs (Zeitzonen/Wochenende/Zustandsmaschine/Session-Gate/Brücke/Retry)
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Module
|
|
350
|
+
|
|
351
|
+
| Datei | Verantwortung |
|
|
352
|
+
|---|---|
|
|
353
|
+
| `src/time.js` | Peak-/Wochenenderkennung (zeitzonengenau) + `msUntilOffPeak` (präzises Timing des Spitzenendes) |
|
|
354
|
+
| `src/scheduler.js` | Reine Zustandsmaschine NORMAL ↔ PAUSED_PEAK |
|
|
355
|
+
| `src/provider.js` | Fünfstufige Prüfung offizieller Quellen (pure Funktion: Endpunkt-Normalisierung + Entscheidungsmatrix) |
|
|
356
|
+
| `src/provider-directory.js` | Endpunktverzeichnis (`llm.listConfigurableProviders` + `settings.get`, Degradierung auf der ganzen Kette) |
|
|
357
|
+
| `src/deferrals.js` | Register der Verzögerungen (Halten / Freigabe / Limitüberschreitung / `PeakDeferredError`) |
|
|
358
|
+
| `src/request-guard.js` | Anfragegenauer Wächter auf `agent/request` (zwei Modi hold / error) |
|
|
359
|
+
| `src/step-gate.js` | **Step-Gate auf `agent/pre-step`** (v0.2.0: Schließen / Freigeben / Timeout-Eskalation / Bypass, das reine `decideStepHold` ist unit-testbar) |
|
|
360
|
+
| `src/targets.js` | Nachverfolgung des „letzten echten Ziels" einer Session (`request/header` + `model/selection`) |
|
|
361
|
+
| `src/wiring.js` | Verdrahtungs-Orchestrierung (Spitzenbeginn-Filter / Step-Gate-Verdrahtung / Freigabe nach der Spitzenzeit / präziser Zeitgeber / Aufräumen beim Entladen) |
|
|
362
|
+
| `src/pause-gate.js` | Engine des eigenen Session-Gates (agent.cancel keepInbox + goals.pause + sichere Grenze + followup-Fortsetzung; gibt vor dem Pausieren zuerst das Step-Gate frei) |
|
|
363
|
+
| `src/pause-store.js` | Persistenz des eigenen Pausenzustands |
|
|
364
|
+
| `src/gate.js` | Treiber des Session-Gates (echte eigene Pause / Rückfall Lock-Warteschlange, fail-open) |
|
|
365
|
+
| `src/bridge.js` | Redundanter Port `sessionGuard` |
|
|
366
|
+
| `src/retry.js` | Automatische Backend-Wiederholung (Fehlerklassifikation/Backoff/Ausweichen bei Freeze; Kurzschluss nur beim exakten Code `PEAK_DEFERRED`) |
|
|
367
|
+
| `src/detect.js` | Automatische Erkennung (Host-taskControl / Client-input-traffic-Brücke) |
|
|
368
|
+
| `src/store.js` | Persistenter Zustand pro Session |
|
|
369
|
+
| `src/settings.js` | Einstellungsunterbereich (schemastery-Schema + fail-open-Registrierung) |
|
|
370
|
+
| `src/index.js` | Host-apply (Einstellungen/Routen/Tick/Dienst bereitstellen/Retry-Verdrahtung/Anfrage-Wächter) |
|
|
371
|
+
| `src/client/` | Browser-Hälfte (**Session-Pausieren-Button** + Status-Badge + Einstellungskarte) |
|
|
372
|
+
|
|
373
|
+
## Lizenz
|
|
374
|
+
|
|
375
|
+
MIT — siehe [LICENSE](LICENSE).
|