dsh-session-guard 3.2.4 → 4.0.0

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 CHANGED
@@ -1,216 +1,227 @@
1
- # Changelog
2
-
3
- All notable changes to `dsh-session-guard` are recorded here. Versions follow semver.
4
-
5
- - [English changelog](./CHANGELOG.md)
6
- - [日本語 changelog](./CHANGELOG.ja.md)
7
- - [한국어 changelog](./CHANGELOG.ko.md)
8
-
9
- ## 3.2.4 — 2026-09-27
10
-
11
- ### Fixed
12
- - **Session format v4 adaptation (host >= 0.1.7-rc.1)**: the three session-writing paths no longer use the retired `source: { kind: 'plugin', plugin: 'session-guard' }` signature, which the v4 host rejects with `SessionFormatError` (whole round fails). All three now use the producer-owned kind:
13
- - delayed-resume notice injection (`src/wiring.js`);
14
- - auto-retry notice injection (`src/retry.js`);
15
- - pause-resume followup (`src/pause-gate.js`, ```kind: `plugin:${pluginId}````).
16
- - **tool-result v4 native adaptation (N1)**: `findToolOutcome` in `src/pause-gate.js` now reads the v4 first-class `role:'tool'` message (`toolCallId`/`isError` at message top level) first; the v3 `tool-result` block path is kept as historical fallback. Without this, a failed tool at pause time was reported as completed on resume ("do not re-run") — a silent wrong decision. `src/tool-call-id.js` reads the native top-level id first; stale 0.1.1/0.1.2-era header notes corrected to the three-shape generation table.
17
- `form` and all other fields are unchanged; the host's own v3-to-v4 migration lifts existing legacy rows, so no historical data is rewritten. Evidence: `@deepseek-ai/dsh-session-format-v3-to-v4@0.1.7-rc.2` validates only that `source.kind` is non-empty and not `'plugin'`.
18
-
19
- ## 0.4.0 — 2026-09-18 (mis-versioned; superseded by 3.0.0)
20
-
21
- ### Fixed
22
-
23
- - **Version identity.** This line was published as `0.4.0` while `dsh.plugin.json` and this
24
- changelog already said `3.0.0` — one artifact with two version numbers. `package.json` is now
25
- `3.0.0`, so the package version, the manifest version and the changelog agree. `0.4.0` is kept
26
- here as a historical record because npm versions are immutable; `dist-tag dsh-0.1.5` should be
27
- repointed to `3.0.0` once it is published.
28
- - **Docs.** README/INSTALL (zh/en/ja/ko) no longer describe the sibling line as living on `main`.
29
- The 0.1.2 line's release branch is **`legacy/0.1.2`** (npm dist-tag `dsh-0.1.2`, version `0.3.1`);
30
- `main` is frozen at `0.2.0-beta.1`. The duplicated branch fragment in the INSTALL.zh/ja/ko install
31
- command is fixed, explicit dist-tag install commands are documented, and "2.x / 3.x" is
32
- now marked as a **line nickname** rather than a version number.
33
-
34
- ### Notes
35
-
36
- - **No source changes** relative to `3.0.0`; `0.4.0` is a packaging-only publish of the same tree.
37
-
38
- ## 3.0.0 — 2026-09-14
39
-
40
- ### Changed
41
-
42
- - **DSH v0.1.5-rc.2 dedicated line (`compat/0.1.5`).** `engines.dsh` and the `dsh-client-*`
43
- peer ranges narrow to `>=0.1.5-rc.2 <0.2.0-0` (strict-semver prerelease matching means the
44
- old `>=0.1.0-rc.7` range never matches `0.1.5-rc.2`); `dsh.plugin.json` gains `engines.dsh`.
45
- The 2.x / 0.2.x line on `main` keeps serving DSH 0.1.0-rc.7 … 0.1.2-rc.1.
46
- - **`session.events` → `snapshotEvents()`.** DSH 0.1.5 removed the `session.events` array
47
- accessor (compatibility-guide §20.3). `pause-gate.js` now reads session events through a
48
- dual-path helper: `snapshotEvents()` first, the legacy `events` array as a defensive
49
- fallback, `null` (fail-open) when neither exists. Affects `findToolOutcome` and
50
- `lastUserPrompt` only; event-type matching is unchanged.
51
-
52
- ### Unchanged
53
-
54
- - Zero changes on every other integration seam: the self-held webServer prefix route
55
- (`/session-guard/rpc`), `settings.register`, the `settings.plugin.item` card slot,
56
- client injections, and `llm.listConfigurableProviders()` are all verified intact against
57
- the published 0.1.5-rc.2 bundle (`tools/check-api-drift.ps1`, 12/12 required assertions).
58
-
59
- ### Pending
60
-
61
- - Live smoke on a real DSH 0.1.5-rc.2 host (same status as the perm-gate 0.1.5 line).
62
-
63
- ## 0.2.0-beta.2 — 2026-09-13
64
-
65
- ### Fixed (DSH 0.1.5 compat — `compat/0.1.5` branch)
66
-
67
- - **Dual-path resume enqueue.** DSH 0.1.5 turns the Inbox into an agent-loop read-only
68
- projection, so `agent.followup` may no longer exist. The resume flow now tries
69
- `agent.followup` first, falls back to `agent.send`, and degrades to a warn (never throws)
70
- when neither is available — a failed enqueue can no longer break resume.
71
- - **Resume messages carry `source.form: 'instructions'`** per the 0.1.5 `ContextFormed`
72
- message-source contract (`kind: 'plugin'` is a built-in kind; older DSH versions ignore
73
- the extra field).
74
- - **`webServer.register` is wrapped in try/catch**: a failed route registration now logs an
75
- error instead of throwing out of `apply` and breaking host plugin loading.
76
- - Verified against the 0.1.5-rc.2 source: the `WebRoute` contract (exact/prefix + SSE) is
77
- unchanged, so client `fetch('/session-guard/...')` paths need no `/api` prefix.
78
-
79
- ## 0.2.0-beta.1 — 2026-09-10
80
-
81
- ### Added
82
-
83
- - **Step-level gate (`agent/pre-step`).** During peak hours the turn is now held **before** the
84
- next step's model request instead of being interrupted at a turn boundary: the session keeps
85
- running until the next `agent/pre-step`, where the gate holds it (setting `stepLevelPause`, on by
86
- default). The turn resumes **in place** off-peak — no followup message needed. Hold conditions:
87
- peak (Beijing time) + not weekend + `step > 1` + official target provider (`providerGuard`) +
88
- not request-held + not bypassed in this peak window. New module `src/step-gate.js` (pure
89
- `decideStepHold` + hold / release / abort / timeout engine).
90
- - **`stepResume` port + RPC + `/resume`.** `sessionGuard.stepResume(sessionId, {bypass})`,
91
- `POST /session-guard/rpc {action:'stepResume'}` and `/resume` all release the gate; a manual
92
- resume also stops gating that session for the rest of the peak window.
93
- - **Timeout escalation.** `stepGateTimeoutMs` (default 300000) releases the gate and escalates to a
94
- turn-level **force** pause, so a long peak neither deadlocks nor drips one step every five minutes.
95
- - **"⏸ Pause session" button** (client, slot `conversation.input.right`, id `session-guard-pause`,
96
- order 20 — left of input-traffic's freeze button). It polls `/session-guard/state` once a second,
97
- stays disabled while nothing is held, and calls `stepResume` when it is. The status badge moved to
98
- order 40 and now reports the number of step-held sessions.
99
- - **New settings**: `stepLevelPause`, `stepGateTimeoutMs`.
100
- - **New state**: `GET /session-guard/state` now returns `paused: { step, turn }` and
101
- `stepGate: { held, since, bypass }`; `/status` returns `stepHeld`; `/diag` returns `stepGate`.
102
- The service port's `state().paused` stays boolean for compatibility (new field `pausedStep`).
103
-
104
- ### Fixed
105
-
106
- - **Deadlock between a held step and a turn-level pause.** `pauseTask` / `resumeTask` /
107
- `cancelTask` now release the step gate first: a step hold sits at `agent/pre-step`, where no
108
- `assistant/message` or `tool/result` can ever arrive, so a `safe` pause used to wait forever and
109
- never persisted `paused`.
110
-
111
- ### Changed
112
-
113
- - **Peak entry no longer interrupts running turns** when `stepLevelPause` is on (`onEnterPeak` arms
114
- the step gate instead of calling `stopNextTurn`); with it off the previous turn-level behaviour is
115
- unchanged.
116
- - input-traffic's freeze button label is now **"Freeze & append"** (`冻结追加` / `凍結して追加` /
117
- `동결 후 추가`), and its resume label **"Resume & append"** (`恢复追加` / `再開して追加` /
118
- `재개 후 추가`) — it freezes the turn and keeps queued messages, distinct from the pause button.
119
- - **The pause button is a toggle now**: "Pause session" / "Resume session" (no disabled grey state).
120
- Clicking "Pause session" calls the new `stepPause` action, which holds the session at the **next
121
- step boundary** (step 1 included, regardless of peak or provider); "Resume session" calls
122
- `stepResume`. New port method `sessionGuard.stepPause(sessionId)`.
123
- - **SSE push**: new route `GET /session-guard/events?session=<id>` pushes step-gate state changes the
124
- moment they happen, so peak auto-holds flip the button to "Resume session" without waiting for a
125
- poll; the 10s `/session-guard/state` poll remains as a fallback. `/state` now reports
126
- `paused.manual` and `stepGate.manual`.
127
- - **Styling aligned** with input-traffic's composer button (24px height, 6px radius, 12px font, the
128
- same border/hover/pressed tokens) for both the pause button and the status badge; styles are
129
- injected once via `<style data-plugin-css="session-guard-client">`.
130
-
131
- ## Unreleased
132
-
133
- ### Added
134
-
135
- - **Official-source two-dimensional guard (peak × target provider).** Peak hours now block only
136
- requests whose target route is a DeepSeek official source; local/third-party providers keep
137
- running. Verdict order: explicit `officialProviders` id list → live `baseURL` endpoint →
138
- catalog builtin endpoint (pi-ai's `deepseek`) → builtin id (`deepseek-official`); each verdict
139
- reports `matchedBy`. New modules: `src/provider.js` (pure), `src/provider-directory.js`,
140
- `src/deferrals.js`, `src/request-guard.js`, `src/targets.js`, `src/wiring.js`.
141
- - **Request-level backstop (`agent/request`).** Covers sessions started after peak entry and
142
- sessions switched to an official source mid-run — the 30s tick only handled sessions that were
143
- already `running` at the transition. Default `hold` mode suspends the request without an error
144
- and releases it at the exact off-peak instant (`msUntilOffPeak`); `error` mode throws a
145
- recognizable `PEAK_DEFERRED` failure and records a deferral for off-peak resume.
146
- - **New settings**: `providerGuard`, `officialProviders`, `officialBaseURLs`, `deferredResume`,
147
- `deferredResumeText`, `deferredMode`, `deferredMaxHoldMs` (default 6h), `guardSubagents`.
148
- - **New routes**: `GET /session-guard/provider?provider=<id>` (verdict diagnostics);
149
- `/session-guard/status` now reports `providerGuard` / `held` / `deferred`;
150
- `/session-guard/state` reports the session's last target.
151
- - **Drift guard** `tools/check-api-drift.ps1` asserting the required APIs exist on
152
- `dsh-v0.1.1-rc.2` / `dsh-v0.1.2-rc.1` / `dsh-v0.1.3-alpha.2` / `dsh-v0.1.5-alpha.1`.
153
-
154
- ### Changed
155
-
156
- - **DSH dual-version support (0.1.0-rc.7 … 0.1.2-rc.1).** One artifact now covers both
157
- `dsh-v0.1.1-rc.2` and `dsh-v0.1.2-rc.1`. The `tool/result` call-id dual read
158
- (`content[].toolCallId` first, `source.callId` fallback — both forms appear in replay logs of
159
- both versions) is extracted to the dependency-free `src/tool-call-id.js` with unit tests.
160
- `dsh.client.inject` no longer names `@deepseek-ai/dsh-client-runtime` (removed in 0.1.2) or
161
- `@deepseek-ai/dsh-client-ui-slots` (not a dynamic client row); peer ranges widened to
162
- `>=0.1.0-rc.7 <0.2.0-0` and the removed package dropped. Added `engines.dsh` and a
163
- version-compatibility table to the README (ZH/EN).
164
- - **Settings surface stays on the intersection API**: `settings.register` + `settings.get` only;
165
- `installSection` (0.1.2+) and the removed `installSettingsSection` are never used. Optional
166
- APIs (`model/selection`, `llm.listConfigurableProviders`, `settings.get`) are feature-probed and
167
- degrade instead of throwing.
168
- - **`src/retry.js` short-circuits only the exact `PEAK_DEFERRED` sentinel.** 429 / `RATE_LIMIT` /
169
- `TRANSPORT` / timeout failures remain transient, so DSH's global retry (`dsh-llm-retry` on
170
- `agent/request-error`) and this plugin's own retry semantics are unchanged. `dsh-llm-retry`
171
- itself is never touched.
172
- - Badge distinguishes "peak · official only" from "peak · all paused".
173
-
174
- ## 0.1.4 — 2026-09-09
175
-
176
- ### Changed
177
-
178
- - **Public beta release** of the dual-version line (`0.1.4-beta.1`): version metadata, README
179
- compatibility table and package metadata aligned for the beta channel.
180
-
181
- ## 0.1.3 — 2026-09-09
182
-
183
- ### Fixed
184
-
185
- - **package.json encoding restored**: description was corrupted (GB2312 bytes misread as UTF-8); rewritten with correct Chinese text.
186
- - **Missing metadata**: added `repository`, `homepage` fields.
187
- - **peerDependencies corrected**: removed pinned `dsh-llm` exact version; added `cordis`, `dsh-client-runtime`, `dsh-client-locale`, `dsh-client-ui-settings`, `dsh-client-ui-slots` as optional peers matching `dsh.client.inject`.
188
- - Added `dsh.plugin.json` manifest.
189
-
190
- ## 0.1.2 — 2026-08-28
191
-
192
- ### Fixed
193
-
194
- - **Peak timezone fixed**: 峰谷判定固定北京时间 (`BILLING_TIMEZONE`),周末判定用配置时区。
195
-
196
- ## 0.1.1 — 2026-08-24
197
-
198
- ### Added
199
-
200
- - **Backend auto-retry (D9)**: `turn/end` transient failures (error/429/max-tokens) trigger adaptive-backoff `followup(retryText)` resume; permanent failures (auth/balance/model/context limit) stop; user intervention or successful turn resets consecutive failure count.
201
- - **Freeze/gate yield**: retry skips when `isFrozen(sessionId)` is true (queueLocked / paused / taskControl paused), never bypasses the session gate.
202
-
203
- ### Changed
204
-
205
- - `sessionGuard` redundant port now exposes `state(sessionId)` returning `{ queueLocked, lockReason, paused, taskControlAvailable, taskControl }`.
206
- - HTTP route `GET /session-guard/diag` returns runtime diagnostics including retry state.
207
-
208
- ### Fixed
209
-
210
- - Weekend detection now uses `Intl.DateTimeFormat` with the configured timezone instead of bare `getUTCDay()`, fixing an 8-hour boundary bug for Beijing timezone.
211
-
212
- ## 0.1.0 — 2026-08-18
213
-
214
- ### Added
215
-
216
- - Initial release: peak auto-pause (global), weekend mode, per-session freeze/resume via `sessionGuard` redundant port + RPC bridge, custom session gate (`agent.cancel keepInbox + goals.pause + session/event safe boundary + followup resume`), settings panel (Settings → Plugins → session-guard).
1
+ # Changelog
2
+
3
+ All notable changes to `dsh-session-guard` are recorded here. Versions follow semver.
4
+
5
+ - [English changelog](./CHANGELOG.md)
6
+ - [日本語 changelog](./CHANGELOG.ja.md)
7
+ - [한국어 changelog](./CHANGELOG.ko.md)
8
+
9
+ ## 4.0.0 — 2026-09-29
10
+
11
+ ### Changed
12
+ - **DSH 0.2.0 line adaptation**: `engines.dsh` and the four `@deepseek-ai/dsh-client-*` peers move to `>=0.2.0-rc.1 <0.2.1-0` (replacing the 0.1.7 ranges); npm dist-tag `dsh-0.2.0`; manifest version aligned to `4.0.0`. The plugin-facing API surface (manifest / settings / HMR / slots / session v4) is unchanged from 0.1.7 — no runtime code changes.
13
+ - `@deepseek-ai/cordis` peer floor aligned to `^4.0.4` (the 0.2.0-rc.1 host UI packages declare `~4.0.4`; the previous `^4.0.1` accepted it but understated the floor).
14
+
15
+ ### Fixed
16
+ - `src/client/family-section.tsx` landed in version control (it had existed only in the working tree since 3.2.x, so a clean checkout could not build).
17
+ - eslint flat config: dropped the core `no-unused-vars` rule — it mis-flags TS type-signature parameters; the typescript-eslint variant is kept, and the lint baseline is green again.
18
+ - v4 source-kind static gates (`tests/source-kind.test.mjs`) are now tracked and run in `npm test` (248 tests).
19
+
20
+ ## 3.2.4 — 2026-09-27
21
+
22
+ ### Fixed
23
+ - **Session format v4 adaptation (host >= 0.1.7-rc.1)**: the three session-writing paths no longer use the retired `source: { kind: 'plugin', plugin: 'session-guard' }` signature, which the v4 host rejects with `SessionFormatError` (whole round fails). All three now use the producer-owned kind:
24
+ - delayed-resume notice injection (`src/wiring.js`);
25
+ - auto-retry notice injection (`src/retry.js`);
26
+ - pause-resume followup (`src/pause-gate.js`, ```kind: `plugin:${pluginId}````).
27
+ - **tool-result v4 native adaptation (N1)**: `findToolOutcome` in `src/pause-gate.js` now reads the v4 first-class `role:'tool'` message (`toolCallId`/`isError` at message top level) first; the v3 `tool-result` block path is kept as historical fallback. Without this, a failed tool at pause time was reported as completed on resume ("do not re-run") — a silent wrong decision. `src/tool-call-id.js` reads the native top-level id first; stale 0.1.1/0.1.2-era header notes corrected to the three-shape generation table.
28
+ `form` and all other fields are unchanged; the host's own v3-to-v4 migration lifts existing legacy rows, so no historical data is rewritten. Evidence: `@deepseek-ai/dsh-session-format-v3-to-v4@0.1.7-rc.2` validates only that `source.kind` is non-empty and not `'plugin'`.
29
+
30
+ ## 0.4.0 — 2026-09-18 (mis-versioned; superseded by 3.0.0)
31
+
32
+ ### Fixed
33
+
34
+ - **Version identity.** This line was published as `0.4.0` while `dsh.plugin.json` and this
35
+ changelog already said `3.0.0` — one artifact with two version numbers. `package.json` is now
36
+ `3.0.0`, so the package version, the manifest version and the changelog agree. `0.4.0` is kept
37
+ here as a historical record because npm versions are immutable; `dist-tag dsh-0.1.5` should be
38
+ repointed to `3.0.0` once it is published.
39
+ - **Docs.** README/INSTALL (zh/en/ja/ko) no longer describe the sibling line as living on `main`.
40
+ The 0.1.2 line's release branch is **`legacy/0.1.2`** (npm dist-tag `dsh-0.1.2`, version `0.3.1`);
41
+ `main` is frozen at `0.2.0-beta.1`. The duplicated branch fragment in the INSTALL.zh/ja/ko install
42
+ command is fixed, explicit dist-tag install commands are documented, and "2.x / 3.x" is
43
+ now marked as a **line nickname** rather than a version number.
44
+
45
+ ### Notes
46
+
47
+ - **No source changes** relative to `3.0.0`; `0.4.0` is a packaging-only publish of the same tree.
48
+
49
+ ## 3.0.0 — 2026-09-14
50
+
51
+ ### Changed
52
+
53
+ - **DSH v0.1.5-rc.2 dedicated line (`compat/0.1.5`).** `engines.dsh` and the `dsh-client-*`
54
+ peer ranges narrow to `>=0.1.5-rc.2 <0.2.0-0` (strict-semver prerelease matching means the
55
+ old `>=0.1.0-rc.7` range never matches `0.1.5-rc.2`); `dsh.plugin.json` gains `engines.dsh`.
56
+ The 2.x / 0.2.x line on `main` keeps serving DSH 0.1.0-rc.7 … 0.1.2-rc.1.
57
+ - **`session.events` → `snapshotEvents()`.** DSH 0.1.5 removed the `session.events` array
58
+ accessor (compatibility-guide §20.3). `pause-gate.js` now reads session events through a
59
+ dual-path helper: `snapshotEvents()` first, the legacy `events` array as a defensive
60
+ fallback, `null` (fail-open) when neither exists. Affects `findToolOutcome` and
61
+ `lastUserPrompt` only; event-type matching is unchanged.
62
+
63
+ ### Unchanged
64
+
65
+ - Zero changes on every other integration seam: the self-held webServer prefix route
66
+ (`/session-guard/rpc`), `settings.register`, the `settings.plugin.item` card slot,
67
+ client injections, and `llm.listConfigurableProviders()` are all verified intact against
68
+ the published 0.1.5-rc.2 bundle (`tools/check-api-drift.ps1`, 12/12 required assertions).
69
+
70
+ ### Pending
71
+
72
+ - Live smoke on a real DSH 0.1.5-rc.2 host (same status as the perm-gate 0.1.5 line).
73
+
74
+ ## 0.2.0-beta.2 — 2026-09-13
75
+
76
+ ### Fixed (DSH 0.1.5 compat — `compat/0.1.5` branch)
77
+
78
+ - **Dual-path resume enqueue.** DSH 0.1.5 turns the Inbox into an agent-loop read-only
79
+ projection, so `agent.followup` may no longer exist. The resume flow now tries
80
+ `agent.followup` first, falls back to `agent.send`, and degrades to a warn (never throws)
81
+ when neither is available — a failed enqueue can no longer break resume.
82
+ - **Resume messages carry `source.form: 'instructions'`** per the 0.1.5 `ContextFormed`
83
+ message-source contract (`kind: 'plugin'` is a built-in kind; older DSH versions ignore
84
+ the extra field).
85
+ - **`webServer.register` is wrapped in try/catch**: a failed route registration now logs an
86
+ error instead of throwing out of `apply` and breaking host plugin loading.
87
+ - Verified against the 0.1.5-rc.2 source: the `WebRoute` contract (exact/prefix + SSE) is
88
+ unchanged, so client `fetch('/session-guard/...')` paths need no `/api` prefix.
89
+
90
+ ## 0.2.0-beta.1 — 2026-09-10
91
+
92
+ ### Added
93
+
94
+ - **Step-level gate (`agent/pre-step`).** During peak hours the turn is now held **before** the
95
+ next step's model request instead of being interrupted at a turn boundary: the session keeps
96
+ running until the next `agent/pre-step`, where the gate holds it (setting `stepLevelPause`, on by
97
+ default). The turn resumes **in place** off-peak — no followup message needed. Hold conditions:
98
+ peak (Beijing time) + not weekend + `step > 1` + official target provider (`providerGuard`) +
99
+ not request-held + not bypassed in this peak window. New module `src/step-gate.js` (pure
100
+ `decideStepHold` + hold / release / abort / timeout engine).
101
+ - **`stepResume` port + RPC + `/resume`.** `sessionGuard.stepResume(sessionId, {bypass})`,
102
+ `POST /session-guard/rpc {action:'stepResume'}` and `/resume` all release the gate; a manual
103
+ resume also stops gating that session for the rest of the peak window.
104
+ - **Timeout escalation.** `stepGateTimeoutMs` (default 300000) releases the gate and escalates to a
105
+ turn-level **force** pause, so a long peak neither deadlocks nor drips one step every five minutes.
106
+ - **"⏸ Pause session" button** (client, slot `conversation.input.right`, id `session-guard-pause`,
107
+ order 20 — left of input-traffic's freeze button). It polls `/session-guard/state` once a second,
108
+ stays disabled while nothing is held, and calls `stepResume` when it is. The status badge moved to
109
+ order 40 and now reports the number of step-held sessions.
110
+ - **New settings**: `stepLevelPause`, `stepGateTimeoutMs`.
111
+ - **New state**: `GET /session-guard/state` now returns `paused: { step, turn }` and
112
+ `stepGate: { held, since, bypass }`; `/status` returns `stepHeld`; `/diag` returns `stepGate`.
113
+ The service port's `state().paused` stays boolean for compatibility (new field `pausedStep`).
114
+
115
+ ### Fixed
116
+
117
+ - **Deadlock between a held step and a turn-level pause.** `pauseTask` / `resumeTask` /
118
+ `cancelTask` now release the step gate first: a step hold sits at `agent/pre-step`, where no
119
+ `assistant/message` or `tool/result` can ever arrive, so a `safe` pause used to wait forever and
120
+ never persisted `paused`.
121
+
122
+ ### Changed
123
+
124
+ - **Peak entry no longer interrupts running turns** when `stepLevelPause` is on (`onEnterPeak` arms
125
+ the step gate instead of calling `stopNextTurn`); with it off the previous turn-level behaviour is
126
+ unchanged.
127
+ - input-traffic's freeze button label is now **"Freeze & append"** (`冻结追加` / `凍結して追加` /
128
+ `동결 후 추가`), and its resume label **"Resume & append"** (`恢复追加` / `再開して追加` /
129
+ `재개 후 추가`) — it freezes the turn and keeps queued messages, distinct from the pause button.
130
+ - **The pause button is a toggle now**: "Pause session" / "Resume session" (no disabled grey state).
131
+ Clicking "Pause session" calls the new `stepPause` action, which holds the session at the **next
132
+ step boundary** (step 1 included, regardless of peak or provider); "Resume session" calls
133
+ `stepResume`. New port method `sessionGuard.stepPause(sessionId)`.
134
+ - **SSE push**: new route `GET /session-guard/events?session=<id>` pushes step-gate state changes the
135
+ moment they happen, so peak auto-holds flip the button to "Resume session" without waiting for a
136
+ poll; the 10s `/session-guard/state` poll remains as a fallback. `/state` now reports
137
+ `paused.manual` and `stepGate.manual`.
138
+ - **Styling aligned** with input-traffic's composer button (24px height, 6px radius, 12px font, the
139
+ same border/hover/pressed tokens) for both the pause button and the status badge; styles are
140
+ injected once via `<style data-plugin-css="session-guard-client">`.
141
+
142
+ ## Unreleased
143
+
144
+ ### Added
145
+
146
+ - **Official-source two-dimensional guard (peak × target provider).** Peak hours now block only
147
+ requests whose target route is a DeepSeek official source; local/third-party providers keep
148
+ running. Verdict order: explicit `officialProviders` id list → live `baseURL` endpoint →
149
+ catalog builtin endpoint (pi-ai's `deepseek`) → builtin id (`deepseek-official`); each verdict
150
+ reports `matchedBy`. New modules: `src/provider.js` (pure), `src/provider-directory.js`,
151
+ `src/deferrals.js`, `src/request-guard.js`, `src/targets.js`, `src/wiring.js`.
152
+ - **Request-level backstop (`agent/request`).** Covers sessions started after peak entry and
153
+ sessions switched to an official source mid-run — the 30s tick only handled sessions that were
154
+ already `running` at the transition. Default `hold` mode suspends the request without an error
155
+ and releases it at the exact off-peak instant (`msUntilOffPeak`); `error` mode throws a
156
+ recognizable `PEAK_DEFERRED` failure and records a deferral for off-peak resume.
157
+ - **New settings**: `providerGuard`, `officialProviders`, `officialBaseURLs`, `deferredResume`,
158
+ `deferredResumeText`, `deferredMode`, `deferredMaxHoldMs` (default 6h), `guardSubagents`.
159
+ - **New routes**: `GET /session-guard/provider?provider=<id>` (verdict diagnostics);
160
+ `/session-guard/status` now reports `providerGuard` / `held` / `deferred`;
161
+ `/session-guard/state` reports the session's last target.
162
+ - **Drift guard** `tools/check-api-drift.ps1` asserting the required APIs exist on
163
+ `dsh-v0.1.1-rc.2` / `dsh-v0.1.2-rc.1` / `dsh-v0.1.3-alpha.2` / `dsh-v0.1.5-alpha.1`.
164
+
165
+ ### Changed
166
+
167
+ - **DSH dual-version support (0.1.0-rc.7 … 0.1.2-rc.1).** One artifact now covers both
168
+ `dsh-v0.1.1-rc.2` and `dsh-v0.1.2-rc.1`. The `tool/result` call-id dual read
169
+ (`content[].toolCallId` first, `source.callId` fallback — both forms appear in replay logs of
170
+ both versions) is extracted to the dependency-free `src/tool-call-id.js` with unit tests.
171
+ `dsh.client.inject` no longer names `@deepseek-ai/dsh-client-runtime` (removed in 0.1.2) or
172
+ `@deepseek-ai/dsh-client-ui-slots` (not a dynamic client row); peer ranges widened to
173
+ `>=0.1.0-rc.7 <0.2.0-0` and the removed package dropped. Added `engines.dsh` and a
174
+ version-compatibility table to the README (ZH/EN).
175
+ - **Settings surface stays on the intersection API**: `settings.register` + `settings.get` only;
176
+ `installSection` (0.1.2+) and the removed `installSettingsSection` are never used. Optional
177
+ APIs (`model/selection`, `llm.listConfigurableProviders`, `settings.get`) are feature-probed and
178
+ degrade instead of throwing.
179
+ - **`src/retry.js` short-circuits only the exact `PEAK_DEFERRED` sentinel.** 429 / `RATE_LIMIT` /
180
+ `TRANSPORT` / timeout failures remain transient, so DSH's global retry (`dsh-llm-retry` on
181
+ `agent/request-error`) and this plugin's own retry semantics are unchanged. `dsh-llm-retry`
182
+ itself is never touched.
183
+ - Badge distinguishes "peak · official only" from "peak · all paused".
184
+
185
+ ## 0.1.4 — 2026-09-09
186
+
187
+ ### Changed
188
+
189
+ - **Public beta release** of the dual-version line (`0.1.4-beta.1`): version metadata, README
190
+ compatibility table and package metadata aligned for the beta channel.
191
+
192
+ ## 0.1.3 — 2026-09-09
193
+
194
+ ### Fixed
195
+
196
+ - **package.json encoding restored**: description was corrupted (GB2312 bytes misread as UTF-8); rewritten with correct Chinese text.
197
+ - **Missing metadata**: added `repository`, `homepage` fields.
198
+ - **peerDependencies corrected**: removed pinned `dsh-llm` exact version; added `cordis`, `dsh-client-runtime`, `dsh-client-locale`, `dsh-client-ui-settings`, `dsh-client-ui-slots` as optional peers matching `dsh.client.inject`.
199
+ - Added `dsh.plugin.json` manifest.
200
+
201
+ ## 0.1.2 — 2026-08-28
202
+
203
+ ### Fixed
204
+
205
+ - **Peak timezone fixed**: 峰谷判定固定北京时间 (`BILLING_TIMEZONE`),周末判定用配置时区。
206
+
207
+ ## 0.1.1 — 2026-08-24
208
+
209
+ ### Added
210
+
211
+ - **Backend auto-retry (D9)**: `turn/end` transient failures (error/429/max-tokens) trigger adaptive-backoff `followup(retryText)` resume; permanent failures (auth/balance/model/context limit) stop; user intervention or successful turn resets consecutive failure count.
212
+ - **Freeze/gate yield**: retry skips when `isFrozen(sessionId)` is true (queueLocked / paused / taskControl paused), never bypasses the session gate.
213
+
214
+ ### Changed
215
+
216
+ - `sessionGuard` redundant port now exposes `state(sessionId)` returning `{ queueLocked, lockReason, paused, taskControlAvailable, taskControl }`.
217
+ - HTTP route `GET /session-guard/diag` returns runtime diagnostics including retry state.
218
+
219
+ ### Fixed
220
+
221
+ - Weekend detection now uses `Intl.DateTimeFormat` with the configured timezone instead of bare `getUTCDay()`, fixing an 8-hour boundary bug for Beijing timezone.
222
+
223
+ ## 0.1.0 — 2026-08-18
224
+
225
+ ### Added
226
+
227
+ - Initial release: peak auto-pause (global), weekend mode, per-session freeze/resume via `sessionGuard` redundant port + RPC bridge, custom session gate (`agent.cancel keepInbox + goals.pause + session/event safe boundary + followup resume`), settings panel (Settings → Plugins → session-guard).
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 dsh-session-guard contributors
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 dsh-session-guard contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -34,14 +34,23 @@
34
34
  > | --- | --- | --- | --- | --- |
35
35
  > | 0.1.0-rc.7 ~ 0.1.1-rc.x | ➖ 不在本线(`legacy/0.1.2` 之前的历史版本) | `ctx.settings.register(ns, schema, { base })` | ✅ 形状一致 | ✅ 无平台值导入 |
36
36
  > | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ➖ 不在本线 → 用 `legacy/0.1.2`(npm `@dsh-0.1.2`) | `register` 仍保留(另加 `installSection`) | ✅ 形状一致 | ✅ 无平台值导入 |
37
- > | **0.1.7-rc.1+** | ✅(**本线**,dist-tag `dsh-0.1.7`) | 声明式:Config `.volatile()` 字段由宿主投影成表单,`register` 已删除 | ✅ 事件经 `snapshotEvents()` 双路径读取 | ✅ 客户端设置卡改 `configForms` |
37
+ > | **0.2.0-rc.1+** | ✅(**本线**,dist-tag `dsh-0.2.0`,npm `4.0.0`) | 声明式:Config `.volatile()` 字段由宿主投影成表单,`register` 已删除 | ✅ 事件经 `snapshotEvents()` 双路径读取 | ✅ 客户端设置卡改 `configForms` |
38
+ > | 0.1.7-rc.1+ | ✅(`compat/0.1.7` 分支,dist-tag `dsh-0.1.7`) | 声明式:Config `.volatile()` 字段由宿主投影成表单,`register` 已删除 | ✅ 事件经 `snapshotEvents()` 双路径读取 | ✅ 客户端设置卡改 `configForms` |
38
39
  > | 0.1.5-rc.2 | ✅(`compat/0.1.5` 分支,dist-tag `dsh-0.1.5`) | `register` 仍在(字符串命名空间) | ✅ 事件经 `snapshotEvents()` 双路径读取 | ✅ |
39
40
  >
40
- > **本线身份**:分支 `compat/0.1.7`,npm 版本号 **`3.1.0`**(semver),dist-tag **`dsh-0.1.7`**。
41
- > `package.json` 与 `dsh.plugin.json` 的 `engines.dsh` 与三个 `@deepseek-ai/dsh-client-*`
42
- > peer 线统一为 `>=0.1.7-rc.1 <0.2.0-0`。
41
+ > **本线身份**:分支 `compat/0.2.0`,npm 版本号 **`4.0.0`**(semver),dist-tag **`dsh-0.2.0`**。
42
+ > `package.json` 与 `dsh.plugin.json` 的 `engines.dsh` 与四个 `@deepseek-ai/dsh-client-*`
43
+ > peer 线统一为 `>=0.2.0-rc.1 <0.2.1-0`;`@deepseek-ai/cordis` peer 下限对齐宿主线的 `^4.0.4`。
43
44
  > 历史上 README 曾用「2.x / 3.x」当**线代号**,那是叙述习惯,**不是注册表里可拉取的版本号** ——
44
- > 请一律以 npm 版本号 `0.3.1`(0.1.2 线)、`3.0.0`/`3.0.1`(0.1.5 线)与 `3.1.0`(0.1.7 线)为准。
45
+ > 请一律以 npm 版本号 `0.3.1`(0.1.2 线)、`3.0.0`/`3.0.1`(0.1.5 线)、`3.2.4`(0.1.7 线)与 `4.0.0`(0.2.0 线)为准。
46
+ >
47
+ > **0.2.0 适配(compat/0.2.0 分支,npm 4.0.0)**:
48
+ > 0.2.0-rc.1 对本插件用到的插件 API(manifest/settings/HMR/slot/会话 V4)与 0.1.7 完全兼容,
49
+ > 适配为元数据换代(peer/engines/dist-tag/版本 4.0.0)。随基线一并对齐的工程修复:
50
+ > ① `src/client/family-section.tsx` 补入版本管理(此前仅在本地工作区,纯净 checkout 无法 build);
51
+ > ② eslint 配置去掉对 TS 类型签名误报的核心 `no-unused-vars`,lint 基线可全绿;
52
+ > ③ v4 写路径署名静态闸(`tests/source-kind.test.mjs`)随基线带入本线;
53
+ > ④ `@deepseek-ai/cordis` peer 对齐 `^4.0.4`(0.2.0-rc.1 宿主 UI 包声明 `~4.0.4`,原 `^4.0.1` 不冲突但下限过旧)。
45
54
  >
46
55
  > **其它线的落点**:DSH `0.1.2-rc.x` 宿主请使用分支 **`legacy/0.1.2`**(npm dist-tag
47
56
  > `dsh-0.1.2`,版本 `0.3.1`)。**`main` 已冻结在 `0.2.0-beta.1`,不是 0.1.2 线的发布分支。**
package/cordis.patch.yml CHANGED
@@ -1,10 +1,10 @@
1
- # dsh-session-guard bundle patch:一行装配进 profile。
2
- #
3
- # host 半(src/index.js)注册 sessionGuard 服务(冗余端口,供 input-traffic 适配)、
4
- # 高峰自动会话门(taskControl.pause/resume,缺失时回退锁等待队列)、
5
- # /session-guard/* 路由与 设置→插件→session-guard 子板块(简单开关)。
6
- # 浏览器半(src/client/index.js,经 package.json dsh.client 声明)提供
7
- # 高峰状态徽标 + 无 input-traffic 时的回退冻结按钮。
8
- - insert:
9
- - id: session-guard
10
- name: dsh-session-guard
1
+ # dsh-session-guard bundle patch:一行装配进 profile。
2
+ #
3
+ # host 半(src/index.js)注册 sessionGuard 服务(冗余端口,供 input-traffic 适配)、
4
+ # 高峰自动会话门(taskControl.pause/resume,缺失时回退锁等待队列)、
5
+ # /session-guard/* 路由与 设置→插件→session-guard 子板块(简单开关)。
6
+ # 浏览器半(src/client/index.js,经 package.json dsh.client 声明)提供
7
+ # 高峰状态徽标 + 无 input-traffic 时的回退冻结按钮。
8
+ - insert:
9
+ - id: session-guard
10
+ name: dsh-session-guard