@telosmaylx/dsh-session-notify 0.1.9 → 0.1.10

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/README.ja.md ADDED
@@ -0,0 +1,590 @@
1
+ <div align="center">
2
+
3
+ # dsh-session-notify
4
+
5
+ [简体中文](README.md) · [English](README.en.md) · [繁體中文](README.zh-TW.md) · **日本語** · [한국어](README.ko.md)
6
+
7
+ **DSH(DeepSeek Harness)セッション完了通知プラグイン —— 各ターンが終わったら、完了状態があなたの方へ来る。画面をじっと見て待つ必要はありません。**
8
+
9
+ [![npm version](https://img.shields.io/npm/v/@telosmaylx/dsh-session-notify)](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
10
+ [![npm downloads](https://img.shields.io/npm/dm/@telosmaylx/dsh-session-notify)](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
11
+ [![license](https://img.shields.io/npm/l/@telosmaylx/dsh-session-notify)](./LICENSE)
12
+ [![node](https://img.shields.io/node/v/@telosmaylx/dsh-session-notify)](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
13
+ [![DSH](https://img.shields.io/badge/DSH-Web%20Profile-4D6BFE)](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
14
+ [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](https://github.com/TelosmaYLX/dsh-session-notify/pulls)
15
+
16
+ 各ターンの会話終了時に、「完了 / エラー / ブロック / 上限到達」を所要時間・トークン消費とともにセッションログへ書き込み、ブラウザのシステム通知とページ内トーストをプッシュします。5 言語、ビジュアルなメッセージテンプレートエディタ、カスタムプリセットライブラリを内蔵。キャッシュヒット率と生成速度は公式プロジェクションから取得し、ステータスバーと同じ口径です。
17
+
18
+ </div>
19
+
20
+ ---
21
+
22
+ ## 目次
23
+
24
+ - [機能概要](#機能概要)
25
+ - [環境要件](#環境要件)
26
+ - [インストール](#インストール)
27
+ - [アンインストール](#アンインストール)
28
+ - [クイックスタート](#クイックスタート)
29
+ - [通知の動作](#通知の動作)
30
+ - [トリガー条件](#トリガー条件)
31
+ - [通知本文の取得元](#通知本文の取得元)
32
+ - [通知の例](#通知の例)
33
+ - [通知の権限](#通知の権限)
34
+ - [設定](#設定)
35
+ - [設定パネル](#設定パネル)
36
+ - [メッセージテンプレートとプレースホルダー](#メッセージテンプレートとプレースホルダー)
37
+ - [プリセットシステム](#プリセットシステム)
38
+ - [ホスト設定項目](#ホスト設定項目)
39
+ - [動作原理](#動作原理)
40
+ - [プロジェクト構成](#プロジェクト構成)
41
+ - [開発とデバッグ](#開発とデバッグ)
42
+ - [よくある質問](#よくある質問)
43
+ - [更新履歴](#更新履歴)
44
+ - [コントリビューション](#コントリビューション)
45
+ - [関連リンク](#関連リンク)
46
+ - [ライセンス](#ライセンス)
47
+
48
+ ---
49
+
50
+ ## 機能概要
51
+
52
+ ### 3 チャンネルで通知、取りこぼしなし
53
+
54
+ | チャンネル | 形式 | 説明 |
55
+ | --- | --- | --- |
56
+ | セッション内システムメッセージ | 折りたたみ可能な通知行 | 各ターン終了時に、終了理由・所要時間・消費量をプラグイン由来のシステムメッセージとしてセッションログへ追加し、JSONL とともに保存。セッションを復元・リプレイした後も表示されます。 |
57
+ | ブラウザシステム通知 | Web Notification | ネイティブポップアップ。各完了イベントで独立した `tag`(`dsh-session-notify:<timestamp>`)を使用するため、前回と置き換わらず、グループ項目に折りたたまれません。通知をクリックするとウィンドウにフォーカスが戻ります。 |
58
+ | ページ内トースト | 右下のフローティングポップアップ | 常に表示されるセーフティネット。システム通知がプラットフォームにサイレント化されたり、権限が拒否されたり、環境が非対応の場合でも、目に見えるフィードバックを提供します。同画面に最大 3 件(超えた場合は最古のものを削除)、10 秒で自動的に消え、クリックで閉じます。 |
59
+
60
+ ### バックグラウンドセッションを完全カバー
61
+
62
+ - ホストはすべてのセッション(バックグラウンド、未表示ウィンドウを含む)について「最新の通知本文」のセッションプロジェクションを維持します(key = `session-complete-notify`)。プッシュ本文はセッション間で一貫し、たまたまそのウィンドウを開いているかどうかに依存しません。
63
+ - クライアントはセッションリストのスナップショットから全セッションの `running` ビットを監視し、`true → false` のエッジでプッシュをトリガーします。公式サイドバー通知と同じ戦略です(初回監視ではベースラインを記録するだけで、すでに idle のセッションには追って送信しません)。
64
+
65
+ ### 一言一句までカスタマイズ可能
66
+
67
+ - **5 言語**:簡体中文、繁體中文、English、日本語、한국어 —— 通知メッセージ、所要時間・消費量の表現、設定パネルの UI がすべて言語に応じて切り替わります(切り替え時に即再レンダリング)。
68
+ - **ビジュアルテンプレートエディタ**(Chip カプセルエディタ):動的情報をインラインカプセルとしてレンダリング(プレースホルダーコードは露出しません)。「+ 情報を挿入」でカーソル位置に挿入(テキストの途中にも挿入可)、カプセルをクリックで削除、各欄にリアルタイムプレビュー(情報がサンプル値として本文に流れ込みます)。
69
+ - **プリセットシステム**:「デフォルト」プリセットをベースラインとして内蔵。現在の設定をカスタムプリセットとして保存可能(`localStorage` に永続化)。自動採番される無名プリセット(`未命名`、`未命名 2`…)、「出自:xxx · 変更あり」の出所表示、プリセット削除に対応。
70
+ - **プッシュタイトルテンプレート**:空欄の場合は各理由でデフォルトタイトルを使用(完了=タスク完了 / エラー=タスクエラー / …)。`{title}` はセッションタイトルを参照します。
71
+
72
+ ### 公式の口径と同じソース
73
+
74
+ - **キャッシュヒット率**は公式 `tokenUsage` プロジェクションから取得:キャッシュ読み込み /(非キャッシュ入力 + キャッシュ読み込み + キャッシュ書き込み)。
75
+ - **生成速度**は公式 `sessionStats` プロジェクションから取得:出力トークン ÷ デコード所要時間。
76
+ - どちらも dsh-web-ui のステータスバーと完全に同じ口径で、待機・準備・ツール実行時間は含みません。プロジェクションが利用不可、またはデータが未準備の場合は、ローカルの使用量集計による推定に自動フォールバックします。
77
+
78
+ > [!NOTE]
79
+ > キャッシュヒット率と速度は、カスタムテンプレートで `{cache}`、`{tps}` プレースホルダーを挿入した場合にのみ表示されます。内蔵デフォルトメッセージを使用する場合、本文には所要時間と消費量のみが含まれます。
80
+
81
+ ### エンジニアリング品質
82
+
83
+ - **リアルタイムイベントのみに反応**:resume、replay で古い通知を再生せず、セッション読み込み時に画面をスパムしません。
84
+ - **自己ループ免疫**:プラグインが追加するメッセージタイプ(`user/message`)と、自身が監視する対象(`turn/*`)は交差しません。
85
+ - **外部依存ゼロ**:ホスト側で裸の import がゼロ。UserMessage は `dsh-llm` の `createUserMessage` 契約に従って手作業で構築。純粋ロジック層(`lib/core.js`)は依存ゼロで、単独でテスト可能です。
86
+ - **Cordis effect の規律**:リトライタイマーを `ctx.effect()` でラップし `clearTimeout` disposer を返すため、fiber のアンロードに伴い登録が自動解除され、HMR ホットリロードに対しても安全です。
87
+ - **インストール即マウント**:公式 `dsh.bundle` manifest を宣言しており、`dsh plugin add` の 1 コマンドでインストール完了後すぐ使用可能。手書きの patch は不要です。
88
+
89
+ ---
90
+
91
+ ## 環境要件
92
+
93
+ | 依存 | 要件 |
94
+ | --- | --- |
95
+ | DSH(DeepSeek Harness) | Web profile でデプロイ。公式 base bundle にはデフォルトで `@deepseek-ai/dsh-settings`(設定名前空間)とセッションプロジェクションが含まれており、追加設定は不要です |
96
+ | cordis | `>=4.0.0-rc <5`(peer dependency、ホスト側で提供) |
97
+ | Node.js | `>=22`(ホスト側) |
98
+ | ブラウザ | Web Notification 対応ならシステム通知あり。非対応、権限拒否、サイレント化の場合はトーストでフォールバック |
99
+
100
+ ---
101
+
102
+ ## インストール
103
+
104
+ > [!WARNING]
105
+ > 裸の `npm install` はパッケージを依存ツリーに追加するだけで、**プラグインを登録しません** —— これは DSH 公式の設計です(`npm install only adds the dependency; it does not register the plugin`)。自動マウントの唯一の公式手段は `dsh plugin add`:パッケージ内の `dsh.bundle` manifest(本プラグインは 0.1.3 以降で宣言、リポジトリ直下の `cordis.patch.yml` を指します)を読み取り、自動適用します。
106
+
107
+ ### 方法 1:dsh plugin add(推奨)
108
+
109
+ パッケージのインストールと同時に `cordis.patch.yml` を自動適用し、プラグインを profile の組み立てにマウントします(host のイベント購読 + client の起動グラフ注入)。
110
+
111
+ ```bash
112
+ dsh plugin --profile web add @telosmaylx/dsh-session-notify
113
+ ```
114
+
115
+ ### 方法 2:GitHub リポジトリからインストール
116
+
117
+ ```bash
118
+ dsh plugin add github:TelosmaYLX/dsh-session-notify
119
+ ```
120
+
121
+ DSH Web GUI のセッション内で実行することもできます:
122
+
123
+ ```bash
124
+ dev_install_package github=TelosmaYLX/dsh-session-notify
125
+ ```
126
+
127
+ ### 方法 3:ローカルディレクトリのホットインストール(開発用)
128
+
129
+ パスを自分のクローン先ディレクトリに置き換え、DSH Web GUI のセッション内で実行します:
130
+
131
+ ```bash
132
+ dev_install_package dir=/あなたの/クローン先/dsh-session-notify
133
+ ```
134
+
135
+ ### 方法 4:npm パッケージの手動インストール
136
+
137
+ まずパッケージングします:
138
+
139
+ ```bash
140
+ npm pack @telosmaylx/dsh-session-notify
141
+ ```
142
+
143
+ 解凍後、ディレクトリを指定してインストールします(DSH Web GUI のセッション内で実行):
144
+
145
+ ```bash
146
+ dev_install_package dir=/解凍/ディレクトリ/package
147
+ ```
148
+
149
+ ### 方法 5:cordis patch の手動適用(インストーラ非依存)
150
+
151
+ `~/.dsh/profiles/web/cordis.patch.yml` に追記します:
152
+
153
+ ```yaml
154
+ - insert:
155
+ - id: dsh-session-notify
156
+ name: '@telosmaylx/dsh-session-notify'
157
+ config: {}
158
+ ```
159
+
160
+ > [!IMPORTANT]
161
+ > どの方法でも、インストール後は**ブラウザのページを 1 回リロード**する必要があります —— クライアント bundle は `__DSH_BOOT__` 起動グラフから注入されるためです。
162
+
163
+ ## アンインストール
164
+
165
+ 1 コマンドでプラグインとそのマウントを削除します(`cordis.patch.yml` から insert エントリを自動的に削除):
166
+
167
+ ```bash
168
+ dsh plugin --profile web remove @telosmaylx/dsh-session-notify
169
+ ```
170
+
171
+ > [!NOTE]
172
+ > 手動インストール(方法 4 / 5)の場合は、`~/.dsh/profiles/web/cordis.patch.yml` から該当の insert エントリを削除し、ページをリロードしてください。
173
+
174
+ ### アンインストール時に自動クリーンアップされる内容
175
+
176
+ プラグインは完全なライフサイクル終了処理を実装しています(Cordis effect の規律)。アンインストール / 無効化 / HMR ホットリロード時:
177
+
178
+ | 側面 | 自動解放されるリソース |
179
+ | --- | --- |
180
+ | host | `session/event` イベント購読、settings 名前空間、セッションプロジェクション、設定登録リトライタイマー(`ctx.effect` でラップ)。アンインストールフラグを立て、スケジュール済みのマイクロタスク追加を抑制します |
181
+ | client | セッションリスト購読、完了プッシュ本文のポーリングタイマー、`window.__dsch_notify_debug` デバッグフック(参照で削除、クロージャリーク防止)、ページ内トーストコンテナの DOM |
182
+
183
+ ### アンインストール後も保持されるデータ
184
+
185
+ - **設定構成**(言語、メッセージテンプレート)は settings ドキュメントに残り、再インストール後に自動復元されます。
186
+ - **カスタムプリセット**はブラウザの `localStorage`(`dsh-scn-custom-presets`)に保存され、再インストール後も残ります。
187
+ - 過去のセッションに追加済みのシステムメッセージと JSONL ログは**ロールバックされません**(これらはセッションデータの一部であり、公式サイドバー通知と同じ意味づけです)。
188
+
189
+ ---
190
+
191
+ ## クイックスタート
192
+
193
+ 1. 上記いずれかの方法でインストールし、ページをリロードします。
194
+ 2. 任意の 1 ターンの会話を開始し、終了を待ちます —— 右下にトーストが表示され、ブラウザにシステム通知が届き、セッションログに折りたたみ可能なシステム通知行が現れます。
195
+ 3. 初めて完了イベントを受け取ったとき、ブラウザが通知権限を要求します(ページごとに 1 回のみ)。許可すると、以降の完了でシステム通知が届きます。
196
+ 4. **設定 → プラグイン → セッション完了通知**を開き、言語の切り替え、メッセージテンプレートの編集、プリセットの保存を行います。保存後、「クリックでリロード」をクリックしてホストとクライアントの両側で読み直すと、新しい設定が有効になります。
197
+
198
+ インストール直後は、セッションログに次のような折りたたみ可能な通知行が表示されます:
199
+
200
+ ```text
201
+ 会话「重构登录模块」已完成(用时 1 分 12 秒,消耗 1,240 输入 / 3,560 输出)。
202
+ ```
203
+
204
+ > デフォルトメッセージは「セッション」の後にセッションタイトルのラベル(`{title}`)を埋め込みます。セッションにタイトルがない場合は「セッション完了」に自動フォールバックします。
205
+
206
+ ---
207
+
208
+ ## 通知の動作
209
+
210
+ ### トリガー条件
211
+
212
+ 各ターンの会話終了(`turn/end`)時に終了理由で判定し、ホワイトリストに一致すれば通知します:
213
+
214
+ | 終了理由 | 意味 | デフォルト |
215
+ | --- | --- | --- |
216
+ | `completed` | セッションが正常に完了 | 通知 |
217
+ | `aborted` | セッション中止 | 通知 |
218
+ | `blocked` | セッションがブロック | 通知 |
219
+ | `error` | セッションでエラー(エラー詳細付き、超長は切り詰め) | 通知 |
220
+ | `max-tokens` | 出力トークン上限に到達 | 通知 |
221
+ | `interrupted` | 中断(クラッシュ復旧後に永続化バックエンドが書き足す孤児ターンのクローズマーカー) | 通知しない(設定で追加可) |
222
+
223
+ **サブエージェントセッションはデフォルトでスキップ**(`header.origin === 'subagent'` または `delegationDepth > 0`)—— サブエージェントは親セッションがオーケストレーションするため、ターンごとの通知はノイズになります。ホスト設定でスキップを無効化できます。
224
+
225
+ ### 通知本文の取得元
226
+
227
+ クライアントはセッションリストで `running: true → false` のエッジを観測したときにプッシュします。本文は以下の優先順位で取得します(最大 6 秒のポーリング、400ms 間隔):
228
+
229
+ 1. **ホストプロジェクション**(key = `session-complete-notify`)—— すべてのセッションにあり、バックグラウンドセッションでも全文を取得できます。
230
+ 2. **セッションイベントウィンドウ内の notice ノード**(`kind=context` + `form=notice`)—— 表示中のセッションで、保存後すぐに利用可能。
231
+ 3. **フォールバック** —— 「詳細はセッション内のシステムメッセージを参照」+ ワークスペース情報(`cwd` の最後のセグメント)。
232
+
233
+ ### 通知の例
234
+
235
+ 以下はすべて `lib/core.js` の `buildNotice` が実際に生成したものです。デフォルトメッセージは終了理由ごとに**表現を変えています**(画一的な構文ではありません):
236
+
237
+ 簡体中文のデフォルトメッセージ:
238
+
239
+ ```text
240
+ 会话「重构登录模块」已完成(用时 3 分 25 秒,消耗 12,400 输入 / 35,600 输出)。 ← 完成:括号紧凑式 + 内嵌会话标题
241
+ 会话「重构登录模块」已中止。用时 3 分 25 秒,消耗 12,400 输入 / 35,600 输出。 ← 中止:句号拆句
242
+ 会话「重构登录模块」被阻塞。用时 3 分 25 秒,消耗 12,400 输入 / 35,600 输出。 ← 阻塞:句号拆句
243
+ 会话「重构登录模块」达到输出上限。用时 3 分 25 秒,消耗 12,400 输入 / 35,600 输出,建议拆分任务后重试。 ← 上限:附建议
244
+ ```
245
+
246
+ > セッションにタイトルがない(`titleValue` が空)場合は、タイトルなしの構文に自動フォールバックします。例:「セッション完了(所要時間 …)」。
247
+
248
+ エラー時はエラー詳細を前に置きます(単行化、40 文字超は切り詰め):
249
+
250
+ ```text
251
+ 会话「重构登录模块」出错:connection timeout(用时 12 秒)。
252
+ ```
253
+
254
+ English のデフォルトメッセージ(セッションタイトルは二重引用符):
255
+
256
+ ```text
257
+ Session "重构登录模块" completed (took 3m25s, used 12,400 in / 35,600 out).
258
+ Session "重构登录模块" hit the output-token cap. Took 3m25s, used 12,400 in / 35,600 out — consider splitting the task.
259
+ ```
260
+
261
+ カスタムテンプレート(設定パネルで編集。この例ではすべての情報枠を使用):
262
+
263
+ ```text
264
+ {title} 干完了!用时 {duration},消耗 {usage},缓存命中 {cache},速度 {tps}
265
+ ```
266
+
267
+ レンダリング結果:
268
+
269
+ ```text
270
+ 重构登录模块 干完了!用时 3 分 25 秒,消耗 103,600 输入 / 35,600 输出,缓存命中 96.5%,速度 92 tok/s
271
+ ```
272
+
273
+ 5 言語での同じイベント:
274
+
275
+ ```text
276
+ 会话「重构登录模块」已完成(用时 3 分 25 秒,消耗 1,240 输入 / 3,560 输出)。
277
+ 會話「重構登入模組」已完成(用時 3 分 25 秒,消耗 1,240 輸入 / 3,560 輸出)。
278
+ Session "重构登录模块" completed (took 3m25s, used 1,240 in / 3,560 out).
279
+ セッション「重构登录模块」完了(所要 3 分 25 秒、消費 1,240 入力 / 3,560 出力)。
280
+ 세션「重构登录模块」 완료(소요 3분 25초, 소모 1,240 입력 / 3,560 출력)。
281
+ ```
282
+
283
+ ### 通知の権限
284
+
285
+ | 権限ステータス | 動作 |
286
+ | --- | --- |
287
+ | `default`(未決定) | 完了イベントではトーストのみ送信。設定パネルの「通知権限」エリアに「許可をリクエスト」ボタンを提供(**ユーザージェスチャ内でリクエスト**——Chromium はジェスチャ以外の自動リクエストを無視するため、プラグインは自動リクエストを行いません) |
288
+ | `granted` | 「プッシュ方式」に従ってシステム通知を送信(独立した tag、互いに上書きしません) |
289
+ | `denied`(ブラウザでブロック) | トーストのみ。設定パネルにアドレスバーの操作ガイドを表示(権限アイコン → サイト設定 → 通知 → 許可) |
290
+ | `undefined`(非セキュアコンテキスト / 非対応) | トーストのみ。「ページ内表示のみ」への切り替えを推奨 |
291
+
292
+ ---
293
+
294
+ ## 設定
295
+
296
+ ほとんどの設定は **DSH Web UI → 設定 → プラグイン → セッション完了通知** パネルで行います(保存後、「クリックでリロード」を押すと有効になります)。「トリガー理由のホワイトリスト」と「サブエージェントをスキップ」の 2 項目のみ、ホストの `cordis.patch.yml` の `config` で設定します。
297
+
298
+ ### 設定パネル
299
+
300
+ パネルは公式の「設定 → プラグイン」パネルに登録されます(`settings.plugin.item` keyed slot、key = `session-complete-notify`)。スタイルはネイティブプラグインカードを 1 つ 1 つ再現しています(12px の角丸、開閉、回転する chevron、footer のステータス表示 + ゴーストボタン + メインカラーの保存ボタン):
301
+
302
+ | エリア | 内容 |
303
+ | --- | --- |
304
+ | プリセット | ドロップダウンで内蔵またはカスタムプリセットを選択。「新規作成」で現在の設定をカスタムプリセットとして保存。現在のプリセットは「削除」可能 |
305
+ | 言語 | 5 言語の単一選択。切り替えでパネル全体が即再レンダリング |
306
+ | プッシュ方式 | 3 択:デュアルチャンネル(システム通知 + ページ内表示、デフォルト)/ システム通知のみ / ページ内表示のみ |
307
+ | プッシュタイトル | 全理由で共通のタイトルテンプレート(通常の入力欄、ネイティブのプレースホルダー:入力すると消え、空にすると復元)。空欄の場合は各理由でデフォルトタイトル(完了=タスク完了、エラー=タスクエラー、中止=タスク中止、ブロック=タスクブロック、上限=タスクが出力上限に到達)。`{title}` はセッションタイトルを参照(「+ セッションタイトル」をクリックでカーソル位置に挿入) |
308
+ | テンプレート × 5 | 各終了理由(完了、エラー、中止、ブロック、出力上限)ごとに独立した Chip エディタ:テキスト + インライン情報カプセル、カーソル位置に挿入、クリックで削除、リアルタイムプレビュー |
309
+ | サブエージェントセッションをスキップ | チェックボックス(保存時に設定ドキュメントへ一緒に書き込み) |
310
+ | 通知権限 | 状態をリアルタイム表示:許可済み(緑)/ 未許可(「許可をリクエスト」ボタン付き)/ ブラウザにブロック済み(アドレスバーの操作ガイド付き)/ 環境が非対応 |
311
+ | 理由別のタイトルカスタマイズ | 折りたたみエリア(デフォルトで収納):終了理由ごとに独立したタイトル入力欄。空欄=グローバルテンプレートまたは言語デフォルトのタイトルを使用 |
312
+ | 保存 | ホストの設定ドキュメントに書き込み(`language` / `templates` / `titleTemplate` / `titleTemplates` / `pushMode`)。保存後「クリックでリロード」リンクを表示 |
313
+ | リセット | 1 クリックでデフォルト値に復元(**言語は現在の選択を保持**、タイトル / テンプレート / プッシュ方式はデフォルトに戻す)し、即座に保存 |
314
+
315
+ > [!NOTE]
316
+ > 「プッシュ方式」の取舍:`dual`(デフォルト)は Windows システム通知とページ内トーストを同時に表示します。トーストはセーフティネットとして、システム通知がプラットフォームにサイレント化されるのを防ぎます(集中アシスタント、通知バナー無効化など)。ただし **QQ ブラウザなどの国産 Chromium シェルブラウザは `Notification` を「ブラウザ内蔵のページ内プッシュポップアップ」としてレンダリングします**(ページ上部 / 隅のバナーで、Windows 通知センターを経由しません)—— この場合、`dual` ではページ内に 2 つの通知(ブラウザ内蔵ポップアップ + プラグインのトースト)が表示されます。このようなブラウザでは「ページ内表示のみ」を選択してください(`Notification` を呼び出さないため、ブラウザ内蔵ポップアップは表示されず、ページ内にはプラグインの小さなトーストのみ表示されます)。「システム通知のみ」モードは QQ ブラウザでは無効です(常にページ内ポップアップとしてレンダリングされます)。設定パネルの各理由の「送信」テストボタンも同様の影響を受けます。
317
+
318
+ > [!NOTE]
319
+ > システム通知(`Notification` API)が表示されるかどうかは、**ブラウザとサイトへのアクセス方法**の両方で決まります。Edge / Chrome は「馴染みのない」サイトへの通知を**自動的にブロック**します(アドレスバーに「通知がブロックされました」と表示)。アドレスバー左の権限アイコン → サイト設定 → 通知 → 許可で復元できます。`http://IP` のような非セキュアコンテキストでのアクセス時は `Notification` 自体が存在しないため、「ページ内表示のみ」に切り替えてください。設定パネルの「通知権限」エリアは現在の状態をリアルタイム表示し、対応する操作ガイドを提供します(1 クリックで権限をリクエスト可能)。Firefox ではウィンドウにフォーカスがある間は通知がページ内バナーとして表示され、フォーカスが外れて初めてシステム通知センターに入ります。
320
+
321
+ > [!NOTE]
322
+ > パネルの「サブエージェントセッションをスキップ」は設定ドキュメント内のブール値を保存します。ホスト `cordis.patch.yml` の `config.skipSubagents` は起動時のデフォルト値であり、どちらか一方でも true ならスキップされます。
323
+
324
+ ### メッセージテンプレートとプレースホルダー
325
+
326
+ 各終了理由ごとに独立したテンプレート入力欄があります。**ラベルがスイッチ**です —— テンプレートに対応する情報ラベルを挿入して初めて、そのデータが表示されます:
327
+
328
+ | プレースホルダー | 意味 | 例の値 |
329
+ | --- | --- | --- |
330
+ | `{title}` | セッションタイトル(プッシュタイトルテンプレートでも使用可) | `重构登录模块` |
331
+ | `{duration}` | 本ターンの所要時間(`turn/start` で計測開始 → `turn/end` で終了) | `3 分 25 秒` / `3m25s` |
332
+ | `{usage}` | トークン消費(入力 = 非キャッシュ + キャッシュ読み込み + キャッシュ書き込み) | `1,240 输入 / 3,560 输出` |
333
+ | `{error}` | エラー情報(エラーなしの場合は `none` と表示。単行化、80 文字で切り詰め) | `connection timeout` |
334
+ | `{cache}` | キャッシュヒット率(公式プロジェクション口径、データなしの場合は空) | `96.5%` |
335
+ | `{tps}` | 生成速度(公式プロジェクション口径、データなしの場合は空) | `92 tok/s` |
336
+ | `{label}` | 非推奨 —— レンダリング時に自動的に除去され、旧テンプレートとの互換性を維持(挿入メニューからは削除済み) | — |
337
+
338
+ テンプレートを空欄にすると内蔵のデフォルトメッセージを使用します(所要時間と消費量が自動付与)。折りたたみ行の `summary` は本文と同じソースです(レンダリング結果を 120 文字に切り詰め)—— 折りたたみ行だけを見るユーザーにも実際のタイトル・所要時間・消費量がわかります。
339
+
340
+ ### プリセットシステム
341
+
342
+ - **内蔵プリセット**:「デフォルト」のみ。ベースラインとして使用。
343
+ - **カスタムプリセット**:`localStorage`(key = `dsh-scn-custom-presets`)に保存:
344
+ - 「新規作成」で名前を付けてカスタムプリセットとして保存。保存後は「変更」で自動同期、「削除」で除去できます。
345
+ - **自動採番される無名プリセット**:「デフォルト / 空白」から直接保存すると、`未命名`、`未命名 2`、`未命名 3`… が自動生成されます(番号は現在の最大値 + 1)。
346
+ - フォームに「出自:xxx · 変更あり」の出所表示があります(プリセット由来だが内容が変更された場合)。
347
+ - **保存即同期**:保存時、フォームの出所がカスタムプリセットならそのプリセットを更新し、それ以外は新規作成するか無名プリセットの採番を続けます。
348
+
349
+ ### ホスト設定項目
350
+
351
+ ```yaml
352
+ - insert:
353
+ - id: dsh-session-notify
354
+ name: '@telosmaylx/dsh-session-notify'
355
+ config:
356
+ reasons: [completed, aborted, blocked, error, max-tokens]
357
+ skipSubagents: true
358
+ ```
359
+
360
+ | フィールド | 型 | デフォルト値 | 説明 |
361
+ | --- | --- | --- | --- |
362
+ | `reasons` | `string[]` | `[completed, aborted, blocked, error, max-tokens]` | 通知をトリガーする `turn/end` 理由のホワイトリスト |
363
+ | `skipSubagents` | `boolean` | `true` | サブエージェントセッションをスキップ(`origin=subagent` または `delegationDepth>0`) |
364
+
365
+ ---
366
+
367
+ ## 動作原理
368
+
369
+ プラグインは**ホスト側**(Node)と**クライアント側**(ブラウザ)に分かれ、間をセッションログ(JSONL)と公式セッションプロジェクションで接続します:
370
+
371
+ ```text
372
+ ┌─────────────────── 宿主平面(lib/index.js,Node)──────────────────┐
373
+ │ │
374
+ │ session/event 火线 │
375
+ │ ├─ turn/start → tracker 起表(key: sessionId:turn) │
376
+ │ ├─ assistant/message → 累加该轮 token 用量 │
377
+ │ └─ turn/end → reason.kind ∈ reasons ? │
378
+ │ ├─ 子代理会话?跳过 │
379
+ │ ├─ 读官方投影:cache / tps / title │
380
+ │ ├─ 按语言+模板构建通知(summary ≤120 字) │
381
+ │ └─ queueMicrotask 追加系统消息 │
382
+ │ (避开 append 重入窗口) │
383
+ │ │
384
+ │ settings.register → 官方「设置 → 插件」命名空间(失败退避重试) │
385
+ │ sessionProjections → 注册投影单元(key=session-complete-notify) │
386
+ └──────────────────────────────┬──────────────────────────────────────┘
387
+ │ user/message (source: plugin, form: notice)
388
+ ▼ JSONL 持久化 + 投影推送
389
+ ┌─────────────────── 客户端平面(lib/client.js,浏览器)──────────────┐
390
+ │ │
391
+ │ 会话列表订阅:running true → false 边沿 → pushCompletion │
392
+ │ ├─ 取正文:投影 → 事件窗口 notice → 降级(轮询 ≤6s) │
393
+ │ ├─ Web Notification(独立 tag,点击聚焦) │
394
+ │ └─ 页内 toast(永远展示,≤3 条,10s 自动消失) │
395
+ │ │
396
+ │ slots.inject('settings.plugin.item') → 设置卡片(预设/语言/模板) │
397
+ └─────────────────────────────────────────────────────────────────────┘
398
+ ```
399
+
400
+ ### 主要な設計判断
401
+
402
+ - **再生しない**:リアルタイムイベントのみを処理し、resume、replay では過去の通知を送りません。
403
+ - **自己ループなし**:プラグインは `user/message` を追加し、自身は `turn/*` のみを監視するため、イベントタイプが交差しません。
404
+ - **外部 import ゼロ**:プラグインはリポジトリディレクトリから realpath で読み込まれるため、`@deepseek-ai/*` を裸で解決できません —— ホスト側は `createRequire` で profile 共有依存のハブ(`.dsh/profiles/node_modules`)に固定し、`schemastery`(設定 schema)と `zod`(プロジェクション schema)を取得します。UserMessage は `dsh-llm` の契約に従って手作業で構築します(`id = crypto.randomUUID()`、deep-freeze は `session.append` の adopt スナップショット段階で完了)。
405
+ - **append 再入の回避**:`session/event` のオブザーバーコールバックは、`turn/end` の append のパブリッシュ境界内で実行されます(dsh-session は dispatch の前に `entry.appending` を立て、`finally` でリセット)。同期 append は拒否されるため、`queueMicrotask` に延期します(マイクロタスクは今回の同期スタックが `finally` リセットを含めて完了した後に実行されます)。
406
+ - **effect の規律**:設定登録のバックオフリトライタイマーを `ctx.effect()` でラップし `clearTimeout` disposer を返します —— リトライウィンドウ中にプラグインがアンロードまたはホットリロードされた場合、タイマーは fiber とともに破棄され、解放済みの ctx に対して登録を発火しません(非常に古い環境で `ctx.effect` API がない場合は、裸のタイマー + ctx 破棄後のフォールバックキャッチに退化)。
407
+ - **HMR 安全**:`core.js` のインポートに `?v=1` のキャッシュバスターを付与(HMR リロードは URL をキーとして制御)。設定登録がホットリロードの競合(duplicate)に遭遇した場合は自動でバックオフリトライ(最大 8 回、間隔 `400ms × attempts`)。
408
+ - **プロジェクション登録の二重トラック**:優先的に `ctx.root.get('sessionProjections')`(ホストルートに最も近いもの)を使用し、取得できない場合は注入インスタンスにフォールバック。注入インスタンスのみに登録した場合、クライアントがプロジェクションを読み取れず、プッシュ本文はフォールバックパスを通る可能性があります —— ベストエフォートであり、セッション内のシステムメッセージには影響しません。
409
+
410
+ ---
411
+
412
+ ## プロジェクト構成
413
+
414
+ ```text
415
+ dsh-session-notify/
416
+ ├── lib/
417
+ │ ├── index.js # 宿主平面(Node):session/event 订阅 → 系统消息落盘;
418
+ │ │ # settings 命名空间注册(schemastery schema,退避重试);
419
+ │ │ # sessionProjections 投影单元(后台会话推送正文)
420
+ │ ├── core.js # 纯逻辑层(零依赖,可独立测试):轮次计时与用量聚合、
421
+ │ │ # 5 语言文案表、时长/用量/缓存/速度格式化、
422
+ │ │ # 模板渲染({title}{duration}{usage}{error}{cache}{tps})
423
+ │ └── client.js # 浏览器平面:完成推送(系统通知 + toast)、
424
+ │ # 设置卡片(Chip 模板编辑器 + 预设系统 + 实时预览)
425
+ ├── scripts/
426
+ │ ├── build.sh # 零构建:仅 node --check 语法校验
427
+ │ ├── verify-notice.mjs # 校验会话日志落盘证据(zstd 多帧逐帧解压)
428
+ │ ├── probe-client.mjs # 探针:客户端装配
429
+ │ ├── probe-client-e2e.mjs # 探针:客户端端到端
430
+ │ ├── probe-card-render.mjs # 探针:设置卡片渲染
431
+ │ ├── probe-settings-card.mjs # 探针:设置面板卡片
432
+ │ ├── probe-settings-check.mjs# 探针:设置面板检查
433
+ │ └── probe-diag-settings.mjs # 探针:settings 诊断
434
+ ├── cordis.patch.yml # dsh.bundle manifest —— dsh plugin add 自动挂载的凭证
435
+ ├── package.json # dsh.bundle(patch)+ dsh.client(web 注入)双 manifest;
436
+ │ # exports: "." / "./client" / "./core"
437
+ ├── LICENSE # MIT
438
+ └── README.md # 本文档
439
+ ```
440
+
441
+ ---
442
+
443
+ ## 開発とデバッグ
444
+
445
+ 構文チェック(ゼロビルド、`prepublishOnly` と同じ検査):
446
+
447
+ ```bash
448
+ npm run build
449
+ ```
450
+
451
+ 公開(公開前に `prepublishOnly` の構文チェックを自動実行):
452
+
453
+ ```bash
454
+ npm publish --registry=https://registry.npmjs.org --access public
455
+ ```
456
+
457
+ オフライン検証:セッションログからすべての plugin-source イベントと `turn/end` の末尾シーケンスを抽出(パスを渡さない場合は `~/.dsh/sessions` 配下の最新セッションを自動選択):
458
+
459
+ ```bash
460
+ node scripts/verify-notice.mjs <session.jsonl.zstd>
461
+ ```
462
+
463
+ ### デバッグ入口
464
+
465
+ | 入口 | 内容 |
466
+ | --- | --- |
467
+ | `~/.dsh/session-complete-notify.log` | ホスト診断ログ:設定登録、リトライと失敗、プロジェクション登録、追加失敗のスタック |
468
+ | ブラウザ console `[dsh-session-notify-client]` | クライアントログ:権限ステータス、通知表示、設定保存 |
469
+ | `window.__dsch_notify_debug.readNotice(id)` | 指定セッションの最新通知本文を手動で読み取り |
470
+ | `window.__dsch_notify_debug.snapshotDebug(id)` | セッション末尾のノードタイプ + notice 数 + 最新の本文(先頭 200 文字) |
471
+
472
+ ---
473
+
474
+ ## よくある質問
475
+
476
+ <details>
477
+ <summary><b>npm install の後に自動マウントされないのはなぜ?</b></summary>
478
+
479
+ これは DSH 公式の設計です:`npm install` はパッケージを依存ツリーに追加するだけで、プラグインを登録しません。自動マウントの唯一の手段は `dsh plugin add` —— パッケージ内の `dsh.bundle` manifest(本プラグインは 0.1.3 以降で宣言)を読み取り、`cordis.patch.yml` を自動適用します。[インストール](#インストール)を参照してください。
480
+
481
+ </details>
482
+
483
+ <details>
484
+ <summary><b>「中断」(interrupted)で通知されないのはなぜ?</b></summary>
485
+
486
+ `interrupted` はクラッシュ復旧後に永続化バックエンドが書き足す孤児ターンのクローズマーカーで、ユーザー視点の「完了」には含まれません(含めると復元セッションで誤通知が並んでしまいます)。必要な場合はホスト設定の `reasons` に追加してください。
487
+
488
+ </details>
489
+
490
+ <details>
491
+ <summary><b>バックグラウンドセッション(ウィンドウを開いていない)でもプッシュされますか?</b></summary>
492
+
493
+ はい。クライアントはセッションリストのスナップショットから全セッションの `running` エッジを監視します。本文は優先的にホストプロジェクションを取得します —— ホストがすべてのセッション(バックグラウンド含む)でプロジェクションを維持するため、プッシュ本文はセッション間で一貫します。プロジェクションが利用不可の場合は、イベントウィンドウまたはワークスペース情報にフォールバックします。
494
+
495
+ </details>
496
+
497
+ <details>
498
+ <summary><b>設定を保存したのにページのリロードを促されるのはなぜ?</b></summary>
499
+
500
+ ホストは名前空間の登録時に一度だけ設定を読み取り、クライアント bundle はページ読み込み時に組み立てられます。保存後、「クリックでリロード」をクリックして両側で読み直すと、新しい言語・テンプレートが有効になります。
501
+
502
+ </details>
503
+
504
+ <details>
505
+ <summary><b>キャッシュヒット率・速度のデータはどこから来ますか?なぜ空のことがあるのですか?</b></summary>
506
+
507
+ 公式の `sessionProjections`(`tokenUsage`、`sessionStats`)から取得し、dsh-web-ui のステータスバーと同じ口径です。ホストがプロジェクションスナップショットの読み取りに失敗した場合やデータが未準備の場合は、ローカルの使用量集計による推定にフォールバックし、それでもデータがない場合はその項目を空にします(ラベルを挿入しても表示されません)。また、この 2 項目はカスタムテンプレートで `{cache}`、`{tps}` を挿入した場合にのみ現れ、デフォルトメッセージには含まれません。
508
+
509
+ </details>
510
+
511
+ <details>
512
+ <summary><b>通知本文のエラー情報が長すぎる、改行がある場合は?</b></summary>
513
+
514
+ サマリー行(折りたたみ行)とエラー詳細はどちらも単行化され切り詰められます:サマリー 120 文字、テンプレート `{error}` 80 文字、デフォルトメッセージのエラー詳細 40 文字。超長の場合は省略記号で終わります。
515
+
516
+ </details>
517
+
518
+ <details>
519
+ <summary><b>システム通知のアイコンやサウンドをカスタマイズできますか?</b></summary>
520
+
521
+ 現在のバージョンはブラウザデフォルトの通知スタイルを使用し、カスタムアイコンやサウンドは注入しません。トーストは固定のダークカードです。これらの機能が必要な場合は、Issue または PR をお寄せください。
522
+
523
+ </details>
524
+
525
+ <details>
526
+ <summary><b>Edge でシステム通知が届かないのはなぜ?QQ ブラウザではページ内バナー(内蔵プッシュポップアップ)しか出ないのはなぜ?</b></summary>
527
+
528
+ どちらもブラウザの挙動であり、プラグインから強制はできません:
529
+
530
+ - **Edge / Chrome**:「馴染みのない」サイトへの通知を**自動的にブロック**します(アドレスバーに「通知がブロックされました」と表示)。アドレスバー左の権限アイコン → サイト設定 → 通知 → 許可で復元され、以降は Windows 通知センターに正常に表示されます。ブラウザの通知設定で「自動ブロック」をオフにすることもできます。
531
+ - **QQ ブラウザなどの国産 Chromium シェル**:`Notification` を**ブラウザ内蔵のページ内プッシュポップアップ**として固定レンダリングします(ページ上部 / 隅のバナー、Windows 通知センターを経由せず)、システム通知のオプションもありません。3 つのプッシュ方式の実際の挙動:
532
+ - `デュアルチャンネル` → ブラウザ内蔵ポップアップ + プラグイントースト。ページ内に 2 つの通知。
533
+ - `システム通知のみ` → 無効(QQ ブラウザでは常にページ内ポップアップとしてレンダリング)。
534
+ - `ページ内表示のみ` → ブラウザ内蔵ポップアップは表示されず、ページ内にはプラグインの小さなトーストのみ(推奨)。
535
+ 設定パネルの各理由の「送信」テストボタンも同じ規則でレンダリングされます。
536
+ - **Firefox**:ウィンドウにフォーカスがある間は通知がページ内バナーとして表示され、フォーカスが外れる / 最小化して初めてシステム通知センターに入ります。権限はアドレスバーで手動許可が必要です。
537
+ - もう一点:`http://IP` アクセス(非セキュアコンテキスト)時は `Notification` が存在せず、どのブラウザでもシステム通知を表示できません。
538
+
539
+ 設定パネルの「通知権限」エリアは現在の状態と対応する操作ガイドをリアルタイム表示します。
540
+
541
+ </details>
542
+
543
+ ---
544
+
545
+ ## 更新履歴
546
+
547
+ | バージョン | 日付 | 変更内容 |
548
+ | --- | --- | --- |
549
+ | **0.1.10** | 2026-08-29 | 「プッシュタイトル」をネイティブ入力欄に変更(ネイティブのプレースホルダー:コピー不可、入力すると消え、空にすると復元。「+ セッションタイトル」でカーソル位置に `{title}` を挿入)。ドキュメントに QQ ブラウザの内蔵プッシュポップアップの説明を追記(3 つのプッシュ方式の実際の挙動 + 送信ボタンのテストも同じ規則) |
550
+ | **0.1.9** | 2026-08-29 | プッシュタイトルが**理由別のカスタマイズ**に対応(折りたたみエリア UI、デフォルトで収納され冗長にならない。空欄時は各理由で異なるデフォルトタイトル:タスク完了 / タスクエラー / タスク中止 / タスクブロック / タスクが出力上限に到達、5 言語)。プロジェクションをオブジェクト(kind/text/title)にアップグレードし、host がレンダリングしたタイトルを保持。リセットボタンが**現在の言語を保持**。デフォルトメッセージに「セッションタイトル」ラベルを埋め込み(セッション「{title}」が完了、タイトルなしは自動フォールバック)。設定パネルのテンプレートプレビューを同期。「+ 情報を挿入」でラベル挿入後に自動折りたたみしなくなった。使用中のカスタムプリセットを削除すると自動的にデフォルトへ戻る。テンプレートプレビューを修正(クリックで消えない、入力でのみ非表示、空にすると復元)。各理由に「送信」ボタンを追加(現在のテンプレートでレンダリングしたテスト通知を 1 クリックで送信) |
551
+ | **0.1.8** | 2026-08-29 | デフォルトのプッシュタイトルを「タスク完了」に変更(`{title}` は引き続きセッションタイトルを参照可)。デフォルトメッセージを終了理由ごとに異なる表現に(完了=コンパクトな括弧式 / 中止・ブロック=文を分ける / エラー=エラー前置 / 上限=アドバイス付き、5 言語)。設定パネルに「リセット」ボタンを追加し、1 クリックでデフォルトに復元 |
552
+ | **0.1.7** | 2026-08-29 | 0.1.6 の設定カードのクラッシュを修正:`notificationPermissionRow`/`requestPermissionNow` が Card コンポーネント内の state(スコープ外)を参照していたため、レンダリング時に ReferenceError が発生し、設定カード全体が消えていた。自己完結型 + コールバックで引数を渡す方式に変更 |
553
+ | **0.1.6** | 2026-08-29 | 設定パネルに「通知権限」ステータスエリアを追加(許可状態のリアルタイム表示 + 1 クリック権限リクエストボタン + ブロック時のアドレスバー操作ガイド)。権限リクエストを**ユーザージェスチャ内での要求**に変更(Chromium はジェスチャ以外の自動リクエストを無視する。Edge が馴染みのないサイトで通知を自動ブロックする典型的なシナリオを解決)。FAQ にブラウザ差分の説明を追加 |
554
+ | **0.1.5** | 2026-08-29 | 「プッシュ方式」設定を追加(デュアルチャンネル / システム通知のみ / ページ内表示のみ)。QQ ブラウザなどの Chromium シェルが `Notification` をページ内バナーとしてレンダリングすることによる二重通知を解決。`pushMode` を設定 schema と設定パネルに追加 |
555
+ | **0.1.4** | 2026-08-28 | 完全なアンインストール対応を追加:`dispose` ライフサイクルの終了処理(host はアンインストールフラグを立て、保留中のマイクロタスク追加を抑制。client は本文ポーリングタイマー、`__dsch_notify_debug` フック、toast コンテナをクリーンアップ)。アンインストールのドキュメントと FAQ を同期 |
556
+ | **0.1.3** | 2026-08-28 | 公式 `dsh.bundle` manifest を宣言(`dsh plugin add` の 1 コマンドで自動マウント)。settings リトライタイマーを `ctx.effect()` ラップに変更(Cordis effect の規律)。インストールドキュメントを再編成 |
557
+ | 0.1.2 | 2026-08-27 | パッケージ名を `@telosmaylx` scope に変更(npm ユーザー名のスコープ) |
558
+ | 0.1.1 | 2026-08-27 | GitHub、npm のインストール方法をドキュメント化 |
559
+ | 0.1.0 | 2026-08-26 | 初期バージョン:セッション内システムメッセージ + ブラウザプッシュ + 公式設定パネル |
560
+
561
+ ---
562
+
563
+ ## コントリビューション
564
+
565
+ Issue と PR をお待ちしています:
566
+
567
+ 1. リポジトリを Fork し、新しいブランチを作成(`feat/xxx`)
568
+ 2. 変更後に `npm run build` を実行して構文チェック
569
+ 3. PR を提出し、動機と検証方法を説明
570
+
571
+ 提出前には [Cordis 開発チュートリアル](https://deepseek-harness.github.io/deepseek-harness/develop/cordis-tutorial) の規律を守ってください:
572
+
573
+ - Cordis 以外のリソース(タイマー、購読、watcher)は `ctx.effect()` でラップし、disposer を返す必要があります。
574
+ - 設定項目に明示的な `id` を付けて編集のズレを防止。
575
+ - プラグインは `dsh.bundle` manifest を宣言して初めて `dsh plugin add` で認識・インストールされます。
576
+
577
+ ---
578
+
579
+ ## 関連リンク
580
+
581
+ - [awesome-dsh-plugin](https://github.com/awesome-dsh-plugin/awesome-dsh-plugin) —— DSH プラグインの精選リスト(投稿規約:`dsh.bundle` がインストールの唯一の証明)
582
+ - [Cordis 開発チュートリアル](https://deepseek-harness.github.io/deepseek-harness/develop/cordis-tutorial) —— プラグイン開発の全プロセス(01-07 章)
583
+ - [npm パッケージホームページ](https://www.npmjs.com/package/@telosmaylx/dsh-session-notify)
584
+ - [GitHub リポジトリ](https://github.com/TelosmaYLX/dsh-session-notify)
585
+
586
+ ---
587
+
588
+ ## ライセンス
589
+
590
+ [MIT](./LICENSE) © dsh-session-notify contributors