dsh-session-guard 0.3.0 → 0.4.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.ja.md CHANGED
@@ -6,6 +6,45 @@
6
6
  - [日本語 changelog](./CHANGELOG.ja.md)
7
7
  - [한국어 changelog](./CHANGELOG.ko.md)
8
8
 
9
+ ## 3.0.0 — 2026-09-14
10
+
11
+ ### 変更
12
+
13
+ - **DSH v0.1.5-rc.2 専用ライン(`compat/0.1.5`)。** `engines.dsh` と `dsh-client-*` の peer
14
+ 範囲を `>=0.1.5-rc.2 <0.2.0-0` に狭めました(semver のプレリリース規則上、旧
15
+ `>=0.1.0-rc.7` は `0.1.5-rc.2` に一致しません)。`dsh.plugin.json` に `engines.dsh` を追加。
16
+ 旧ホスト向けには `main` 上の 2.x / 0.2.x ラインが継続します。
17
+ - **`session.events` → `snapshotEvents()`。** DSH 0.1.5 で `session.events` 配列アクセサが
18
+ 削除されたため(compatibility-guide §20.3)、`pause-gate.js` はデュアルパスのヘルパーで
19
+ セッションイベントを読みます:まず `snapshotEvents()`、防御的に旧 `events` 配列へフォール
20
+ バック、どちらも無ければ `null`(fail-open)。対象は `findToolOutcome` と `lastUserPrompt`
21
+ のみで、イベント型のマッチングは不変です。
22
+
23
+ ### 変更なし
24
+
25
+ - その他の接続面は一切変更なし:自前の webServer prefix ルート(`/session-guard/rpc`)、
26
+ `settings.register`、`settings.plugin.item` スロット、クライアント注入、
27
+ `llm.listConfigurableProviders()` は公開済み 0.1.5-rc.2 バンドルに対して検証済み
28
+ (`tools/check-api-drift.ps1`、必須アサーション 12/12)。
29
+
30
+ ### 未実施
31
+
32
+ - 実機 DSH 0.1.5-rc.2 ホストでのライブスモーク(perm-gate の 0.1.5 ラインと同じ状態)。
33
+
34
+ ## 0.2.0-beta.2 — 2026-09-13
35
+
36
+ ### 修正(DSH 0.1.5 互換 — `compat/0.1.5` ブランチ)
37
+
38
+ - **レジューム入隊のデュアルパス。** DSH 0.1.5 では Inbox が agent-loop の読み取り専用
39
+ 投影に変わるため、`agent.followup` が存在しない可能性があります。レジューム時は
40
+ `agent.followup` → `agent.send` → warn 降級(例外は投げない)の順で試行します。
41
+ - **レジュームメッセージの `source` に `form: 'instructions'` を追加**(0.1.5 の
42
+ `ContextFormed` 契約。旧バージョンは未知フィールドを無視します)。
43
+ - **`webServer.register` を try/catch で保護**:ルート登録の失敗はログのみで、
44
+ `apply` から例外を投げてホストのプラグイン読み込みを壊しません。
45
+ - 0.1.5-rc.2 ソースに対し `WebRoute`(exact/prefix + SSE)契約が不変であることを確認。
46
+ クライアントの `fetch('/session-guard/...')` に `/api` プレフィックスは不要です。
47
+
9
48
  ## 0.2.0-beta.1 — 2026-09-10
10
49
 
11
50
  ### 追加
package/CHANGELOG.ko.md CHANGED
@@ -6,6 +6,45 @@
6
6
  - [日本語 changelog](./CHANGELOG.ja.md)
7
7
  - [한국어 changelog](./CHANGELOG.ko.md)
8
8
 
9
+ ## 3.0.0 — 2026-09-14
10
+
11
+ ### 변경
12
+
13
+ - **DSH v0.1.5-rc.2 전용 라인(`compat/0.1.5`).** `engines.dsh`와 `dsh-client-*` peer 범위를
14
+ `>=0.1.5-rc.2 <0.2.0-0`으로 좁혔습니다(semver 프리릴리스 규칙상 기존 `>=0.1.0-rc.7`은
15
+ `0.1.5-rc.2`와 일치하지 않음). `dsh.plugin.json`에 `engines.dsh`를 추가했습니다. 구버전
16
+ 호스트용으로는 `main`의 2.x / 0.2.x 라인이 계속 유지됩니다.
17
+ - **`session.events` → `snapshotEvents()`.** DSH 0.1.5에서 `session.events` 배열 접근자가
18
+ 제거됨에 따라(compatibility-guide §20.3) `pause-gate.js`는 듀얼 패스 헬퍼로 세션 이벤트를
19
+ 읽습니다: 우선 `snapshotEvents()`, 방어적으로 구 `events` 배열로 폴백, 둘 다 없으면 `null`
20
+ (fail-open). 대상은 `findToolOutcome`과 `lastUserPrompt` 두 곳뿐이며 이벤트 타입 매칭은
21
+ 불변입니다.
22
+
23
+ ### 변경 없음
24
+
25
+ - 나머지 연동면은 전부 무변경: 자체 webServer prefix 라우트(`/session-guard/rpc`),
26
+ `settings.register`, `settings.plugin.item` 슬롯, 클라이언트 주입,
27
+ `llm.listConfigurableProviders()`는 공개된 0.1.5-rc.2 번들 기준으로 검증 완료
28
+ (`tools/check-api-drift.ps1`, 필수 어설션 12/12).
29
+
30
+ ### 보류
31
+
32
+ - 실제 DSH 0.1.5-rc.2 호스트에서의 라이브 스모크(perm-gate 0.1.5 라인과 동일 상태).
33
+
34
+ ## 0.2.0-beta.2 — 2026-09-13
35
+
36
+ ### 수정 (DSH 0.1.5 호환 — `compat/0.1.5` 브랜치)
37
+
38
+ - **재개 큐잉 듀얼 경로.** DSH 0.1.5에서는 Inbox가 agent-loop의 읽기 전용 프로젝션으로
39
+ 바뀌어 `agent.followup`이 존재하지 않을 수 있습니다. 재개 시 `agent.followup` →
40
+ `agent.send` → warn 강등(예외 없음) 순으로 시도합니다.
41
+ - **재개 메시지 `source`에 `form: 'instructions'` 추가** (0.1.5 `ContextFormed` 계약;
42
+ 구버전은 알 수 없는 필드를 무시합니다).
43
+ - **`webServer.register` try/catch 보호**: 라우트 등록 실패는 로그만 남기고 `apply`에서
44
+ 예외를 던져 호스트 플러그인 로딩을 깨뜨리지 않습니다.
45
+ - 0.1.5-rc.2 소스 기준 `WebRoute`(exact/prefix + SSE) 계약이 불변임을 확인했습니다.
46
+ 클라이언트 `fetch('/session-guard/...')`에 `/api` 접두사는 불필요합니다.
47
+
9
48
  ## 0.2.0-beta.1 — 2026-09-10
10
49
 
11
50
  ### 추가
package/CHANGELOG.md CHANGED
@@ -6,6 +6,47 @@ All notable changes to `dsh-session-guard` are recorded here. Versions follow se
6
6
  - [日本語 changelog](./CHANGELOG.ja.md)
7
7
  - [한국어 changelog](./CHANGELOG.ko.md)
8
8
 
9
+ ## 3.0.0 — 2026-09-14
10
+
11
+ ### Changed
12
+
13
+ - **DSH v0.1.5-rc.2 dedicated line (`compat/0.1.5`).** `engines.dsh` and the `dsh-client-*`
14
+ peer ranges narrow to `>=0.1.5-rc.2 <0.2.0-0` (strict-semver prerelease matching means the
15
+ old `>=0.1.0-rc.7` range never matches `0.1.5-rc.2`); `dsh.plugin.json` gains `engines.dsh`.
16
+ The 2.x / 0.2.x line on `main` keeps serving DSH 0.1.0-rc.7 … 0.1.2-rc.1.
17
+ - **`session.events` → `snapshotEvents()`.** DSH 0.1.5 removed the `session.events` array
18
+ accessor (compatibility-guide §20.3). `pause-gate.js` now reads session events through a
19
+ dual-path helper: `snapshotEvents()` first, the legacy `events` array as a defensive
20
+ fallback, `null` (fail-open) when neither exists. Affects `findToolOutcome` and
21
+ `lastUserPrompt` only; event-type matching is unchanged.
22
+
23
+ ### Unchanged
24
+
25
+ - Zero changes on every other integration seam: the self-held webServer prefix route
26
+ (`/session-guard/rpc`), `settings.register`, the `settings.plugin.item` card slot,
27
+ client injections, and `llm.listConfigurableProviders()` are all verified intact against
28
+ the published 0.1.5-rc.2 bundle (`tools/check-api-drift.ps1`, 12/12 required assertions).
29
+
30
+ ### Pending
31
+
32
+ - Live smoke on a real DSH 0.1.5-rc.2 host (same status as the perm-gate 0.1.5 line).
33
+
34
+ ## 0.2.0-beta.2 — 2026-09-13
35
+
36
+ ### Fixed (DSH 0.1.5 compat — `compat/0.1.5` branch)
37
+
38
+ - **Dual-path resume enqueue.** DSH 0.1.5 turns the Inbox into an agent-loop read-only
39
+ projection, so `agent.followup` may no longer exist. The resume flow now tries
40
+ `agent.followup` first, falls back to `agent.send`, and degrades to a warn (never throws)
41
+ when neither is available — a failed enqueue can no longer break resume.
42
+ - **Resume messages carry `source.form: 'instructions'`** per the 0.1.5 `ContextFormed`
43
+ message-source contract (`kind: 'plugin'` is a built-in kind; older DSH versions ignore
44
+ the extra field).
45
+ - **`webServer.register` is wrapped in try/catch**: a failed route registration now logs an
46
+ error instead of throwing out of `apply` and breaking host plugin loading.
47
+ - Verified against the 0.1.5-rc.2 source: the `WebRoute` contract (exact/prefix + SSE) is
48
+ unchanged, so client `fetch('/session-guard/...')` paths need no `/api` prefix.
49
+
9
50
  ## 0.2.0-beta.1 — 2026-09-10
10
51
 
11
52
  ### Added
package/INSTALL.ja.md CHANGED
@@ -22,9 +22,12 @@ dsh --version
22
22
  ## 1. インストール
23
23
 
24
24
  ```bash
25
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
25
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5#compat/0.1.5
26
26
  ```
27
27
 
28
+ `compat/0.1.5` は DSH `0.1.5-rc.x` 専用ライン(3.x、`engines.dsh: >=0.1.5-rc.2 <0.2.0-0`)。`main` は
29
+ 引き続き DSH `0.1.0-rc.7` – `0.1.2-rc.1`(2.x / 0.2.x)を担当します。
30
+
28
31
  dsh web を再起動し、ページをリフレッシュ。
29
32
 
30
33
  ## 2. 検証
@@ -52,7 +55,7 @@ npm test
52
55
 
53
56
  ```bash
54
57
  dsh plugin --profile web remove dsh-session-guard
55
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
58
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
56
59
  ```
57
60
 
58
61
  dsh web を再起動しページをリフレッシュ。設定は `$DSH_HOME/settings.yaml` の `session-guard`
package/INSTALL.ko.md CHANGED
@@ -22,9 +22,12 @@ dsh --version
22
22
  ## 1. 설치
23
23
 
24
24
  ```bash
25
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
25
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5#compat/0.1.5
26
26
  ```
27
27
 
28
+ `compat/0.1.5`는 DSH `0.1.5-rc.x` 전용 라인(3.x, `engines.dsh: >=0.1.5-rc.2 <0.2.0-0`)입니다. `main`은
29
+ 계속 DSH `0.1.0-rc.7` – `0.1.2-rc.1`(2.x / 0.2.x)을 담당합니다.
30
+
28
31
  dsh web을 재시작하고 페이지를 새로고침.
29
32
 
30
33
  ## 2. 검증
@@ -52,7 +55,7 @@ npm test
52
55
 
53
56
  ```bash
54
57
  dsh plugin --profile web remove dsh-session-guard
55
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
58
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
56
59
  ```
57
60
 
58
61
  dsh web을 재시작하고 페이지를 새로고침. 설정은 `$DSH_HOME/settings.yaml`의 `session-guard`
package/INSTALL.md CHANGED
@@ -24,9 +24,12 @@ dsh --version
24
24
  ## 1. Install
25
25
 
26
26
  ```bash
27
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
27
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
28
28
  ```
29
29
 
30
+ `compat/0.1.5` is the DSH `0.1.5-rc.x` line (3.x, `engines.dsh: >=0.1.5-rc.2 <0.2.0-0`); `main` keeps
31
+ serving DSH `0.1.0-rc.7` – `0.1.2-rc.1` (2.x / 0.2.x).
32
+
30
33
  Restart dsh web and refresh the page.
31
34
 
32
35
  ## 2. Verify
@@ -55,7 +58,7 @@ npm test
55
58
 
56
59
  ```bash
57
60
  dsh plugin --profile web remove dsh-session-guard
58
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
61
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
59
62
  ```
60
63
 
61
64
  Restart dsh web and refresh the page. Settings live in `$DSH_HOME/settings.yaml` under the
package/INSTALL.zh.md CHANGED
@@ -22,9 +22,12 @@ dsh --version
22
22
  ## 1. 安装
23
23
 
24
24
  ```bash
25
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
25
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5#compat/0.1.5
26
26
  ```
27
27
 
28
+ `compat/0.1.5` 是 DSH `0.1.5-rc.x` 专线(3.x,`engines.dsh: >=0.1.5-rc.2 <0.2.0-0`);`main` 继续服务
29
+ DSH `0.1.0-rc.7` – `0.1.2-rc.1`(2.x / 0.2.x)。
30
+
28
31
  重启 dsh web 并刷新页面。
29
32
 
30
33
  ## 2. 验证
@@ -52,7 +55,7 @@ npm test
52
55
 
53
56
  ```bash
54
57
  dsh plugin --profile web remove dsh-session-guard
55
- dsh plugin --profile web add github:drscrewdriver/dsh-session-guard
58
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
56
59
  ```
57
60
 
58
61
  重启 dsh web 并刷新页面。设置在 `$DSH_HOME/settings.yaml` 的 `session-guard` 命名空间下,升级不会丢;
package/README.en.md CHANGED
@@ -30,22 +30,38 @@
30
30
  >
31
31
  > | DSH version | Load | Settings registration | Session events / gate | Client half |
32
32
  > | --- | --- | --- | --- | --- |
33
- > | 0.1.0-rc.7 ~ 0.1.1-rc.x | ✅ | `ctx.settings.register(ns, schema, { base })` | ✅ same shape | ✅ no platform value imports |
34
- > | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ✅ | `register` still present (`installSection` added) | ✅ same shape | ✅ no platform value imports |
35
- > | 0.1.3+ / 0.1.5-alpha.1 | APIs still present (unverified) | `register` unchanged | | ✅ |
33
+ > | 0.1.0-rc.7 ~ 0.1.1-rc.x | ✅ (2.x / 0.2.x line) | `ctx.settings.register(ns, schema, { base })` | ✅ same shape | ✅ no platform value imports |
34
+ > | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ✅ (2.x / 0.2.x line) | `register` still present (`installSection` added) | ✅ same shape | ✅ no platform value imports |
35
+ > | **0.1.5-rc.2** | ✅ (**3.x dedicated line `compat/0.1.5`**) | `register` unchanged (string namespaces) | ✅ events read via `snapshotEvents()` dual path | ✅ |
36
36
  >
37
- > One artifact covers both. `session/event`, `agent.cancel`, `goals.pause`,
37
+ > **3.x is the 0.1.5 dedicated line**: engines/peers narrow to `>=0.1.5-rc.2 <0.2.0-0`;
38
+ > 0.1.0-rc.7~0.1.2 hosts should stay on the 2.x / 0.2.x line (`main`).
39
+ > **0.1.5 adaptation (compat/0.1.5 branch, v3.0.0)**:
40
+ > ① dual-path resume enqueue — 0.1.5 turns Inbox into an agent-loop read-only projection;
41
+ > if `agent.followup` is gone the plugin falls back to `agent.send`, and degrades to a warn
42
+ > (never throws) when neither exists; ② resume messages now carry
43
+ > `source.form: 'instructions'` (0.1.5 ContextFormed contract; older versions ignore it);
44
+ > ③ `webServer.register` is wrapped in try/catch so a failed registration only logs instead
45
+ > of breaking host loading; ④ 0.1.5 removes the `session.events` array accessor — events are
46
+ > now read via `snapshotEvents()` (legacy array kept as fallback), affecting only the
47
+ > `findToolOutcome` / `lastUserPrompt` helper paths.
48
+ > Verified that 0.1.5 `WebRoute` (exact/prefix + SSE) contract is
49
+ > unchanged — client `fetch('/session-guard/...')` needs no `/api` prefix.
50
+ > The 2.x-line table (one artifact, both versions) is kept below. `session/event`, `agent.cancel`, `goals.pause`,
38
51
  > `agent.followup`, `commands.register`, `timer.interval`, `webServer.register`,
39
52
  > `agent/request`, `llm.listConfigurableProviders` and `settings.register/get`
40
53
  > are signature-identical between `dsh-v0.1.1-rc.2` and `dsh-v0.1.2-rc.1`
41
- > (verified through `0.1.5-alpha.1`). The one
54
+ > (3.x verified against `dsh-v0.1.5-rc.2`). The one
42
55
  > seam that needs a dual read is the `tool/result` call id (`content[].toolCallId`
43
56
  > first, `source.callId` as fallback) — both forms appear in replay logs of both
44
57
  > versions. It now lives in `src/tool-call-id.js` with unit tests.
58
+ > Since 0.1.5 the `session.events` array accessor is removed; 3.x reads events
59
+ > through `snapshotEvents()` (with the legacy array as fallback), affecting only
60
+ > the `findToolOutcome` / `lastUserPrompt` helper paths.
45
61
  > The `model/selection` event exists **only on 0.1.2+** and is feature-probed; the
46
62
  > settings surface uses only the `register` + `get` intersection (never
47
63
  > `installSection`, never the removed `installSettingsSection`). Drift guard:
48
- > `tools/check-api-drift.ps1`.
64
+ > `tools/check-api-drift.ps1` (3.x defaults to asserting against `dsh-v0.1.5-rc.2`).
49
65
 
50
66
  > Automatically pause running sessions during peak pricing hours and resume during off-peak/weekend; pair with input-traffic's freeze button for **per-session** locking; backend **auto-retry** yields during freeze/gate. Core based on a custom session gate (`agent.cancel keepInbox + goals.pause + session/event safe boundary + followup resume`), no longer depending on dsh-task-control.
51
67
 
@@ -66,9 +82,12 @@ A cordis plugin assembled via the `dsh plugin` command and a bundle patch — no
66
82
  ## Installation
67
83
 
68
84
  ```bash
69
- dsh plugin --profile web add github:<owner>/dsh-session-guard
85
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
70
86
  ```
71
87
 
88
+ `compat/0.1.5` is the DSH `0.1.5-rc.x` line (3.x); `main` keeps serving DSH `0.1.0-rc.7` – `0.1.2-rc.1`
89
+ (2.x / 0.2.x).
90
+
72
91
  Restart dsh web and refresh the page after installation.
73
92
 
74
93
  ## Settings (Settings → Plugins → session-guard, simple toggles)
package/README.ja.md CHANGED
@@ -45,9 +45,11 @@
45
45
  ## インストール
46
46
 
47
47
  ```bash
48
- dsh plugin --profile web add github:<owner>/dsh-session-guard
48
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
49
49
  ```
50
50
 
51
+ `compat/0.1.5` は DSH `0.1.5-rc.x` 専用ライン(3.x)。`main` は引き続き DSH `0.1.0-rc.7` – `0.1.2-rc.1`(2.x / 0.2.x)を担当します。
52
+
51
53
  インストール後 dsh web を再起動し、ページをリフレッシュ。
52
54
 
53
55
  ## 設定(設定 → プラグイン → session-guard)
package/README.ko.md CHANGED
@@ -45,9 +45,11 @@
45
45
  ## 설치
46
46
 
47
47
  ```bash
48
- dsh plugin --profile web add github:<owner>/dsh-session-guard
48
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
49
49
  ```
50
50
 
51
+ `compat/0.1.5`는 DSH `0.1.5-rc.x` 전용 라인(3.x)입니다. `main`은 계속 DSH `0.1.0-rc.7` – `0.1.2-rc.1`(2.x / 0.2.x)을 담당합니다.
52
+
51
53
  설치 후 dsh web을 재시작하고 페이지를 새로고침.
52
54
 
53
55
  ## 설정 (설정 → 플러그인 → session-guard)
package/README.md CHANGED
@@ -32,19 +32,31 @@
32
32
  >
33
33
  > | DSH 版本 | 加载 | 设置注册 | 会话事件 / 会话门 | 客户端半 |
34
34
  > | --- | --- | --- | --- | --- |
35
- > | 0.1.0-rc.7 ~ 0.1.1-rc.x | | `ctx.settings.register(ns, schema, { base })` | ✅ 形状一致 | ✅ 无平台值导入 |
36
- > | 0.1.2-alpha.2+ / 0.1.2-rc.1 | | `register` 仍保留(另加 `installSection`) | ✅ 形状一致 | ✅ 无平台值导入 |
37
- > | 0.1.3+ / 0.1.5-alpha.1 | 接口仍在(未验证) | `register` 仍在(行号未变) | ✅ | ✅ |
35
+ > | 0.1.0-rc.7 ~ 0.1.1-rc.x | ✅(2.x / 0.2.x 线) | `ctx.settings.register(ns, schema, { base })` | ✅ 形状一致 | ✅ 无平台值导入 |
36
+ > | 0.1.2-alpha.2+ / 0.1.2-rc.1 | ✅(2.x / 0.2.x 线) | `register` 仍保留(另加 `installSection`) | ✅ 形状一致 | ✅ 无平台值导入 |
37
+ > | **0.1.5-rc.2** | ✅(**3.x 专线 `compat/0.1.5`**) | `register` 仍在(字符串命名空间) | ✅ 事件经 `snapshotEvents()` 双路径读取 | ✅ |
38
38
  >
39
- > 一份产物同时支持两版本。`session/event`、`agent.cancel`、`goals.pause`、
39
+ > **3.x 为 0.1.5 专线**:engines/peer 收窄为 `>=0.1.5-rc.2 <0.2.0-0`,0.1.0-rc.7~0.1.2 宿主请继续使用 2.x / 0.2.x 线(`main`)。
40
+ > **0.1.5 适配(compat/0.1.5 分支,v3.0.0)**:
41
+ > ① `agent.followup` 双路径——0.1.5 将 Inbox 改为 agent-loop 只读投影,若 `followup`
42
+ > 不再存在则回退 `agent.send`,两者皆无时 warn 降级不抛错(恢复续跑的兜底);
43
+ > ② 恢复消息 `source` 补 `form: 'instructions'`(0.1.5 ContextFormed 契约,旧版本忽略);
44
+ > ③ `webServer.register` 包 try/catch,注册失败仅记日志不炸宿主加载;
45
+ > ④ 0.1.5 移除 `session.events` 数组访问器,事件读取改为 `snapshotEvents()` 主路径 +
46
+ > 旧数组回退(仅影响 `findToolOutcome` / `lastUserPrompt` 两条辅助路径,事件类型匹配不变)。
47
+ > 已核实 0.1.5 的 `WebRoute`(exact/prefix + SSE)契约不变,客户端
48
+ > `fetch('/session-guard/...')` 无需改 `/api` 前缀。
49
+ > 2.x 线的旧表(一份产物双版本)保留如下。`session/event`、`agent.cancel`、`goals.pause`、
40
50
  > `agent.followup`、`commands.register`、`timer.interval`、`webServer.register`、
41
51
  > `agent/request`、`llm.listConfigurableProviders`、`settings.register/get` 在
42
- > `dsh-v0.1.1-rc.2` 与 `dsh-v0.1.2-rc.1` 之间签名一致(并已核到 `0.1.5-alpha.1`);
52
+ > `dsh-v0.1.1-rc.2` 与 `dsh-v0.1.2-rc.1` 之间签名一致(3.x 已核到 `dsh-v0.1.5-rc.2`);
43
53
  > 唯一需要双读的是 `tool/result` 记录的调用 id 形态(`content[].toolCallId` 优先、
44
54
  > `source.callId` 回退),已抽到 `src/tool-call-id.js` 并配单测——两版本的回放日志都可能出现这两种形态。
55
+ > 0.1.5 起 `session.events` 数组访问器被移除,3.x 经 `snapshotEvents()`(保留旧数组回退)读取,
56
+ > 只影响 `findToolOutcome` / `lastUserPrompt` 两条辅助路径。
45
57
  > `model/selection` 事件**仅 0.1.2+**,只做切模型加速且必须特性探测;设置面只用
46
58
  > `register` + `get` 交集(不碰 `installSection` / 已移除的 `installSettingsSection`)。
47
- > 漂移守卫脚本:`tools/check-api-drift.ps1`(对四个 tag 断言必需接口存在)。
59
+ > 漂移守卫脚本:`tools/check-api-drift.ps1`(3.x 默认对 `dsh-v0.1.5-rc.2` 断言必需接口存在)。
48
60
 
49
61
  > 高峰时段自动暂停运行中的会话、低峰/周末自动续跑;配合 input-traffic 的冻结按钮做到**会话级**锁定;后端**自动重试**在冻结/门控期间让路。核心基于**自研会话门**(`agent.cancel keepInbox + goals.pause + session/event 安全边界 + followup 续跑`),不再依赖 dsh-task-control。
50
62
 
@@ -74,9 +86,11 @@
74
86
  ## 安装
75
87
 
76
88
  ```bash
77
- dsh plugin --profile web add github:<owner>/dsh-session-guard
89
+ dsh plugin --profile web add github:drscrewdriver/dsh-session-guard#compat/0.1.5
78
90
  ```
79
91
 
92
+ `compat/0.1.5` 是 DSH `0.1.5-rc.x` 专线(3.x);`main` 继续服务 DSH `0.1.0-rc.7` – `0.1.2-rc.1`(2.x / 0.2.x)。
93
+
80
94
  装后重启 dsh web 并刷新页面。
81
95
 
82
96
  ## 设置(设置 → 插件 → session-guard,简单开关)
package/dsh.plugin.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-session-guard",
3
- "version": "0.2.0-beta.1",
3
+ "version": "3.0.0",
4
4
  "main": "./src/index.js",
5
5
  "description": "高峰自动会话门:step 级门控 + 周末模式 + 高峰自动暂停 + 官方源二维判定 + 会话级冻结 + 后端自动重试。",
6
+ "engines": {
7
+ "dsh": ">=0.1.5-rc.2 <0.2.0-0"
8
+ },
6
9
  "contributes": {
7
10
  "tools": [],
8
11
  "skills": []
package/package.json CHANGED
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "name": "dsh-session-guard",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org",
6
6
  "access": "public",
7
- "tag": "dsh-0.1.2"
7
+ "tag": "dsh-0.1.5"
8
8
  },
9
9
  "type": "module",
10
10
  "description": "高峰自动会话门:周末模式 + 高峰自动暂停 + 官方源二维判定 + 会话级冻结 + 后端自动重试。",
@@ -23,9 +23,9 @@
23
23
  "license": "MIT",
24
24
  "repository": {
25
25
  "type": "git",
26
- "url": "git+https://github.com/drscrewdriver/session-guard.git"
26
+ "url": "git+https://github.com/drscrewdriver/dsh-session-guard.git"
27
27
  },
28
- "homepage": "https://github.com/drscrewdriver/session-guard",
28
+ "homepage": "https://github.com/drscrewdriver/dsh-session-guard",
29
29
  "main": "src/index.js",
30
30
  "exports": {
31
31
  ".": "./src/index.js",
@@ -70,16 +70,16 @@
70
70
  },
71
71
  "engines": {
72
72
  "node": ">=20",
73
- "dsh": ">=0.1.0-rc.7 <0.2.0-0"
73
+ "dsh": ">=0.1.5-rc.2 <0.2.0-0"
74
74
  },
75
75
  "dependencies": {
76
76
  "@deepseek-ai/schemastery": "^3.18.1"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "@deepseek-ai/cordis": "^4.0.1",
80
- "@deepseek-ai/dsh-client-locale": ">=0.1.0-rc.7 <0.2.0-0",
81
- "@deepseek-ai/dsh-client-ui-settings": ">=0.1.0-rc.7 <0.2.0-0",
82
- "@deepseek-ai/dsh-client-ui-slots": ">=0.1.0-rc.7 <0.2.0-0"
80
+ "@deepseek-ai/dsh-client-locale": ">=0.1.5-rc.2 <0.2.0-0",
81
+ "@deepseek-ai/dsh-client-ui-settings": ">=0.1.5-rc.2 <0.2.0-0",
82
+ "@deepseek-ai/dsh-client-ui-slots": ">=0.1.5-rc.2 <0.2.0-0"
83
83
  },
84
84
  "peerDependenciesMeta": {
85
85
  "@deepseek-ai/cordis": {
package/src/index.js CHANGED
@@ -220,8 +220,12 @@ export function apply(ctx) {
220
220
  tick()
221
221
 
222
222
  // ── HTTP 路由 ──
223
+ // 0.1.5 compat:注册失败只记日志,不让 apply 抛错炸掉宿主插件加载(#5926 教训)。
224
+ // webServer.register 前缀路由 + SSE 在 0.1.5 契约不变(packages/host/webserver WebRoute)。
223
225
  if (ctx.webServer && typeof ctx.webServer.register === 'function') {
224
- ctx.effect(() => ctx.webServer.register({
226
+ ctx.effect(() => {
227
+ try {
228
+ return ctx.webServer.register({
225
229
  kind: 'prefix',
226
230
  path: '/session-guard',
227
231
  handler: async (req, res) => {
@@ -400,6 +404,10 @@ export function apply(ctx) {
400
404
  res.end(JSON.stringify({ ok: false, error: String(e && e.message || e) }))
401
405
  }
402
406
  },
403
- }), 'session-guard: routes')
407
+ })
408
+ } catch (e) {
409
+ ctx.logger?.error?.(`[session-guard] webServer.register failed (routes disabled): ${String(e && e.message || e)}`)
410
+ }
411
+ }, 'session-guard: routes')
404
412
  }
405
413
  }
package/src/pause-gate.js CHANGED
@@ -46,6 +46,36 @@ export function createPluginUserMessage({ content, source }) {
46
46
  return { id, role: 'user', content, source }
47
47
  }
48
48
 
49
+ /**
50
+ * 恢复消息入队(0.1.5 compat 双路径)。
51
+ *
52
+ * 0.1.5 把 Inbox 从独立服务改为 agent-loop 只读投影(消息入队走 session 事件),
53
+ * `agent.followup` 是否保留无法静态确认,因此运行时检测:
54
+ * 1. `agent.followup(msg)` —— ≤0.1.4 与(若保留的)0.1.5 首选路径;
55
+ * 2. `agent.send(msg)` —— followup 被移除时的回退;
56
+ * 3. 两者皆无 —— 记 warn 并返回 false,绝不让 resume 因入队失败而抛错。
57
+ * @param {object} agent 目标 agent(live agent)
58
+ * @param {{id: string, role: 'user', content: unknown, source: object}} message 本地构造的 user 消息
59
+ * @param {object} [ctx] host context(仅用于 logger)
60
+ * @returns {boolean} 是否成功入队
61
+ */
62
+ export function enqueueFollowup(agent, message, ctx) {
63
+ if (typeof agent.followup === 'function') {
64
+ agent.followup(message)
65
+ return true
66
+ }
67
+ if (typeof agent.send === 'function') {
68
+ agent.send(message)
69
+ return true
70
+ }
71
+ try {
72
+ ctx?.logger?.warn?.('[session-guard] agent has no followup/send; resume message dropped')
73
+ } catch {
74
+ /* logger 不可用时静默 */
75
+ }
76
+ return false
77
+ }
78
+
49
79
  /**
50
80
  * @param {object} deps
51
81
  * @param {object} deps.ctx host context(含 agents.get / get / logger,可选 goals)
@@ -132,11 +162,31 @@ export function createPauseGate({ ctx, pauseStore, pluginId = 'session-guard', m
132
162
  * 两种形态在 DSH 0.1.1-rc.2 与 0.1.2-rc.1 的回放日志里都保留,勿改成单读。
133
163
  */
134
164
 
165
+ /**
166
+ * 读取会话事件数组。DSH 0.1.5 移除了 `session.events` 数组访问器,改为
167
+ * `snapshotEvents()`(compatibility-guide §20.3 实测表);本专线以
168
+ * snapshotEvents() 为主,events 数组仅作回退防御,两者皆缺时返回 null
169
+ * (fail-open,与既有防御语义一致)。
170
+ */
171
+ function readSessionEvents(session) {
172
+ if (!session) return null
173
+ if (typeof session.snapshotEvents === 'function') {
174
+ try {
175
+ const events = session.snapshotEvents()
176
+ return Array.isArray(events) ? events : null
177
+ } catch {
178
+ return null
179
+ }
180
+ }
181
+ return Array.isArray(session.events) ? session.events : null
182
+ }
183
+
135
184
  /** 在 session log 查一个中断工具的实际结果(kernel 会 drain 已启动工具到 tool/result)。 */
136
185
  function findToolOutcome(agent, callId) {
137
- if (!agent?.session?.events) return null
186
+ const events = readSessionEvents(agent?.session)
187
+ if (!events) return null
138
188
  let outcome = null
139
- for (const event of agent.session.events) {
189
+ for (const event of events) {
140
190
  if (event.type === 'tool/result') {
141
191
  const message = event.data?.message ?? {}
142
192
  const block = (Array.isArray(message.content) ? message.content : []).find((b) => b?.type === 'tool-result')
@@ -162,9 +212,10 @@ export function createPauseGate({ ctx, pauseStore, pluginId = 'session-guard', m
162
212
  }
163
213
 
164
214
  function lastUserPrompt(agent) {
165
- if (!agent?.session?.events) return null
166
- for (let index = agent.session.events.length - 1; index >= 0; index -= 1) {
167
- const event = agent.session.events[index]
215
+ const events = readSessionEvents(agent?.session)
216
+ if (!events) return null
217
+ for (let index = events.length - 1; index >= 0; index -= 1) {
218
+ const event = events[index]
168
219
  if (event.type !== 'user/message') continue
169
220
  if (event.data?.source?.kind !== 'user') continue
170
221
  return event.data.content ?? null
@@ -286,7 +337,8 @@ export function createPauseGate({ ctx, pauseStore, pluginId = 'session-guard', m
286
337
  state.pendingPause.delete(sessionId)
287
338
  const current = currentPause(sessionId)
288
339
  if (!current.paused) return { kind: 'success', text: 'no paused task to resume' }
289
- const followup = (blocks) => agent.followup(makeFollowupMessage({ content: blocks, source: { kind: 'plugin', plugin: pluginId } }))
340
+ // 0.1.5 compat:source form: 'instructions'(0.1.5 ContextFormed 契约;旧版本忽略未知字段)
341
+ const followup = (blocks) => enqueueFollowup(agent, makeFollowupMessage({ content: blocks, source: { kind: 'plugin', plugin: pluginId, form: 'instructions' } }), ctx)
290
342
 
291
343
  if (current.forced) {
292
344
  const tool = current.interruptedTool