dsh-session-guard 4.1.11 → 4.1.16
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 +29 -0
- package/CHANGELOG.ko.md +29 -0
- package/CHANGELOG.md +83 -0
- package/dsh.plugin.json +1 -1
- package/lib/client.js +239 -95
- package/lib/client.js.map +1 -1
- package/package.json +1 -1
- package/src/client/index.ts +41 -29
- package/src/client/settings-bridge.ts +202 -0
- package/src/client/settings-card.tsx +9 -3
- package/src/index.js +16 -0
- package/src/settings-bridge.js +141 -0
- package/src/client/shell-overlay.tsx +0 -69
package/CHANGELOG.ja.md
CHANGED
|
@@ -11,6 +11,35 @@
|
|
|
11
11
|
- [Changelog на русском](./CHANGELOG.ru.md)
|
|
12
12
|
- [Changelog en español](./CHANGELOG.es.md)
|
|
13
13
|
|
|
14
|
+
## 4.1.16 — 2026-10-14(マニフェスト整合/欠落していた 4.1.15 の補記)
|
|
15
|
+
|
|
16
|
+
### 修正
|
|
17
|
+
|
|
18
|
+
- `dsh.plugin.json` の版数を `package.json` と一致させた。公開済みの 4.1.15 ターボールは
|
|
19
|
+
まだ 4.1.14 と書かれていた(レジストリから実際にダウンロードして確認)。
|
|
20
|
+
- 動作変更なし。
|
|
21
|
+
- テストの構造ピンが退役済みの歯車入口をまだ要求しており、4.1.15 以来スイートが赤だった。
|
|
22
|
+
不在を断言する側へ修正した。
|
|
23
|
+
|
|
24
|
+
## 4.1.15 — 2026-10-08(補記:リリース当時この項が書かれていませんでした)
|
|
25
|
+
|
|
26
|
+
### 削除
|
|
27
|
+
|
|
28
|
+
- シェルレベルの `shell.overlay` シート注入を廃止:右下固定の歯車ボタンと浮層レイヤーを除去
|
|
29
|
+
(`src/client/shell-overlay.tsx` を削除)。設定への入口はホストの設定ページのみを頼る。
|
|
30
|
+
|
|
31
|
+
## 4.1.14 — 2026-10-08
|
|
32
|
+
|
|
33
|
+
### 削除
|
|
34
|
+
|
|
35
|
+
## 4.1.13 — 2026-10-08
|
|
36
|
+
|
|
37
|
+
### 修正 — ≤0.1.5 でのブリッジ記述子スケルトン(5セルゲートで発覚)
|
|
38
|
+
|
|
39
|
+
- **≤0.1.5 ホストの describe `value` はフィールドスケルトン**(実測:`{enabled:{},…}`、実値は `base`+`user` 層)。旧世代で `descriptor.value` をそのまま消費すると `officialProviders` が `{}` になり `?? []` を素通りして `ListRow` の `values.join` が throw、`dsh-family.tab` スロット全体が殉葬していた。`descriptorPlainValue()` がスケルトンを検出して `{...base, ...user}` を再構築。
|
|
40
|
+
- **カード字段消毒(多層防御)**:スロットは一つの描画エラーで全族タブが死ぬため、`ListRow` 入力を `toList()` で無毒化、`拦截方式` セレクトは非 `error` 値を `hold` にフォールバック。
|
|
41
|
+
- 5セルファームゲートで検証:0.1.0/0.1.5 はブリッジ経由、0.1.7 は configForms 経由でシード値 `321123` の読み返しを確認。
|
|
42
|
+
|
|
14
43
|
## 4.0.0 — 2026-09-29
|
|
15
44
|
|
|
16
45
|
### Changed
|
package/CHANGELOG.ko.md
CHANGED
|
@@ -11,6 +11,35 @@
|
|
|
11
11
|
- [Changelog на русском](./CHANGELOG.ru.md)
|
|
12
12
|
- [Changelog en español](./CHANGELOG.es.md)
|
|
13
13
|
|
|
14
|
+
## 4.1.16 — 2026-10-14 (매니페스트 정리, 누락된 4.1.15 항목 보충)
|
|
15
|
+
|
|
16
|
+
### 수정
|
|
17
|
+
|
|
18
|
+
- `dsh.plugin.json` 버전을 `package.json` 과 일치시켰습니다. 게시된 4.1.15 tarball 은
|
|
19
|
+
여전히 4.1.14 였습니다 (레지스트리에서 직접 내려받아 확인).
|
|
20
|
+
- 동작 변경 없음.
|
|
21
|
+
- 구조 핀이 이미 폐지된 톱니 진입점을 요구해서 4.1.15 이후 테스트가 빨랬습니다.
|
|
22
|
+
부존재를 단언하는 쪽으로 고쳤습니다.
|
|
23
|
+
|
|
24
|
+
## 4.1.15 — 2026-10-08 (보충: 배포 당시에 이 항목이 기록되지 않았습니다)
|
|
25
|
+
|
|
26
|
+
### 삭제
|
|
27
|
+
|
|
28
|
+
- 셸 레벨 `shell.overlay` 슬롯 주입 폐지: 오른쪽 아래 고정 톱니 버튼과 부유 레이어 제거
|
|
29
|
+
(`src/client/shell-overlay.tsx` 삭제). 설정 진입점은 호스트 설정 페이지에만 의존합니다.
|
|
30
|
+
|
|
31
|
+
## 4.1.14 — 2026-10-08
|
|
32
|
+
|
|
33
|
+
### 제거
|
|
34
|
+
|
|
35
|
+
## 4.1.13 — 2026-10-08
|
|
36
|
+
|
|
37
|
+
### 수정 — ≤0.1.5 브리지 디스크립터 스켈레톤(5셀 게이트 발견)
|
|
38
|
+
|
|
39
|
+
- **≤0.1.5 호스트의 describe `value`는 필드 스켈레톤**(실측: `{enabled:{},…}`, 실값은 `base`+`user` 레이어). 구세대에서 `descriptor.value`를 그대로 쓰면 `officialProviders`가 `{}`가 되어 `?? []`를 통과하고 `ListRow`의 `values.join`이 throw, `dsh-family.tab` 슬롯 전체가 순직.`descriptorPlainValue()`가 스켈레톤을 감지해 `{...base, ...user}`로 재구성.
|
|
40
|
+
- **카드 필드 소독(다층 방어)**: 슬롯은 렌더 오류 하나로 전체 탭이 죽으므로 `ListRow` 입력을 `toList()`로 무독화, `拦截方式` 셀렉트는 non-`error` 값을 `hold`로 폴백.
|
|
41
|
+
- 5셀 팜 게이트 검증: 0.1.0/0.1.5는 브리지 경유, 0.1.7은 configForms 경유로 시드값 `321123` 재판독 확인.
|
|
42
|
+
|
|
14
43
|
## 4.0.0 — 2026-09-29
|
|
15
44
|
|
|
16
45
|
### Changed
|
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,89 @@ All notable changes to `dsh-session-guard` are recorded here. Versions follow se
|
|
|
11
11
|
- [Changelog на русском](./CHANGELOG.ru.md)
|
|
12
12
|
- [Changelog en español](./CHANGELOG.es.md)
|
|
13
13
|
|
|
14
|
+
## 4.1.16 — 2026-10-14 (metadata alignment; backfill the missing 4.1.15 entry)
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
|
|
18
|
+
- `dsh.plugin.json` now carries the same version as `package.json`. The published 4.1.15 tarball
|
|
19
|
+
still said 4.1.14 (verified by downloading it from the registry), so the two manifests disagreed in a released artifact.
|
|
20
|
+
- No behaviour change in this release.
|
|
21
|
+
- Test suite repaired: the structural pin in `tests/settings-bridge.test.mjs` still demanded the
|
|
22
|
+
retired gear entry, so the suite had been red since 4.1.15; it now asserts the opposite (absence).
|
|
23
|
+
|
|
24
|
+
## 4.1.15 — 2026-10-08 (backfilled: this entry was never written at release time)
|
|
25
|
+
|
|
26
|
+
### Removed
|
|
27
|
+
|
|
28
|
+
- The shell-level `shell.overlay` slot injection: the fixed bottom-right gear button and its
|
|
29
|
+
positioned floating layer are gone (`src/client/shell-overlay.tsx` deleted). The settings entry
|
|
30
|
+
now relies on the host settings page alone.
|
|
31
|
+
|
|
32
|
+
## 4.1.14 — 2026-10-08
|
|
33
|
+
|
|
34
|
+
### Removed
|
|
35
|
+
|
|
36
|
+
## 4.1.13 — 2026-10-08
|
|
37
|
+
|
|
38
|
+
### Fixed — bridge descriptor skeleton on ≤0.1.5 (five-cell gate catch, 2026-10-08)
|
|
39
|
+
|
|
40
|
+
- **Host describe `value` is a field skeleton on ≤0.1.5** (0.1.0/0.1.5 empirical):
|
|
41
|
+
the old host generations serialize `descriptor.value` as `{enabled:{},…,officialProviders:{}}`
|
|
42
|
+
(one empty object per schema field) while the real merged config lives in
|
|
43
|
+
`descriptor.base` (combined defaults) + `descriptor.user` (user layer).
|
|
44
|
+
`BridgeDocHandle` consumed `value` verbatim, so `officialProviders` arrived as
|
|
45
|
+
`{}` — truthy non-array, invisible to `?? []` — and `ListRow`'s
|
|
46
|
+
`values.join` threw, crashing the whole `dsh-family.tab` slot (guard card +
|
|
47
|
+
IT card + TL nav all died together). `descriptorPlainValue()` now detects the
|
|
48
|
+
skeleton (object whose every own value is an empty plain object — impossible
|
|
49
|
+
for guard's scalar/array config) and rebuilds the effective value as
|
|
50
|
+
`{...base, ...user}` (shallow merge is exact for guard's flat config).
|
|
51
|
+
- **Card-field sanitization (defense in depth)**: any card render error kills
|
|
52
|
+
the entire family tab slot on these hosts, so `ListRow` inputs are now
|
|
53
|
+
sanitized at the call sites (`toList()` — non-arrays become `[]`) and the
|
|
54
|
+
`拦截方式` select falls back to `hold` for any non-`error` value. A malformed
|
|
55
|
+
descriptor may degrade one field, never the tab.
|
|
56
|
+
- Verified on the five-cell farm gate: describe replays on 0.1.0/0.1.5 now
|
|
57
|
+
yield plain merged values; the seeded `stepGateTimeoutMs: 321123` reads back
|
|
58
|
+
through the bridge on ≤0.1.5 and natively (configForms) on 0.1.7.
|
|
59
|
+
|
|
60
|
+
## 4.1.12 — 2026-10-08
|
|
61
|
+
|
|
62
|
+
### Added — settings webServer bridge (M4/T20-b): dual-track card data plane
|
|
63
|
+
|
|
64
|
+
- **Top-level card registration refactor (prerequisite)**: the three settings
|
|
65
|
+
faces (`dsh-family.tab`, `plugins.bundle.config`, `settings.plugin.item`,
|
|
66
|
+
`shell.overlay`) were registered inside the `onScope` callback of the dual
|
|
67
|
+
scoped-inject waist — on ≤0.1.5 that callback never fires (settingsScope fiber
|
|
68
|
+
never resolves; 0.1.0/0.1.1 lack both services), so the cards never registered
|
|
69
|
+
and a bridge would have had no consumer. All registrations now run at apply
|
|
70
|
+
top level with a `scopeRef` live reference; the callback degrades to a pure
|
|
71
|
+
data arm (perm-gate `client/index.ts` L166-257 shape).
|
|
72
|
+
- **Server bridge** (`src/settings-bridge.js`): `describe`/`mutate` endpoints at
|
|
73
|
+
`/api/session-guard/settings/*` (POST-only, `{ok,value|error}` envelope,
|
|
74
|
+
redacted describe filtered to the plugin ns, ops forwarded to the host
|
|
75
|
+
settings service with `expectedRevision` optimistic concurrency, 256 KB cap).
|
|
76
|
+
Registration is fail-open (error log, never blocks plugin load).
|
|
77
|
+
- **Client bridge handle** (`src/client/settings-bridge.ts`): `BridgeDocHandle`
|
|
78
|
+
implements the same `SettingsScope` face as the native handles
|
|
79
|
+
(getSnapshot/subscribe/set/unset); describe misses retry within a bounded
|
|
80
|
+
budget (settings service resolving later than card mount) before landing on
|
|
81
|
+
`unavailable`. Card faces read `scopeRef.scope ?? bridgeScope()` — native
|
|
82
|
+
first, bridge fallback (perm-gate T13b dual-track). **Generation split in the
|
|
83
|
+
data arm (five-cell empirical, 2026-10-08 night)**: on 0.1.2/0.1.5 the
|
|
84
|
+
`settingsScope` handle is write-alive but read-dead — `set()` persists to
|
|
85
|
+
settings.yaml while `getSnapshot().value` stays `undefined`, so that arm
|
|
86
|
+
deliberately leaves `scopeRef` empty and the card falls to the bridge (bridge
|
|
87
|
+
mutate lands in the same host service; describe heals the read path).
|
|
88
|
+
|
|
89
|
+
### Corrected — 4.1.11 publication note
|
|
90
|
+
|
|
91
|
+
- The 4.1.11 entry said "none of 4.1.5-4.1.10 was published to npm"; **4.1.5
|
|
92
|
+
was published** (npm registry confirms `4.1.5` in versions, and `latest`
|
|
93
|
+
pointed at it until this release line). 4.1.6-4.1.10 indeed never shipped.
|
|
94
|
+
The earlier claim came from reading the npmmirror mirror instead of the
|
|
95
|
+
authoritative registry (registry-pin discipline, B2+).
|
|
96
|
+
|
|
14
97
|
## 4.1.11 — 2026-10-08
|
|
15
98
|
|
|
16
99
|
### Fixed — family-section takeover rebuilt (4.1.5 → 4.1.11 arc; none of 4.1.5-4.1.10 was published to npm)
|
package/dsh.plugin.json
CHANGED
package/lib/client.js
CHANGED
|
@@ -429,6 +429,11 @@ window.__ModuleLoader__.load({
|
|
|
429
429
|
})]
|
|
430
430
|
});
|
|
431
431
|
}
|
|
432
|
+
/** 卡面字段消毒:宿主 describe 形态异常(≤0.1.5 骨架残留等)时槽崩会殉葬全族
|
|
433
|
+
* tab——ListRow 的 values 非 nullish 非数组(如 {})会让 `?? []` 失守,逐卡硬护栏。 */
|
|
434
|
+
function toList(v) {
|
|
435
|
+
return Array.isArray(v) ? v.map(String) : [];
|
|
436
|
+
}
|
|
432
437
|
/** 插件配置卡片主体:可展开的 <li> + header 按钮 + pill switch 字段体。 */
|
|
433
438
|
function SessionGuardCard({ scope }) {
|
|
434
439
|
const snapshot = (0, react.useSyncExternalStore)((listener) => scope.subscribe(listener), () => scope.getSnapshot());
|
|
@@ -517,7 +522,7 @@ window.__ModuleLoader__.load({
|
|
|
517
522
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListRow, {
|
|
518
523
|
label: "追加官方 provider id",
|
|
519
524
|
description: "精确匹配,优先级最高(逗号分隔)",
|
|
520
|
-
values: value.officialProviders
|
|
525
|
+
values: toList(value.officialProviders),
|
|
521
526
|
placeholder: "deepseek-official",
|
|
522
527
|
disabled: readonly,
|
|
523
528
|
onCommit: (next) => {
|
|
@@ -527,7 +532,7 @@ window.__ModuleLoader__.load({
|
|
|
527
532
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(ListRow, {
|
|
528
533
|
label: "官方端点名单",
|
|
529
534
|
description: "baseURL 归一化后的 host(逗号分隔)",
|
|
530
|
-
values: value.officialBaseURLs
|
|
535
|
+
values: toList(value.officialBaseURLs),
|
|
531
536
|
placeholder: "api.deepseek.com",
|
|
532
537
|
disabled: readonly,
|
|
533
538
|
onCommit: (next) => {
|
|
@@ -537,7 +542,7 @@ window.__ModuleLoader__.load({
|
|
|
537
542
|
/* @__PURE__ */ (0, react_jsx_runtime.jsx)(SelectRow, {
|
|
538
543
|
label: "拦截方式",
|
|
539
544
|
description: "挂起等待不报错;或报错并记入延后队列",
|
|
540
|
-
value: value.deferredMode
|
|
545
|
+
value: value.deferredMode === "error" ? "error" : "hold",
|
|
541
546
|
options: [{
|
|
542
547
|
value: "hold",
|
|
543
548
|
label: "挂起等待(退峰自动放行)"
|
|
@@ -615,81 +620,220 @@ window.__ModuleLoader__.load({
|
|
|
615
620
|
});
|
|
616
621
|
}
|
|
617
622
|
//#endregion
|
|
618
|
-
//#region
|
|
623
|
+
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/typeof.js
|
|
624
|
+
function _typeof(o) {
|
|
625
|
+
"@babel/helpers - typeof";
|
|
626
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
627
|
+
return typeof o;
|
|
628
|
+
} : function(o) {
|
|
629
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
630
|
+
}, _typeof(o);
|
|
631
|
+
}
|
|
632
|
+
//#endregion
|
|
633
|
+
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/toPrimitive.js
|
|
634
|
+
function toPrimitive(t, r) {
|
|
635
|
+
if ("object" != _typeof(t) || !t) return t;
|
|
636
|
+
var e = t[Symbol.toPrimitive];
|
|
637
|
+
if (void 0 !== e) {
|
|
638
|
+
var i = e.call(t, r || "default");
|
|
639
|
+
if ("object" != _typeof(i)) return i;
|
|
640
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
641
|
+
}
|
|
642
|
+
return ("string" === r ? String : Number)(t);
|
|
643
|
+
}
|
|
644
|
+
//#endregion
|
|
645
|
+
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/toPropertyKey.js
|
|
646
|
+
function toPropertyKey(t) {
|
|
647
|
+
var i = toPrimitive(t, "string");
|
|
648
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
649
|
+
}
|
|
650
|
+
//#endregion
|
|
651
|
+
//#region \0@oxc-project+runtime@0.146.0/helpers/esm/defineProperty.js
|
|
652
|
+
function _defineProperty(e, r, t) {
|
|
653
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
654
|
+
value: t,
|
|
655
|
+
enumerable: !0,
|
|
656
|
+
configurable: !0,
|
|
657
|
+
writable: !0
|
|
658
|
+
}) : e[r] = t, e;
|
|
659
|
+
}
|
|
660
|
+
//#endregion
|
|
661
|
+
//#region src/client/settings-bridge.ts
|
|
619
662
|
/**
|
|
620
|
-
*
|
|
663
|
+
* BridgeDocHandle — 设置卡双轨数据源的桥轨(M4/T20-b)。
|
|
621
664
|
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
665
|
+
* ≤0.1.5(settingsScope fiber 永不解 / 0.1.0-0.1.1 无 client 服务)时,
|
|
666
|
+
* 配置卡经本句柄走自家 webServer 桥(describe/mutate)读写宿主 settings 服务。
|
|
667
|
+
* 与原生 SettingsScope(scope-face.ts)同构:getSnapshot/subscribe/set/unset。
|
|
668
|
+
* 读 = POST /api/<ns>/settings/describe;写 = POST …/mutate(ops 转发宿主服务),
|
|
669
|
+
* 成功即用回填描述符刷新快照并通知订阅者(无本地第二份状态——权威值始终在宿主)。
|
|
670
|
+
*
|
|
671
|
+
* 与 perm-gate src/client/bridge-scope.ts(T13b/T20-b 泛化模板)的差异:
|
|
672
|
+
* ① 快照形状对齐本仓 SettingsScope(status: loading/ready/unavailable + mode);
|
|
673
|
+
* ② describe 未就绪(settings-unavailable/ns-not-registered)时有限重试
|
|
674
|
+
* (宿主 settings 服务解析晚于卡挂载的窗口),重试耗尽才落 'unavailable'。
|
|
626
675
|
*/
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
return
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
overflowY: "auto",
|
|
658
|
-
zIndex: 10001
|
|
659
|
-
},
|
|
660
|
-
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
661
|
-
type: "button",
|
|
662
|
-
onClick: () => setOpen(false),
|
|
663
|
-
style: {
|
|
664
|
-
position: "sticky",
|
|
665
|
-
top: 6,
|
|
666
|
-
float: "right",
|
|
667
|
-
marginRight: 8,
|
|
668
|
-
zIndex: 2
|
|
669
|
-
},
|
|
670
|
-
children: "✕ 关闭"
|
|
671
|
-
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(SessionGuardCard, { scope: props.scope })]
|
|
672
|
-
}),
|
|
673
|
-
/* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
|
|
674
|
-
type: "button",
|
|
675
|
-
title: "会话守护设置",
|
|
676
|
-
onClick: () => setOpen((state) => !state),
|
|
677
|
-
style: {
|
|
678
|
-
width: 40,
|
|
679
|
-
height: 40,
|
|
680
|
-
borderRadius: "50%",
|
|
681
|
-
border: "1px solid rgba(127,127,127,.4)",
|
|
682
|
-
background: "rgba(30,30,32,.85)",
|
|
683
|
-
color: "inherit",
|
|
684
|
-
cursor: "pointer",
|
|
685
|
-
fontSize: 16,
|
|
686
|
-
boxShadow: "0 4px 16px rgba(0,0,0,.35)"
|
|
687
|
-
},
|
|
688
|
-
children: "🛡"
|
|
689
|
-
})
|
|
690
|
-
]
|
|
691
|
-
});
|
|
676
|
+
/** 与 server 侧 settingsBridgeRoutesFor(src/settings-bridge.js)必须逐字符一致。 */
|
|
677
|
+
function settingsBridgeRoutesFor(ns) {
|
|
678
|
+
return {
|
|
679
|
+
describe: `/api/${ns}/settings/describe`,
|
|
680
|
+
mutate: `/api/${ns}/settings/mutate`
|
|
681
|
+
};
|
|
682
|
+
}
|
|
683
|
+
/**
|
|
684
|
+
* ≤0.1.5 宿主 describe 的 descriptor.value 是「每字段 → {}」骨架(0.1.0/0.1.5
|
|
685
|
+
* 实测:value={enabled:{},…,officialProviders:{}},真值拆在 base(合并默认层)
|
|
686
|
+
* 与 user(用户层))——直接吞 value 会让卡拿到 officialProviders={},
|
|
687
|
+
* `?? []` 不兜底({} 非 nullish)→ ListRow values.join 崩,整族 tab 槽殉葬。
|
|
688
|
+
* 判特征:对象且每个自有值都是空 plain 对象(guard 字段是标量/数组,不会全空)。
|
|
689
|
+
* 命中即以 {...base, ...user} 重组有效值(guard 配置全是顶层标量/数组,浅合并即语义)。
|
|
690
|
+
*/
|
|
691
|
+
function isFieldSkeleton(v) {
|
|
692
|
+
if (v === null || typeof v !== "object" || Array.isArray(v)) return false;
|
|
693
|
+
const values = Object.values(v);
|
|
694
|
+
return values.length > 0 && values.every((x) => x !== null && typeof x === "object" && !Array.isArray(x) && Object.keys(x).length === 0);
|
|
695
|
+
}
|
|
696
|
+
function plainObject(v) {
|
|
697
|
+
return v !== null && typeof v === "object" && !Array.isArray(v) ? { ...v } : {};
|
|
698
|
+
}
|
|
699
|
+
/** descriptor → 卡面可用 plain 值:正常代际用 value;骨架代际用 base+user 浅合并。 */
|
|
700
|
+
function descriptorPlainValue(d) {
|
|
701
|
+
if (!isFieldSkeleton(d.value)) return d.value;
|
|
702
|
+
return {
|
|
703
|
+
...plainObject(d.base),
|
|
704
|
+
...plainObject(d.user)
|
|
705
|
+
};
|
|
692
706
|
}
|
|
707
|
+
/** describe 不可就绪码:宿主 settings 未解析/未注册——值得重试的瞬态。 */
|
|
708
|
+
const RETRYABLE_CODES = /* @__PURE__ */ new Set(["settings-unavailable", "ns-not-registered"]);
|
|
709
|
+
const RETRY_DELAY_MS = 1500;
|
|
710
|
+
const RETRY_MAX = 4;
|
|
711
|
+
var BridgeDocHandle = class {
|
|
712
|
+
constructor(ns) {
|
|
713
|
+
_defineProperty(this, "snapshot", void 0);
|
|
714
|
+
_defineProperty(this, "listeners", /* @__PURE__ */ new Set());
|
|
715
|
+
_defineProperty(this, "revision", void 0);
|
|
716
|
+
_defineProperty(this, "retriesLeft", RETRY_MAX);
|
|
717
|
+
_defineProperty(this, "inflight", void 0);
|
|
718
|
+
_defineProperty(this, "ns", void 0);
|
|
719
|
+
_defineProperty(this, "routes", void 0);
|
|
720
|
+
this.ns = ns;
|
|
721
|
+
this.routes = settingsBridgeRoutesFor(ns);
|
|
722
|
+
this.snapshot = {
|
|
723
|
+
status: "loading",
|
|
724
|
+
value: void 0,
|
|
725
|
+
revision: void 0,
|
|
726
|
+
writable: true,
|
|
727
|
+
base: void 0,
|
|
728
|
+
user: void 0,
|
|
729
|
+
mode: "bridge"
|
|
730
|
+
};
|
|
731
|
+
this.refresh();
|
|
732
|
+
}
|
|
733
|
+
getSnapshot() {
|
|
734
|
+
return this.snapshot;
|
|
735
|
+
}
|
|
736
|
+
subscribe(listener) {
|
|
737
|
+
this.listeners.add(listener);
|
|
738
|
+
return () => {
|
|
739
|
+
this.listeners.delete(listener);
|
|
740
|
+
};
|
|
741
|
+
}
|
|
742
|
+
async set(field, value) {
|
|
743
|
+
return this.mutate([{
|
|
744
|
+
op: "set",
|
|
745
|
+
path: [field],
|
|
746
|
+
value
|
|
747
|
+
}]);
|
|
748
|
+
}
|
|
749
|
+
async unset(field) {
|
|
750
|
+
return this.mutate([{
|
|
751
|
+
op: "unset",
|
|
752
|
+
path: [field]
|
|
753
|
+
}]);
|
|
754
|
+
}
|
|
755
|
+
notify() {
|
|
756
|
+
for (const l of [...this.listeners]) l();
|
|
757
|
+
}
|
|
758
|
+
applyDescriptor(d, writable) {
|
|
759
|
+
this.revision = typeof d.revision === "number" ? d.revision : void 0;
|
|
760
|
+
this.snapshot = {
|
|
761
|
+
status: "ready",
|
|
762
|
+
value: descriptorPlainValue(d),
|
|
763
|
+
revision: this.revision,
|
|
764
|
+
writable,
|
|
765
|
+
base: void 0,
|
|
766
|
+
user: void 0,
|
|
767
|
+
mode: "bridge"
|
|
768
|
+
};
|
|
769
|
+
}
|
|
770
|
+
async request(path, body) {
|
|
771
|
+
const response = await fetch(path, {
|
|
772
|
+
method: "POST",
|
|
773
|
+
headers: { "content-type": "application/json" },
|
|
774
|
+
body: JSON.stringify(body ?? {})
|
|
775
|
+
});
|
|
776
|
+
if (!response.ok) throw new Error(`bridge ${path} HTTP ${response.status}`);
|
|
777
|
+
return await response.json();
|
|
778
|
+
}
|
|
779
|
+
/** describe 拉取;瞬态未就绪码在重试预算内延迟重拉,耗尽才落 unavailable。 */
|
|
780
|
+
refresh() {
|
|
781
|
+
if (this.inflight !== void 0) return this.inflight;
|
|
782
|
+
this.inflight = (async () => {
|
|
783
|
+
try {
|
|
784
|
+
const body = await this.request(this.routes.describe);
|
|
785
|
+
if (body.ok && body.value?.descriptor !== void 0) {
|
|
786
|
+
this.retriesLeft = RETRY_MAX;
|
|
787
|
+
this.applyDescriptor(body.value.descriptor, body.value.writable !== false);
|
|
788
|
+
} else if (body.code !== void 0 && RETRYABLE_CODES.has(body.code) && this.retriesLeft > 0) {
|
|
789
|
+
this.retriesLeft -= 1;
|
|
790
|
+
this.inflight = void 0;
|
|
791
|
+
setTimeout(() => {
|
|
792
|
+
this.refresh();
|
|
793
|
+
}, RETRY_DELAY_MS);
|
|
794
|
+
return;
|
|
795
|
+
} else this.snapshot = {
|
|
796
|
+
...this.snapshot,
|
|
797
|
+
status: "unavailable",
|
|
798
|
+
writable: false
|
|
799
|
+
};
|
|
800
|
+
} catch {
|
|
801
|
+
if (this.retriesLeft > 0) {
|
|
802
|
+
this.retriesLeft -= 1;
|
|
803
|
+
this.inflight = void 0;
|
|
804
|
+
setTimeout(() => {
|
|
805
|
+
this.refresh();
|
|
806
|
+
}, RETRY_DELAY_MS);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
this.snapshot = {
|
|
810
|
+
...this.snapshot,
|
|
811
|
+
status: "unavailable",
|
|
812
|
+
writable: false
|
|
813
|
+
};
|
|
814
|
+
}
|
|
815
|
+
this.inflight = void 0;
|
|
816
|
+
this.notify();
|
|
817
|
+
})();
|
|
818
|
+
return this.inflight;
|
|
819
|
+
}
|
|
820
|
+
async mutate(ops) {
|
|
821
|
+
const body = await this.request(this.routes.mutate, {
|
|
822
|
+
ops,
|
|
823
|
+
expectedRevision: this.revision
|
|
824
|
+
});
|
|
825
|
+
if (body.ok && body.value?.descriptor !== void 0) {
|
|
826
|
+
this.applyDescriptor(body.value.descriptor, this.snapshot.writable);
|
|
827
|
+
this.notify();
|
|
828
|
+
return true;
|
|
829
|
+
}
|
|
830
|
+
if (body.code === "settings-conflict") {
|
|
831
|
+
await this.refresh();
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
throw new Error(`bridge mutate failed: ${body.code ?? "unknown"} ${body.message ?? ""}`);
|
|
835
|
+
}
|
|
836
|
+
};
|
|
693
837
|
//#endregion
|
|
694
838
|
//#region src/client/family-section.tsx
|
|
695
839
|
/**
|
|
@@ -764,8 +908,9 @@ window.__ModuleLoader__.load({
|
|
|
764
908
|
}
|
|
765
909
|
/**
|
|
766
910
|
* 双代持久设置腰(移植 input-traffic registerSettingsFaces):两个 scoped
|
|
767
|
-
* 子注入各自产出同形 scope
|
|
768
|
-
*
|
|
911
|
+
* 子注入各自产出同形 scope 后回调。M4 P0 重构后本函数**只承担数据臂**——
|
|
912
|
+
* 回调仅写 scopeRef 活引用(0.1.7+ 原生优先,mounted 位防双挂),卡面注册
|
|
913
|
+
* 已全部提到 apply 顶层(≤0.1.5 回调永不触发,注册不能关在这里)。
|
|
769
914
|
*/
|
|
770
915
|
function registerSettingsFaces(ctx, onScope) {
|
|
771
916
|
let mounted = false;
|
|
@@ -780,20 +925,32 @@ window.__ModuleLoader__.load({
|
|
|
780
925
|
onScope(sctx.settingsScope.bind({ namespace: "session-guard" }), "settingsScope");
|
|
781
926
|
});
|
|
782
927
|
}
|
|
783
|
-
/**
|
|
784
|
-
|
|
928
|
+
/** scope 活引用(perm-gate client/index.ts L166-257 形态):回调填、卡面现读。 */
|
|
929
|
+
const scopeRef = { scope: void 0 };
|
|
930
|
+
/** 桥轨惰性单例:≤0.1.5(settingsScope 死路 / 0.1.0-0.1.1 无服务)的数据源。 */
|
|
931
|
+
let bridgeScopeSingleton;
|
|
932
|
+
function bridgeScope() {
|
|
933
|
+
if (bridgeScopeSingleton === void 0) bridgeScopeSingleton = new BridgeDocHandle("session-guard");
|
|
934
|
+
return bridgeScopeSingleton;
|
|
935
|
+
}
|
|
936
|
+
/** 卡面现取数据源:原生句柄解到即原生,否则桥兜底(perm-gate 同式)。 */
|
|
937
|
+
function currentScope() {
|
|
938
|
+
return scopeRef.scope ?? bridgeScope();
|
|
939
|
+
}
|
|
940
|
+
/** 两张配置卡(family tab + bundle 详情页)挂载;数据经 currentScope() 现取。 */
|
|
941
|
+
function mountConfigCards(ctx) {
|
|
785
942
|
ctx.slots.inject("dsh-family.tab", () => ctx.slots.register({
|
|
786
943
|
name: "dsh-family.tab",
|
|
787
944
|
id: "session-guard",
|
|
788
945
|
order: 20,
|
|
789
946
|
label: "会话守护门禁",
|
|
790
947
|
locale: "session-guard",
|
|
791
|
-
inject: () => ({ scope })
|
|
948
|
+
inject: () => ({ scope: currentScope() })
|
|
792
949
|
}, SessionGuardCard));
|
|
793
950
|
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
794
951
|
name: "plugins.bundle.config",
|
|
795
952
|
key: "dsh-session-guard",
|
|
796
|
-
inject: () => ({ scope })
|
|
953
|
+
inject: () => ({ scope: currentScope() })
|
|
797
954
|
}, SessionGuardCard));
|
|
798
955
|
}
|
|
799
956
|
function apply(ctx) {
|
|
@@ -809,22 +966,9 @@ window.__ModuleLoader__.load({
|
|
|
809
966
|
order: 40,
|
|
810
967
|
locale: "session-guard"
|
|
811
968
|
}, StatusBadge)));
|
|
969
|
+
safe("config-cards/family-tab", () => mountConfigCards(ctx));
|
|
812
970
|
registerSettingsFaces(ctx, (scope, generation) => {
|
|
813
|
-
|
|
814
|
-
mountConfigCards(ctx, scope);
|
|
815
|
-
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
816
|
-
name: "settings.plugin.item",
|
|
817
|
-
id: "session-guard",
|
|
818
|
-
key: "session-guard",
|
|
819
|
-
order: 60,
|
|
820
|
-
locale: "session-guard",
|
|
821
|
-
inject: () => ({ scope })
|
|
822
|
-
}, SessionGuardCard));
|
|
823
|
-
});
|
|
824
|
-
safe("shell.overlay", () => ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
825
|
-
name: "shell.overlay",
|
|
826
|
-
id: "session-guard-settings"
|
|
827
|
-
}, () => ShellSettingsEntry({ scope }))));
|
|
971
|
+
if (generation === "configForms") scopeRef.scope = scope;
|
|
828
972
|
});
|
|
829
973
|
safe("family-section-election", () => {
|
|
830
974
|
console.log("[dsh-session-guard] election: registering settings.section inject (apply-time)");
|