dsh-manage-session 0.2.3 → 0.4.8
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 +19 -0
- package/CHANGELOG.ko.md +18 -0
- package/CHANGELOG.md +40 -0
- package/README.en.md +14 -0
- package/README.ja.md +14 -0
- package/README.ko.md +14 -0
- package/README.md +14 -0
- package/dsh.plugin.json +4 -4
- package/lib/client.js +179 -36
- package/lib/index.d.ts +288 -16
- package/lib/index.js +1003 -384
- package/package.json +125 -121
- package/screenshots.json +5 -1
- package/src/client/card.tsx +150 -153
- package/src/client/health-panel.tsx +56 -1
- package/src/client/host-api.ts +19 -1
- package/src/client/index.ts +337 -234
- package/src/client/locales.ts +22 -2
- package/src/host/health/cache.ts +1 -1
- package/src/host/health/decode.ts +19 -1
- package/src/host/health/gates.ts +94 -3
- package/src/host/health/generation.ts +216 -0
- package/src/host/health/repair.ts +28 -7
- package/src/host/health/scan.ts +84 -27
- package/src/host/health/source-kind.ts +350 -0
- package/src/host/history/archive.ts +4 -1
- package/src/host/history/purge.ts +51 -4
- package/src/index.ts +83 -55
package/CHANGELOG.ja.md
CHANGED
|
@@ -1,5 +1,24 @@
|
|
|
1
1
|
# 変更履歴
|
|
2
2
|
|
|
3
|
+
## 0.4.8
|
|
4
|
+
|
|
5
|
+
### 修正 —— ST1:変換が第一-party プロデューサー名でホストと食い違う問題
|
|
6
|
+
|
|
7
|
+
- `migrateSessionSourceKind` は行ごとに `isFirstPartyLegacyProducer` を照合(ホスト
|
|
8
|
+
`dsh-session-format-v3-to-v4@0.1.7-rc.2` lib/index.js:51-84 の2表を一字一句収録、
|
|
9
|
+
実測 5+25=**30** 名)。該当行はスキップし `skippedFirstParty` で正直に報告——
|
|
10
|
+
第一-party 名はホストの改名表/同名 kind(role 敏感分岐含む)で処理されるため、
|
|
11
|
+
無条件の接頭辞付与はホストが生成しない kind を書き出し、帰属を静かに壊します。
|
|
12
|
+
第一方旧行のみのログは書き込まない。30 名対照テストとホスト公式デコーダ読み返し
|
|
13
|
+
検証を追加(161 テスト全緑)。
|
|
14
|
+
|
|
15
|
+
### 強化 —— R2:purge 側が署名変換バックアップを認識
|
|
16
|
+
|
|
17
|
+
- 統計とクリーンアップの両経路で `*.pre-sourcemigrate-<ts>` バックアップを認識:
|
|
18
|
+
統計は行ごとに `backupBytes`/`backupCount` を追加(`bytes` の部分集合、単列で
|
|
19
|
+
誤読防止)、クリーンアップ結果は `backupsRemoved` を追加。回帰 6 件を追加、
|
|
20
|
+
全 167 テスト。
|
|
21
|
+
|
|
3
22
|
## 0.1.0-alpha.6
|
|
4
23
|
|
|
5
24
|
### 修正 —— アイコンとラベルの間隔を詰め、狭いサイドバーで隣を潰さない
|
package/CHANGELOG.ko.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# 변경 이력
|
|
2
2
|
|
|
3
|
+
## 0.4.8
|
|
4
|
+
|
|
5
|
+
### 수정 —— ST1:변환이 제1자 프로듀서명에서 호스트와 불일치
|
|
6
|
+
|
|
7
|
+
- `migrateSessionSourceKind`는 행마다 `isFirstPartyLegacyProducer`를 조회(호스트
|
|
8
|
+
`dsh-session-format-v3-to-v4@0.1.7-rc.2` lib/index.js:51-84의 두 표를 그대로 수록,
|
|
9
|
+
실측 5+25=**30**명). 해당 행은 건너뛰고 `skippedFirstParty`로 정직하게 보고——
|
|
10
|
+
제1자명은 호스트의 개명표/동명 kind(role 민감 분기 포함)으로 처리되므로 무조건
|
|
11
|
+
접두사를 붙이면 호스트가 생성하지 않는 kind를 기록해 귀속이 조용히 손상됩니다.
|
|
12
|
+
제1자 구형 행만 있는 로그는 기록하지 않음. 30명 대조 테스트와 호스트 공식
|
|
13
|
+
디코더 재독 단언 추가(161 테스트 전부 통과).
|
|
14
|
+
|
|
15
|
+
### 강화 —— R2:purge 측 서명 변환 백업 인식
|
|
16
|
+
|
|
17
|
+
- 통계와 정리 양 경로에서 `*.pre-sourcemigrate-<ts>` 백업을 인식: 통계는 행마다
|
|
18
|
+
`backupBytes`/`backupCount` 추가(`bytes`의 부분집합, 단열로 오독 방지),
|
|
19
|
+
정리 결과에 `backupsRemoved` 추가. 회귀 6건 추가, 전체 167 테스트.
|
|
20
|
+
|
|
3
21
|
## 0.1.0-alpha.6
|
|
4
22
|
|
|
5
23
|
### 수정 — 아이콘과 레이블 간격 축소, 좁은 사이드바에서 이웃을 밀어내지 않음
|
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,45 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.4.8
|
|
4
|
+
|
|
5
|
+
### 修复 —— ST1:转换对第一方生产者名与宿主分叉
|
|
6
|
+
|
|
7
|
+
- `migrateSessionSourceKind` 逐行查 `isFirstPartyLegacyProducer`(两张表逐字照录宿主
|
|
8
|
+
`dsh-session-format-v3-to-v4@0.1.7-rc.2` lib/index.js:51-84,实测 5+25=**30** 个第一方名),
|
|
9
|
+
命中即跳过该行并以 `skippedFirstParty` 如实上报——第一方名走宿主改名表/同名裸 kind
|
|
10
|
+
(含 role 敏感分支),无条件加前缀会写出宿主永不产出的 kind,静默损坏归因。
|
|
11
|
+
只含第一方旧行时不写盘。新增 30 名对照测试与宿主官方解码器读回断言(161 测试全绿)。
|
|
12
|
+
|
|
13
|
+
### 加固 —— R2:purge 侧识别署名转换备份
|
|
14
|
+
|
|
15
|
+
- 归档统计与清理两路识别 `*.pre-sourcemigrate-<ts>` 备份(后缀常量
|
|
16
|
+
`SOURCE_MIGRATE_BACKUP_SUFFIX` 已在 0.4.7 导出,本次接线):
|
|
17
|
+
- **统计**:`locateSessionUsage` / `session-history-list` 每行新增 `backupBytes` /
|
|
18
|
+
`backupCount`——备份是 `bytes` 的子集,单列出来免得被当作日志体积误读;
|
|
19
|
+
- **清理**:`session-history-purge` 结果新增 `backupsRemoved`(整目录删除本就连带
|
|
20
|
+
备份,此处置个数让「备份一并处置」如实上报,不再是黑箱)。
|
|
21
|
+
- 新增 6 条回归(后缀判定、统计单列、如实 0、清理上报、连带保留、列表带出),全套 167 测试。
|
|
22
|
+
|
|
23
|
+
## 0.4.7
|
|
24
|
+
|
|
25
|
+
### 新增 —— 插件署名格式体检门(source-kind)与旧署名转换
|
|
26
|
+
|
|
27
|
+
- **背景**:宿主 0.1.7-rc.1 起(会话格式 v4)拒绝裸 `source: { kind: 'plugin', plugin }` 署名
|
|
28
|
+
(SessionFormatError,整轮失败);genui 之外的未适配插件(session-guard、prime-memory 等)
|
|
29
|
+
已按清单修写路径,但存量日志里由它们写入的旧署名行仍在——未适配插件的读回判断会失配。
|
|
30
|
+
- **检测(只读)**:体检新增第 6 门 `source-kind`,按日志 header 的格式代次分线:
|
|
31
|
+
- **版本依赖表**(详见 `src/host/health/source-kind.ts` 头注):v1/v2 早期线(未考证)与
|
|
32
|
+
v3(0.1.6 时代,待考证)旧行合法、不动(宿主 v3→v4 迁移负责);**v4(≥0.1.7-rc.1)拒绝
|
|
33
|
+
旧署名**,观测到即 warn。
|
|
34
|
+
- v4 之前的版本线记 ok 不抬档——不越权处理宿主迁移契约内的数据(防过度操作)。
|
|
35
|
+
- **转换(显式触发)**:新增 API `session-health-source-migrate` 与体检面板「转换旧署名」按钮
|
|
36
|
+
(仅 source-kind 门 warn 时出现)。把 `{ kind: 'plugin', plugin: N }` 改写为
|
|
37
|
+
`{ kind: 'plugin:N' }`(删除 plugin 字段),其余字节原样保留。
|
|
38
|
+
- **红线三闸**:只动 source 署名字段;改前整文件备份(`*.pre-sourcemigrate-<ts>`,可回退);
|
|
39
|
+
完整性不合格(撕裂尾帧/解码失败/seq 缺口)或 header.version < 4 时拒绝执行。这是
|
|
40
|
+
「禁止改写会话日志」红线的唯一显式例外,经用户逐会话触发。
|
|
41
|
+
- genui 不在掌控范围,本插件不做也不会替它转换(其历史行转换后由其读回判断自行失配与否自负)。
|
|
42
|
+
|
|
3
43
|
## 0.1.0-alpha.6
|
|
4
44
|
|
|
5
45
|
### 修复 —— 图标与文案间距收紧;窄栏下不再挤掉邻居
|
package/README.en.md
CHANGED
|
@@ -10,6 +10,20 @@ A DSH web plugin for **session history files** and **session health checks**. It
|
|
|
10
10
|
> Naming boundary: **this package ships no search or index features**. Search/indexing belongs to
|
|
11
11
|
> `dsh-search-index`; neither side mentions or re-interprets the other's fields.
|
|
12
12
|
|
|
13
|
+
## Screenshots
|
|
14
|
+
|
|
15
|
+
A **Session Steward** entry sits next to Search and Settings at the bottom of the sidebar:
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
**Retirement Home (history files)** — the official archive set, each row carrying its size and whether the on-disk entity still exists; select rows to un-archive or purge:
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
**Checkup** — the entry point of the four-gate checkup, which ends in a reversible prescription:
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
13
27
|
## Install
|
|
14
28
|
|
|
15
29
|
```bash
|
package/README.ja.md
CHANGED
|
@@ -10,6 +10,20 @@
|
|
|
10
10
|
> 命名の境界:**本パッケージは検索・インデックス機能を一切提供しません**。検索/インデックスは
|
|
11
11
|
> `dsh-search-index` に属し、双方が相手のフィールドに言及したり再解釈したりすることはありません。
|
|
12
12
|
|
|
13
|
+
## スクリーンショット
|
|
14
|
+
|
|
15
|
+
サイドバー下部に **「会话管家」**(セッション・スチュワード)のエントリが追加され、検索・設定と並びます:
|
|
16
|
+
|
|
17
|
+

|
|
18
|
+
|
|
19
|
+
**カルテ室(履歴ファイル)** — 公式アーカイブ集合の一覧。各行にサイズとディスク上の実体の有無を表示し、選択してアーカイブ解除または整理ができます:
|
|
20
|
+
|
|
21
|
+

|
|
22
|
+
|
|
23
|
+
**健診** — 四つのゲートによる健診の入口。スキャン後に可逆的な処方を提示します:
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+
|
|
13
27
|
## インストール
|
|
14
28
|
|
|
15
29
|
```bash
|
package/README.ko.md
CHANGED
|
@@ -9,6 +9,20 @@
|
|
|
9
9
|
> 이름 경계: **이 패키지는 검색이나 인덱스 기능을 제공하지 않습니다**. 검색/인덱싱은
|
|
10
10
|
> `dsh-search-index`에 속하며, 어느 쪽도 상대방의 필드를 언급하거나 재해석하지 않습니다.
|
|
11
11
|
|
|
12
|
+
## 스크린샷
|
|
13
|
+
|
|
14
|
+
사이드바 하단에 **「会话管家」**(세션 스튜어드) 항목이 추가되어 검색, 설정과 나란히 표시됩니다:
|
|
15
|
+
|
|
16
|
+

|
|
17
|
+
|
|
18
|
+
**기록 보관소(히스토리 파일)** — 공식 아카이브 집합 목록. 각 행에 용량과 디스크 실체 존재 여부를 표시하며, 선택하여 아카이브 해제 또는 정리를 할 수 있습니다:
|
|
19
|
+
|
|
20
|
+

|
|
21
|
+
|
|
22
|
+
**점검** — 네 개의 gate 점검 입구이며, 검사 후 되돌릴 수 있는 처방을 제시합니다:
|
|
23
|
+
|
|
24
|
+

|
|
25
|
+
|
|
12
26
|
## 설치
|
|
13
27
|
|
|
14
28
|
```bash
|
package/README.md
CHANGED
|
@@ -28,6 +28,20 @@ DSH Web 插件:**会话历史文件 + 会话健康检查**。
|
|
|
28
28
|
> 命名边界:**本插件不提供搜索与索引**。搜索/独立索引属于另一片(`dsh-search-index`),
|
|
29
29
|
> 两侧互不出现对方的子域字眼,也不互相解释对方字段。
|
|
30
30
|
|
|
31
|
+
## 界面预览
|
|
32
|
+
|
|
33
|
+
侧边栏底部新增 **「会话管家」** 入口,与搜索、设置并列:
|
|
34
|
+
|
|
35
|
+

|
|
36
|
+
|
|
37
|
+
**养老院(会话历史文件)**——官方归档集合一览,每条给出体积与磁盘实体状态;勾选后可取消归档或清理:
|
|
38
|
+
|
|
39
|
+

|
|
40
|
+
|
|
41
|
+
**体检**——四门体检的入口,扫完给出可逆处方:
|
|
42
|
+
|
|
43
|
+

|
|
44
|
+
|
|
31
45
|
## 安装
|
|
32
46
|
|
|
33
47
|
```bash
|
package/dsh.plugin.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "dsh-manage-session",
|
|
3
3
|
"name": "dsh-manage-session",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.4.1",
|
|
5
5
|
"description": {
|
|
6
|
-
"en": "Session Steward: browse and bulk-prune the official session archive set, and run a four-gate session health checkup that ends in a reversible, backup-first prescription. Never rewrites session logs.",
|
|
7
|
-
"zh": "
|
|
6
|
+
"en": "Session Steward (DSH 0.1.5 line): browse and bulk-prune the official session archive set, and run a four-gate session health checkup that ends in a reversible, backup-first prescription. Never rewrites session logs.",
|
|
7
|
+
"zh": "会话管家(DSH 0.1.5 专线):浏览并批量清理官方会话归档集合,并对会话做四门体检,最终给出可逆、先备份的处方。不改写会话日志。"
|
|
8
8
|
},
|
|
9
9
|
"homepage": "https://github.com/drscrewdriver/dsh-manage-session",
|
|
10
10
|
"repository": {
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
"license": "MIT",
|
|
15
15
|
"engines": {
|
|
16
|
-
"dsh": ">=0.1.
|
|
16
|
+
"dsh": ">=0.1.7-rc.1 <0.1.8-0"
|
|
17
17
|
},
|
|
18
18
|
"components": {
|
|
19
19
|
"host": "lib/index.js",
|
package/lib/client.js
CHANGED
|
@@ -31,6 +31,7 @@ window.__ModuleLoader__.load({
|
|
|
31
31
|
"card.health.desc": "体检 → 处方 → 出院。关闭后不注册体检接口、不渲染该页签。",
|
|
32
32
|
"card.readonly": "当前会话无法修改设置(只读挂载)。",
|
|
33
33
|
"card.unavailable": "设置服务不可用,无法读取配置。",
|
|
34
|
+
"family.title": "起子插件设置",
|
|
34
35
|
"panel.title": "会话管家",
|
|
35
36
|
"panel.tab.history": "养老院",
|
|
36
37
|
"panel.tab.health": "体检",
|
|
@@ -64,15 +65,18 @@ window.__ModuleLoader__.load({
|
|
|
64
65
|
"health.scanning": "体检中…",
|
|
65
66
|
"health.progress": "{done}/{total} · 已用 {sec}s",
|
|
66
67
|
"health.empty": "未发现异常会话。",
|
|
67
|
-
"health.clean": "
|
|
68
|
+
"health.clean": "全部检查通过。",
|
|
68
69
|
"health.level.ok": "正常",
|
|
69
70
|
"health.level.warn": "注意",
|
|
70
71
|
"health.level.skipped": "未检查",
|
|
71
72
|
"health.level.fail": "异常",
|
|
73
|
+
"health.priority.high": "优先",
|
|
74
|
+
"health.gate.generation": "代次产物",
|
|
72
75
|
"health.gate.log-integrity": "日志完整性",
|
|
73
76
|
"health.gate.projection-cache": "投影缓存",
|
|
74
77
|
"health.gate.lossless-json": "无损 JSON",
|
|
75
78
|
"health.gate.cold-read": "可接续性",
|
|
79
|
+
"health.gate.source-kind": "插件署名",
|
|
76
80
|
"health.attribution": "归属",
|
|
77
81
|
"health.field": "字段",
|
|
78
82
|
"health.unknownOwner": "归属未知",
|
|
@@ -81,6 +85,11 @@ window.__ModuleLoader__.load({
|
|
|
81
85
|
"health.phase.discharge": "③ 出院",
|
|
82
86
|
"health.repair": "执行可逆处置",
|
|
83
87
|
"health.repairing": "处置中…",
|
|
88
|
+
"health.sourceMigrate": "转换旧署名",
|
|
89
|
+
"health.sourceMigrating": "转换中…",
|
|
90
|
+
"health.sourceMigrated": "已转换 {n} 行旧署名(备份:{backup})",
|
|
91
|
+
"health.sourceMigrateNone": "没有需要转换的旧署名行",
|
|
92
|
+
"health.sourceMigrateFail": "转换失败:{error}",
|
|
84
93
|
"health.before": "处置前",
|
|
85
94
|
"health.after": "处置后",
|
|
86
95
|
"health.quarantined": "已隔离投影缓存记录(重启 DSH 后重折叠)",
|
|
@@ -109,6 +118,7 @@ window.__ModuleLoader__.load({
|
|
|
109
118
|
"card.health.desc": "Checkup → Prescription → Discharge. When off, health routes and the tab are gone.",
|
|
110
119
|
"card.readonly": "This session cannot edit settings (read-only mount).",
|
|
111
120
|
"card.unavailable": "Settings service unavailable; configuration cannot be read.",
|
|
121
|
+
"family.title": "Screwdriver plugin settings",
|
|
112
122
|
"panel.title": "Session Steward",
|
|
113
123
|
"panel.tab.history": "Retirement Home",
|
|
114
124
|
"panel.tab.health": "Checkup",
|
|
@@ -142,15 +152,18 @@ window.__ModuleLoader__.load({
|
|
|
142
152
|
"health.scanning": "Running…",
|
|
143
153
|
"health.progress": "{done}/{total} · {sec}s elapsed",
|
|
144
154
|
"health.empty": "No problem session found.",
|
|
145
|
-
"health.clean": "All
|
|
155
|
+
"health.clean": "All checks passed.",
|
|
146
156
|
"health.level.ok": "ok",
|
|
147
157
|
"health.level.warn": "warn",
|
|
148
158
|
"health.level.skipped": "Not checked",
|
|
149
159
|
"health.level.fail": "fail",
|
|
160
|
+
"health.priority.high": "Priority",
|
|
161
|
+
"health.gate.generation": "Generation artifact",
|
|
150
162
|
"health.gate.log-integrity": "Log integrity",
|
|
151
163
|
"health.gate.projection-cache": "Projection cache",
|
|
152
164
|
"health.gate.lossless-json": "Lossless JSON",
|
|
153
165
|
"health.gate.cold-read": "Continuability",
|
|
166
|
+
"health.gate.source-kind": "Plugin signature",
|
|
154
167
|
"health.attribution": "Owner",
|
|
155
168
|
"health.field": "Field",
|
|
156
169
|
"health.unknownOwner": "owner unknown",
|
|
@@ -159,6 +172,11 @@ window.__ModuleLoader__.load({
|
|
|
159
172
|
"health.phase.discharge": "3) Discharge",
|
|
160
173
|
"health.repair": "Apply reversible repair",
|
|
161
174
|
"health.repairing": "Repairing…",
|
|
175
|
+
"health.sourceMigrate": "Migrate legacy signatures",
|
|
176
|
+
"health.sourceMigrating": "Migrating…",
|
|
177
|
+
"health.sourceMigrated": "Migrated {n} legacy signature rows (backup: {backup})",
|
|
178
|
+
"health.sourceMigrateNone": "No legacy signature rows to migrate",
|
|
179
|
+
"health.sourceMigrateFail": "Migration failed: {error}",
|
|
162
180
|
"health.before": "Before",
|
|
163
181
|
"health.after": "After",
|
|
164
182
|
"health.quarantined": "Projection cache quarantined (refolds after a DSH restart)",
|
|
@@ -192,17 +210,14 @@ window.__ModuleLoader__.load({
|
|
|
192
210
|
/**
|
|
193
211
|
* 设置卡(插件配置行):一个可折叠抽屉 —— 会话历史文件 / 健康检查。
|
|
194
212
|
*
|
|
195
|
-
* **chrome
|
|
196
|
-
*
|
|
213
|
+
* **chrome 必须自己渲染**:宿主不为贡献卡提供任何外壳,标题、说明、
|
|
214
|
+
* 展开/收起全归插件自己所有。早期版本只渲染裸行,与同分区其它插件
|
|
215
|
+
* 的抽屉形态不一致,还让 `card.title` / `card.desc` 成为死文案。
|
|
197
216
|
*
|
|
198
|
-
*
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
* 结果既与分区内其它插件的抽屉形态不一致,又让 `card.title` / `card.desc`
|
|
203
|
-
* 变成定义了却没人渲染的死文案。
|
|
204
|
-
*
|
|
205
|
-
* 读写走 settingsScope 的显式 `set`(用户在控件上的直接操作,非隐式提交)。
|
|
217
|
+
* 读写走 configForms(`configForms.get(STEWARD_ENTRY_ID)`,以本插件 entry 为键;
|
|
218
|
+
* 旧宿主回退 settingsScope 绑定)的显式 `set`。0.1.7 起本卡挂在插件族共用
|
|
219
|
+
* 设置节(`dsh-family.tab`,dsh-thinking-levels 顶级「起子插件设置」节声明)里,
|
|
220
|
+
* 与同节其它抽屉一样默认展开。
|
|
206
221
|
*/
|
|
207
222
|
/** 一行设置项。 */
|
|
208
223
|
function Row(props) {
|
|
@@ -265,10 +280,10 @@ window.__ModuleLoader__.load({
|
|
|
265
280
|
strokeLinejoin: "round"
|
|
266
281
|
}))]);
|
|
267
282
|
}
|
|
268
|
-
/**
|
|
283
|
+
/** 插件设置卡主体(抽屉)。默认展开,family 设置节内直接呈现完整面板。 */
|
|
269
284
|
function StewardSettingsCard({ t, scope }) {
|
|
270
285
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
271
|
-
const [open, setOpen] = (0, react.useState)(
|
|
286
|
+
const [open, setOpen] = (0, react.useState)(true);
|
|
272
287
|
const value = snapshot.value ?? {};
|
|
273
288
|
const writable = snapshot.writable;
|
|
274
289
|
const rows = [];
|
|
@@ -669,16 +684,18 @@ window.__ModuleLoader__.load({
|
|
|
669
684
|
//#endregion
|
|
670
685
|
//#region src/client/health-panel.tsx
|
|
671
686
|
/**
|
|
672
|
-
*
|
|
687
|
+
* 体检面板:体检(五门)→ 处方(命令清单)→ 出院(可逆处置 + before/after 对照)。
|
|
673
688
|
*
|
|
674
689
|
* 三态流转完全由 host 侧报告驱动,客户端只做展示与触发;重型动作都在 host,
|
|
675
690
|
* 渲染周期内不发同步重活。
|
|
676
691
|
*/
|
|
677
692
|
const GATE_LABEL = {
|
|
693
|
+
"generation": "health.gate.generation",
|
|
678
694
|
"log-integrity": "health.gate.log-integrity",
|
|
679
695
|
"projection-cache": "health.gate.projection-cache",
|
|
680
696
|
"lossless-json": "health.gate.lossless-json",
|
|
681
|
-
"cold-read": "health.gate.cold-read"
|
|
697
|
+
"cold-read": "health.gate.cold-read",
|
|
698
|
+
"source-kind": "health.gate.source-kind"
|
|
682
699
|
};
|
|
683
700
|
const LEVEL_LABEL = {
|
|
684
701
|
ok: "health.level.ok",
|
|
@@ -728,6 +745,9 @@ window.__ModuleLoader__.load({
|
|
|
728
745
|
const [error, setError] = (0, react.useState)(null);
|
|
729
746
|
const [detail, setDetail] = (0, react.useState)(null);
|
|
730
747
|
const [repairing, setRepairing] = (0, react.useState)(false);
|
|
748
|
+
const [migrating, setMigrating] = (0, react.useState)(false);
|
|
749
|
+
/** 署名转换结果提示(一次性;点击转换或返回列表时清除)。 */
|
|
750
|
+
const [migrateNote, setMigrateNote] = (0, react.useState)(null);
|
|
731
751
|
const [discharge, setDischarge] = (0, react.useState)(null);
|
|
732
752
|
const [copied, setCopied] = (0, react.useState)(null);
|
|
733
753
|
/** 分批扫描进度:done = 已访问会话数,total = 语料总数(0 表示尚未拿到分母)。 */
|
|
@@ -783,6 +803,7 @@ window.__ModuleLoader__.load({
|
|
|
783
803
|
setError(null);
|
|
784
804
|
setDetail(null);
|
|
785
805
|
setDischarge(null);
|
|
806
|
+
setMigrateNote(null);
|
|
786
807
|
setFindings(null);
|
|
787
808
|
setCacheInfo(null);
|
|
788
809
|
setProgress({
|
|
@@ -851,6 +872,7 @@ window.__ModuleLoader__.load({
|
|
|
851
872
|
const openDetail = (sessionId) => {
|
|
852
873
|
setError(null);
|
|
853
874
|
setDischarge(null);
|
|
875
|
+
setMigrateNote(null);
|
|
854
876
|
callHostAny("session-health-session", { sessionId }, 12e4).then((res) => {
|
|
855
877
|
if (res.ok === true) {
|
|
856
878
|
setDetail(res);
|
|
@@ -881,6 +903,28 @@ window.__ModuleLoader__.load({
|
|
|
881
903
|
setCopied(text);
|
|
882
904
|
} catch {}
|
|
883
905
|
};
|
|
906
|
+
const runSourceMigrate = (sessionId) => {
|
|
907
|
+
setMigrating(true);
|
|
908
|
+
setError(null);
|
|
909
|
+
setMigrateNote(null);
|
|
910
|
+
callHostAny("session-health-source-migrate", { sessionId }, 12e4).then((res) => {
|
|
911
|
+
if (res.after !== void 0) {
|
|
912
|
+
setDetail({
|
|
913
|
+
ok: true,
|
|
914
|
+
report: res.after,
|
|
915
|
+
prescriptions: detail?.prescriptions ?? []
|
|
916
|
+
});
|
|
917
|
+
reconcileRow(res.after);
|
|
918
|
+
}
|
|
919
|
+
const changed = res.outcome?.changedRows ?? 0;
|
|
920
|
+
if (res.ok === true && changed > 0) setMigrateNote(translate(t, "health.sourceMigrated", {
|
|
921
|
+
n: changed,
|
|
922
|
+
backup: res.outcome?.backup ?? ""
|
|
923
|
+
}));
|
|
924
|
+
else if (res.ok === true) setMigrateNote(translate(t, "health.sourceMigrateNone"));
|
|
925
|
+
else setMigrateNote(translate(t, "health.sourceMigrateFail", { error: res.outcome?.error ?? res.error ?? "未知原因" }));
|
|
926
|
+
}).catch((err) => setError(String(err instanceof Error ? err.message : err))).finally(() => setMigrating(false));
|
|
927
|
+
};
|
|
884
928
|
const report = detail?.report ?? discharge?.after;
|
|
885
929
|
const lines = [];
|
|
886
930
|
lines.push((0, react.createElement)("div", {
|
|
@@ -1004,6 +1048,10 @@ window.__ModuleLoader__.load({
|
|
|
1004
1048
|
key: "lv",
|
|
1005
1049
|
className: `dss_levelPill dss_level_${item.level}`
|
|
1006
1050
|
}, translate(t, LEVEL_LABEL[item.level])),
|
|
1051
|
+
item.priority === "high" && (0, react.createElement)("span", {
|
|
1052
|
+
key: "p",
|
|
1053
|
+
className: "dss_meta"
|
|
1054
|
+
}, translate(t, "health.priority.high")),
|
|
1007
1055
|
(0, react.createElement)("span", {
|
|
1008
1056
|
key: "id",
|
|
1009
1057
|
className: "dss_meta dss_uuid"
|
|
@@ -1043,6 +1091,25 @@ window.__ModuleLoader__.load({
|
|
|
1043
1091
|
className: "dss_spinner",
|
|
1044
1092
|
"aria-hidden": "true"
|
|
1045
1093
|
}) : null, (0, react.createElement)("span", { key: "tx" }, repairing ? translate(t, "health.repairing") : translate(t, "health.repair"))])]));
|
|
1094
|
+
if (report.gates.some((gate) => gate.id === "source-kind" && gate.level === "warn")) lines.push((0, react.createElement)("div", {
|
|
1095
|
+
key: "migrate",
|
|
1096
|
+
className: "dss_phaseRow"
|
|
1097
|
+
}, [(0, react.createElement)("button", {
|
|
1098
|
+
key: "m",
|
|
1099
|
+
type: "button",
|
|
1100
|
+
className: "dss_actBtn",
|
|
1101
|
+
disabled: migrating,
|
|
1102
|
+
onClick: () => {
|
|
1103
|
+
runSourceMigrate(report.sessionId);
|
|
1104
|
+
}
|
|
1105
|
+
}, [migrating ? (0, react.createElement)("span", {
|
|
1106
|
+
key: "sp",
|
|
1107
|
+
className: "dss_spinner",
|
|
1108
|
+
"aria-hidden": "true"
|
|
1109
|
+
}) : null, (0, react.createElement)("span", { key: "tx" }, migrating ? translate(t, "health.sourceMigrating") : translate(t, "health.sourceMigrate"))]), migrateNote !== null && (0, react.createElement)("span", {
|
|
1110
|
+
key: "note",
|
|
1111
|
+
className: "dss_status"
|
|
1112
|
+
}, migrateNote)]));
|
|
1046
1113
|
const prescriptions = detail?.prescriptions ?? discharge?.prescriptions ?? [];
|
|
1047
1114
|
if (prescriptions.length > 0) lines.push((0, react.createElement)("div", {
|
|
1048
1115
|
key: "cmds",
|
|
@@ -1218,8 +1285,16 @@ window.__ModuleLoader__.load({
|
|
|
1218
1285
|
const style = document.createElement("style");
|
|
1219
1286
|
style.id = id;
|
|
1220
1287
|
style.textContent = `
|
|
1288
|
+
/* 侧栏 footer 槽位公约(2026-09-26):一行多入口(第三方 dsh-context 等)会互相
|
|
1289
|
+
挤占 —— 宿主 .footerActions 是 nowrap flex 行。这里允许容器换行,并把本插件
|
|
1290
|
+
入口钉成独占一整行(flex-basis:100%);其余入口(含第三方)自然落到后续行,
|
|
1291
|
+
各行内部自行布局,谁也不挤谁。类名用 [class*=] 中段匹配:宿主是 CSS Module
|
|
1292
|
+
哈希类名(实测形如 hHd-Xa_footerActions —— <hash>_<name>,哈希在前),
|
|
1293
|
+
中段跨版本稳定。 */
|
|
1294
|
+
[class*="footerActions"]{flex-wrap:wrap;row-gap:2px;height:auto;min-height:0}
|
|
1295
|
+
/* 宽栏:独占一整行、放弃主动收缩(flex-shrink:0),行内居中;收起轨道回落自然宽度。 */
|
|
1221
1296
|
.dss_entryWrap{flex:0 1 auto;display:inline-flex;align-items:center;min-width:0}
|
|
1222
|
-
.dss_entryWrapWide{margin-left:
|
|
1297
|
+
.dss_entryWrapWide{flex:0 0 100%;width:100%;justify-content:center;margin-left:0}
|
|
1223
1298
|
.dss_footerEntry{box-sizing:border-box;display:inline-flex;align-items:center;justify-content:center;gap:4px;height:42px;padding:0 10px 0 8px;border:none;border-radius:12px;background:transparent;cursor:pointer;color:var(--dsw-alias-label-primary);font-family:inherit;font-size:14px;line-height:22px;white-space:nowrap;overflow:hidden;transition:background-color 160ms ease-out,color 160ms ease-out}
|
|
1224
1299
|
.dss_footerEntry:hover{background:var(--dsw-alias-interactive-bg-hover);color:var(--dsw-alias-label-primary)}
|
|
1225
1300
|
.dss_footerEntryRail{width:28px;height:28px;padding:0;gap:0;border-radius:50%}
|
|
@@ -1310,7 +1385,7 @@ window.__ModuleLoader__.load({
|
|
|
1310
1385
|
}
|
|
1311
1386
|
/**
|
|
1312
1387
|
* 客户端插件主体。
|
|
1313
|
-
* @param ctx - 客户端上下文(slots、可选 locale /
|
|
1388
|
+
* @param ctx - 客户端上下文(slots、可选 locale / configForms)。
|
|
1314
1389
|
*/
|
|
1315
1390
|
function apply(ctx) {
|
|
1316
1391
|
ctx.effect(() => injectStyles(), "dsh-manage-session: stylesheet");
|
|
@@ -1319,33 +1394,101 @@ window.__ModuleLoader__.load({
|
|
|
1319
1394
|
zh,
|
|
1320
1395
|
en
|
|
1321
1396
|
}), "dsh-manage-session: dictionaries");
|
|
1322
|
-
const
|
|
1397
|
+
const configForms = ctx.get("configForms");
|
|
1398
|
+
const legacySettings = ctx.get("settingsScope");
|
|
1399
|
+
const bound = configForms?.get("dsh-manage-session") ?? legacySettings?.bind({ namespace: "session-steward" });
|
|
1323
1400
|
const slots = ctx.get("slots");
|
|
1324
1401
|
if (slots === void 0) return;
|
|
1325
1402
|
slots.inject("sidebar.footer.action", () => slots.register({
|
|
1326
1403
|
name: "sidebar.footer.action",
|
|
1327
1404
|
id: STEWARD_ENTRY_ID,
|
|
1328
|
-
order:
|
|
1405
|
+
order: 6
|
|
1329
1406
|
}, (props) => (0, react.createElement)(StewardEntry, {
|
|
1330
1407
|
...props,
|
|
1331
1408
|
scope: bound
|
|
1332
1409
|
})), "dsh-manage-session: sidebar footer entry");
|
|
1333
|
-
slots.inject("
|
|
1334
|
-
name: "
|
|
1335
|
-
id:
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1410
|
+
slots.inject("dsh-family.tab", () => slots.register({
|
|
1411
|
+
name: "dsh-family.tab",
|
|
1412
|
+
id: STEWARD_ENTRY_ID,
|
|
1413
|
+
order: 40,
|
|
1414
|
+
label: () => translate(locale?.bind?.(NS), "card.title"),
|
|
1415
|
+
locale: NS
|
|
1416
|
+
}, (props) => (0, react.createElement)(StewardSettingsCard, {
|
|
1417
|
+
scope: bound,
|
|
1418
|
+
t: props.t
|
|
1419
|
+
})), "dsh-manage-session: family settings tab");
|
|
1420
|
+
const familyTabsHooks = makeFamilyTabsHooks(slots);
|
|
1421
|
+
ctx.effect(() => {
|
|
1422
|
+
let claimed;
|
|
1423
|
+
const timer = setTimeout(() => {
|
|
1424
|
+
if (slots.entries("settings.section").some((e) => e.options.id === FAMILY_SECTION_ID)) return;
|
|
1425
|
+
try {
|
|
1426
|
+
claimed = slots.register({
|
|
1427
|
+
name: "settings.section",
|
|
1428
|
+
id: FAMILY_SECTION_ID,
|
|
1429
|
+
order: 40,
|
|
1430
|
+
label: () => translate(locale?.bind?.(NS), "family.title"),
|
|
1431
|
+
locale: NS,
|
|
1432
|
+
inject: () => ({ hooks: { tabs: familyTabsHooks } }),
|
|
1433
|
+
children: { [FAMILY_CHILD_KEY]: {
|
|
1434
|
+
kind: "list",
|
|
1435
|
+
scope: "root"
|
|
1436
|
+
} }
|
|
1437
|
+
}, StewardFamilySection);
|
|
1438
|
+
} catch {}
|
|
1439
|
+
}, FAMILY_HOST_GRACE_MS);
|
|
1440
|
+
return () => {
|
|
1441
|
+
clearTimeout(timer);
|
|
1442
|
+
if (typeof claimed === "function") claimed();
|
|
1443
|
+
};
|
|
1444
|
+
}, "dsh-manage-session: family fallback host");
|
|
1445
|
+
}
|
|
1446
|
+
/** 家族节固定 id(与 thinking-levels / guard 的注册严格一致)。 */
|
|
1447
|
+
const FAMILY_SECTION_ID = "dsh-family";
|
|
1448
|
+
/** 家族子席位 key(与 thinking-levels 的声明严格一致)。 */
|
|
1449
|
+
const FAMILY_CHILD_KEY = "dsh-family.tab";
|
|
1450
|
+
/** 接管宽限期:guard 2000ms 先试,本插件 2600ms 兜底。 */
|
|
1451
|
+
const FAMILY_HOST_GRACE_MS = 2600;
|
|
1452
|
+
/** 接管节的账本投影 hooks(与 thinking-levels 的 FamilySectionInjected 同形;locale 变更也触发重渲染)。 */
|
|
1453
|
+
function makeFamilyTabsHooks(slots) {
|
|
1454
|
+
let version = -1;
|
|
1455
|
+
let tabs = [];
|
|
1456
|
+
return {
|
|
1457
|
+
getSnapshot: () => {
|
|
1458
|
+
const next = slots.getVersion(FAMILY_CHILD_KEY);
|
|
1459
|
+
if (next !== version) {
|
|
1460
|
+
version = next;
|
|
1461
|
+
tabs = slots.entries(FAMILY_CHILD_KEY).map((entry) => ({
|
|
1462
|
+
id: entry.options.id ?? "",
|
|
1463
|
+
order: entry.options.order ?? 0,
|
|
1464
|
+
label: typeof entry.options.label === "function" ? (() => {
|
|
1465
|
+
try {
|
|
1466
|
+
return String(entry.options.label() ?? entry.options.id ?? "");
|
|
1467
|
+
} catch {
|
|
1468
|
+
return String(entry.options.id ?? "");
|
|
1469
|
+
}
|
|
1470
|
+
})() : String(entry.options.label ?? entry.options.id ?? "")
|
|
1471
|
+
})).sort((a, b) => a.order - b.order);
|
|
1472
|
+
}
|
|
1473
|
+
return tabs;
|
|
1474
|
+
},
|
|
1475
|
+
subscribe: (listener) => slots.subscribe(FAMILY_CHILD_KEY, listener)
|
|
1476
|
+
};
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* 家族节接管组件(fallback host section):纯通用渲染 —— 把 `dsh-family.tab`
|
|
1480
|
+
* 账本里的每张贡献卡(含本插件自己的,接管时本插件也声明了该子席位)按
|
|
1481
|
+
* order 依次 renderSlot;各卡本身是默认展开的抽屉,整节即一页抽屉。
|
|
1482
|
+
*/
|
|
1483
|
+
function StewardFamilySection(props) {
|
|
1484
|
+
const tabs = (0, react.useSyncExternalStore)(props.hooks.tabs.subscribe, props.hooks.tabs.getSnapshot);
|
|
1485
|
+
return (0, react.createElement)("div", { style: {
|
|
1486
|
+
display: "grid",
|
|
1487
|
+
gap: "12px"
|
|
1488
|
+
} }, ...tabs.map((row) => (0, react.createElement)("div", { key: row.id }, props.renderSlot(FAMILY_CHILD_KEY, {}, {
|
|
1489
|
+
only: row.id,
|
|
1490
|
+
fallback: null
|
|
1491
|
+
}))));
|
|
1349
1492
|
}
|
|
1350
1493
|
/** 入口按钮:持有面板开关;两个页签的可见性来自配置快照。 */
|
|
1351
1494
|
function StewardEntry(props) {
|