dsh-session-guard 4.1.11 → 4.1.15

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
@@ -11,6 +11,18 @@
11
11
  - [Changelog на русском](./CHANGELOG.ru.md)
12
12
  - [Changelog en español](./CHANGELOG.es.md)
13
13
 
14
+ ## 4.1.14 — 2026-10-08
15
+
16
+ ### 削除
17
+
18
+ ## 4.1.13 — 2026-10-08
19
+
20
+ ### 修正 — ≤0.1.5 でのブリッジ記述子スケルトン(5セルゲートで発覚)
21
+
22
+ - **≤0.1.5 ホストの describe `value` はフィールドスケルトン**(実測:`{enabled:{},…}`、実値は `base`+`user` 層)。旧世代で `descriptor.value` をそのまま消費すると `officialProviders` が `{}` になり `?? []` を素通りして `ListRow` の `values.join` が throw、`dsh-family.tab` スロット全体が殉葬していた。`descriptorPlainValue()` がスケルトンを検出して `{...base, ...user}` を再構築。
23
+ - **カード字段消毒(多層防御)**:スロットは一つの描画エラーで全族タブが死ぬため、`ListRow` 入力を `toList()` で無毒化、`拦截方式` セレクトは非 `error` 値を `hold` にフォールバック。
24
+ - 5セルファームゲートで検証:0.1.0/0.1.5 はブリッジ経由、0.1.7 は configForms 経由でシード値 `321123` の読み返しを確認。
25
+
14
26
  ## 4.0.0 — 2026-09-29
15
27
 
16
28
  ### Changed
package/CHANGELOG.ko.md CHANGED
@@ -11,6 +11,18 @@
11
11
  - [Changelog на русском](./CHANGELOG.ru.md)
12
12
  - [Changelog en español](./CHANGELOG.es.md)
13
13
 
14
+ ## 4.1.14 — 2026-10-08
15
+
16
+ ### 제거
17
+
18
+ ## 4.1.13 — 2026-10-08
19
+
20
+ ### 수정 — ≤0.1.5 브리지 디스크립터 스켈레톤(5셀 게이트 발견)
21
+
22
+ - **≤0.1.5 호스트의 describe `value`는 필드 스켈레톤**(실측: `{enabled:{},…}`, 실값은 `base`+`user` 레이어). 구세대에서 `descriptor.value`를 그대로 쓰면 `officialProviders`가 `{}`가 되어 `?? []`를 통과하고 `ListRow`의 `values.join`이 throw, `dsh-family.tab` 슬롯 전체가 순직.`descriptorPlainValue()`가 스켈레톤을 감지해 `{...base, ...user}`로 재구성.
23
+ - **카드 필드 소독(다층 방어)**: 슬롯은 렌더 오류 하나로 전체 탭이 죽으므로 `ListRow` 입력을 `toList()`로 무독화, `拦截方式` 셀렉트는 non-`error` 값을 `hold`로 폴백.
24
+ - 5셀 팜 게이트 검증: 0.1.0/0.1.5는 브리지 경유, 0.1.7은 configForms 경유로 시드값 `321123` 재판독 확인.
25
+
14
26
  ## 4.0.0 — 2026-09-29
15
27
 
16
28
  ### Changed
package/CHANGELOG.md CHANGED
@@ -11,6 +11,71 @@ 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.14 — 2026-10-08
15
+
16
+ ### Removed
17
+
18
+ ## 4.1.13 — 2026-10-08
19
+
20
+ ### Fixed — bridge descriptor skeleton on ≤0.1.5 (five-cell gate catch, 2026-10-08)
21
+
22
+ - **Host describe `value` is a field skeleton on ≤0.1.5** (0.1.0/0.1.5 empirical):
23
+ the old host generations serialize `descriptor.value` as `{enabled:{},…,officialProviders:{}}`
24
+ (one empty object per schema field) while the real merged config lives in
25
+ `descriptor.base` (combined defaults) + `descriptor.user` (user layer).
26
+ `BridgeDocHandle` consumed `value` verbatim, so `officialProviders` arrived as
27
+ `{}` — truthy non-array, invisible to `?? []` — and `ListRow`'s
28
+ `values.join` threw, crashing the whole `dsh-family.tab` slot (guard card +
29
+ IT card + TL nav all died together). `descriptorPlainValue()` now detects the
30
+ skeleton (object whose every own value is an empty plain object — impossible
31
+ for guard's scalar/array config) and rebuilds the effective value as
32
+ `{...base, ...user}` (shallow merge is exact for guard's flat config).
33
+ - **Card-field sanitization (defense in depth)**: any card render error kills
34
+ the entire family tab slot on these hosts, so `ListRow` inputs are now
35
+ sanitized at the call sites (`toList()` — non-arrays become `[]`) and the
36
+ `拦截方式` select falls back to `hold` for any non-`error` value. A malformed
37
+ descriptor may degrade one field, never the tab.
38
+ - Verified on the five-cell farm gate: describe replays on 0.1.0/0.1.5 now
39
+ yield plain merged values; the seeded `stepGateTimeoutMs: 321123` reads back
40
+ through the bridge on ≤0.1.5 and natively (configForms) on 0.1.7.
41
+
42
+ ## 4.1.12 — 2026-10-08
43
+
44
+ ### Added — settings webServer bridge (M4/T20-b): dual-track card data plane
45
+
46
+ - **Top-level card registration refactor (prerequisite)**: the three settings
47
+ faces (`dsh-family.tab`, `plugins.bundle.config`, `settings.plugin.item`,
48
+ `shell.overlay`) were registered inside the `onScope` callback of the dual
49
+ scoped-inject waist — on ≤0.1.5 that callback never fires (settingsScope fiber
50
+ never resolves; 0.1.0/0.1.1 lack both services), so the cards never registered
51
+ and a bridge would have had no consumer. All registrations now run at apply
52
+ top level with a `scopeRef` live reference; the callback degrades to a pure
53
+ data arm (perm-gate `client/index.ts` L166-257 shape).
54
+ - **Server bridge** (`src/settings-bridge.js`): `describe`/`mutate` endpoints at
55
+ `/api/session-guard/settings/*` (POST-only, `{ok,value|error}` envelope,
56
+ redacted describe filtered to the plugin ns, ops forwarded to the host
57
+ settings service with `expectedRevision` optimistic concurrency, 256 KB cap).
58
+ Registration is fail-open (error log, never blocks plugin load).
59
+ - **Client bridge handle** (`src/client/settings-bridge.ts`): `BridgeDocHandle`
60
+ implements the same `SettingsScope` face as the native handles
61
+ (getSnapshot/subscribe/set/unset); describe misses retry within a bounded
62
+ budget (settings service resolving later than card mount) before landing on
63
+ `unavailable`. Card faces read `scopeRef.scope ?? bridgeScope()` — native
64
+ first, bridge fallback (perm-gate T13b dual-track). **Generation split in the
65
+ data arm (five-cell empirical, 2026-10-08 night)**: on 0.1.2/0.1.5 the
66
+ `settingsScope` handle is write-alive but read-dead — `set()` persists to
67
+ settings.yaml while `getSnapshot().value` stays `undefined`, so that arm
68
+ deliberately leaves `scopeRef` empty and the card falls to the bridge (bridge
69
+ mutate lands in the same host service; describe heals the read path).
70
+
71
+ ### Corrected — 4.1.11 publication note
72
+
73
+ - The 4.1.11 entry said "none of 4.1.5-4.1.10 was published to npm"; **4.1.5
74
+ was published** (npm registry confirms `4.1.5` in versions, and `latest`
75
+ pointed at it until this release line). 4.1.6-4.1.10 indeed never shipped.
76
+ The earlier claim came from reading the npmmirror mirror instead of the
77
+ authoritative registry (registry-pin discipline, B2+).
78
+
14
79
  ## 4.1.11 — 2026-10-08
15
80
 
16
81
  ### 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-session-guard",
3
- "version": "4.1.11",
3
+ "version": "4.1.14",
4
4
  "main": "./src/index.js",
5
5
  "description": "高峰自动会话门:step 级门控 + 周末模式 + 高峰自动暂停 + 官方源二维判定 + 会话级冻结 + 后端自动重试。",
6
6
  "engines": {
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 ?? "hold",
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 src/client/shell-overlay.tsx
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
- * dsh-session-guard — 无设置页宿主(0.1.0/0.1.1)的兜底配置入口。
663
+ * BridgeDocHandle — 设置卡双轨数据源的桥轨(M4/T20-b)。
621
664
  *
622
- * 壳级 `shell.overlay` 席位渲染:右下角固定齿轮钮,点开固定定位浮层承载
623
- * SessionGuardCard(page 形态,默认展开)。配置存储全在 host 侧(settings
624
- * 服务),浮层与宿主设置页解耦——壳再老也不影响读写。有设置页的宿主上
625
- * 该齿轮与行配置卡/家族 tab 并存,作为全局快捷入口。
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
- function ShellSettingsEntry(props) {
628
- const [open, setOpen] = (0, react.useState)(false);
629
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
630
- style: {
631
- position: "fixed",
632
- right: 16,
633
- bottom: 64,
634
- zIndex: 1e4
635
- },
636
- children: [
637
- open && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
638
- onClick: () => setOpen(false),
639
- style: {
640
- position: "fixed",
641
- top: 0,
642
- left: 0,
643
- right: 0,
644
- bottom: 0,
645
- background: "rgba(0,0,0,.45)",
646
- zIndex: 1e4
647
- }
648
- }),
649
- open && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
650
- style: {
651
- position: "fixed",
652
- top: "6vh",
653
- left: "50%",
654
- transform: "translateX(-50%)",
655
- width: "min(760px, 92vw)",
656
- maxHeight: "84vh",
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 后回调;0.2.0 两代并存时以 configForms 先到为准
768
- * (mounted 位防双挂载,ui-slots 同 id 仲裁兜底)。
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
- /** 两张配置卡(family tab + bundle 详情页)挂载;scope 由双代腰产出。 */
784
- function mountConfigCards(ctx, scope) {
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
- safe(`config-cards/${generation}`, () => {
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)");