dsh-session-guard 4.1.5 → 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 +12 -0
- package/CHANGELOG.ko.md +12 -0
- package/CHANGELOG.md +86 -0
- package/dsh.plugin.json +16 -16
- package/lib/client.js +296 -116
- package/lib/client.js.map +1 -1
- package/package.json +4 -2
- package/src/client/family-section.tsx +26 -6
- package/src/client/index.ts +111 -54
- 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/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)。
|
|
664
|
+
*
|
|
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
|
+
* 成功即用回填描述符刷新快照并通知订阅者(无本地第二份状态——权威值始终在宿主)。
|
|
621
670
|
*
|
|
622
|
-
*
|
|
623
|
-
*
|
|
624
|
-
*
|
|
625
|
-
*
|
|
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
|
-
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);
|
|
692
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
|
+
};
|
|
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
|
/**
|
|
@@ -704,17 +848,24 @@ window.__ModuleLoader__.load({
|
|
|
704
848
|
* 与 thinking-levels 的 FamilySettingsSection 的差别仅在不需要 thinking-
|
|
705
849
|
* levels 自己的卡(宿主不在了,它的卡也不在):渲染循环没有 own-tab 特例。
|
|
706
850
|
*/
|
|
851
|
+
const NO_TABS = [];
|
|
707
852
|
function GuardFamilySection(props) {
|
|
708
|
-
const
|
|
853
|
+
const raw = props.hooks?.tabs;
|
|
854
|
+
const [polled, setPolled] = (0, react.useState)(() => raw?.getSnapshot() ?? NO_TABS);
|
|
855
|
+
(0, react.useEffect)(() => {
|
|
856
|
+
if (props.useTabs !== void 0 || raw === void 0) return;
|
|
857
|
+
return raw.subscribe(() => setPolled(raw.getSnapshot()));
|
|
858
|
+
});
|
|
859
|
+
const tabs = props.useTabs !== void 0 ? props.useTabs((value) => value) : polled;
|
|
709
860
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
710
861
|
style: {
|
|
711
862
|
display: "grid",
|
|
712
863
|
gap: "12px"
|
|
713
864
|
},
|
|
714
|
-
children: tabs.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: props.renderSlot("dsh-family.tab", {}, {
|
|
865
|
+
children: tabs.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: props.renderSlot ? props.renderSlot("dsh-family.tab", {}, {
|
|
715
866
|
only: row.id,
|
|
716
867
|
fallback: null
|
|
717
|
-
}) }, row.id))
|
|
868
|
+
}) : props.renderContributor?.(row.id) ?? null }, row.id))
|
|
718
869
|
});
|
|
719
870
|
}
|
|
720
871
|
//#endregion
|
|
@@ -739,12 +890,8 @@ window.__ModuleLoader__.load({
|
|
|
739
890
|
const FAMILY_SECTION_ID = "dsh-family";
|
|
740
891
|
/** 家族子席位 key(与 thinking-levels 的声明严格一致)。 */
|
|
741
892
|
const FAMILY_CHILD_KEY = "dsh-family.tab";
|
|
742
|
-
/**
|
|
743
|
-
|
|
744
|
-
* 高于 session-steward(1090) → guard 宽限期更短、先尝试接管;并发冲突由
|
|
745
|
-
* ui-slots 的「同 id 同 priority 重复注册抛错」仲裁,后到者放弃即可。
|
|
746
|
-
*/
|
|
747
|
-
const FAMILY_HOST_GRACE_MS = 2e3;
|
|
893
|
+
/** 让位宽限:声明批内 TL 的注册微秒级落账,此延迟后查账本足以分辨「TL 在场」。 */
|
|
894
|
+
const FAMILY_YIELD_MS = 500;
|
|
748
895
|
/**
|
|
749
896
|
* 客户端所需服务:slots(状态徽标)+ locale。持久设置走双代腰
|
|
750
897
|
* (registerSettingsFaces):configForms 为 0.1.7+ 独有,顶层注入会在
|
|
@@ -761,8 +908,9 @@ window.__ModuleLoader__.load({
|
|
|
761
908
|
}
|
|
762
909
|
/**
|
|
763
910
|
* 双代持久设置腰(移植 input-traffic registerSettingsFaces):两个 scoped
|
|
764
|
-
* 子注入各自产出同形 scope
|
|
765
|
-
*
|
|
911
|
+
* 子注入各自产出同形 scope 后回调。M4 P0 重构后本函数**只承担数据臂**——
|
|
912
|
+
* 回调仅写 scopeRef 活引用(0.1.7+ 原生优先,mounted 位防双挂),卡面注册
|
|
913
|
+
* 已全部提到 apply 顶层(≤0.1.5 回调永不触发,注册不能关在这里)。
|
|
766
914
|
*/
|
|
767
915
|
function registerSettingsFaces(ctx, onScope) {
|
|
768
916
|
let mounted = false;
|
|
@@ -777,20 +925,32 @@ window.__ModuleLoader__.load({
|
|
|
777
925
|
onScope(sctx.settingsScope.bind({ namespace: "session-guard" }), "settingsScope");
|
|
778
926
|
});
|
|
779
927
|
}
|
|
780
|
-
/**
|
|
781
|
-
|
|
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) {
|
|
782
942
|
ctx.slots.inject("dsh-family.tab", () => ctx.slots.register({
|
|
783
943
|
name: "dsh-family.tab",
|
|
784
944
|
id: "session-guard",
|
|
785
945
|
order: 20,
|
|
786
946
|
label: "会话守护门禁",
|
|
787
947
|
locale: "session-guard",
|
|
788
|
-
inject: () => ({ scope })
|
|
948
|
+
inject: () => ({ scope: currentScope() })
|
|
789
949
|
}, SessionGuardCard));
|
|
790
950
|
ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
|
|
791
951
|
name: "plugins.bundle.config",
|
|
792
952
|
key: "dsh-session-guard",
|
|
793
|
-
inject: () => ({ scope })
|
|
953
|
+
inject: () => ({ scope: currentScope() })
|
|
794
954
|
}, SessionGuardCard));
|
|
795
955
|
}
|
|
796
956
|
function apply(ctx) {
|
|
@@ -806,45 +966,65 @@ window.__ModuleLoader__.load({
|
|
|
806
966
|
order: 40,
|
|
807
967
|
locale: "session-guard"
|
|
808
968
|
}, StatusBadge)));
|
|
969
|
+
safe("config-cards/family-tab", () => mountConfigCards(ctx));
|
|
809
970
|
registerSettingsFaces(ctx, (scope, generation) => {
|
|
810
|
-
|
|
811
|
-
mountConfigCards(ctx, scope);
|
|
812
|
-
ctx.slots.inject("settings.plugin.item", () => ctx.slots.register({
|
|
813
|
-
name: "settings.plugin.item",
|
|
814
|
-
id: "session-guard",
|
|
815
|
-
key: "session-guard",
|
|
816
|
-
order: 60,
|
|
817
|
-
locale: "session-guard",
|
|
818
|
-
inject: () => ({ scope })
|
|
819
|
-
}, SessionGuardCard));
|
|
820
|
-
});
|
|
821
|
-
safe("shell.overlay", () => ctx.slots.inject("shell.overlay", () => ctx.slots.register({
|
|
822
|
-
name: "shell.overlay",
|
|
823
|
-
id: "session-guard-settings"
|
|
824
|
-
}, () => ShellSettingsEntry({ scope }))));
|
|
971
|
+
if (generation === "configForms") scopeRef.scope = scope;
|
|
825
972
|
});
|
|
826
973
|
safe("family-section-election", () => {
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
974
|
+
console.log("[dsh-session-guard] election: registering settings.section inject (apply-time)");
|
|
975
|
+
ctx.slots.inject("settings.section", () => {
|
|
976
|
+
console.log("[dsh-session-guard] election: factory fired (settings.section declared)");
|
|
977
|
+
setTimeout(() => {
|
|
978
|
+
if (ctx.slots.entries("settings.section").some((e) => e.options.id === FAMILY_SECTION_ID)) {
|
|
979
|
+
console.log("[dsh-session-guard] election: dsh-family already seated — stand down");
|
|
980
|
+
return;
|
|
981
|
+
}
|
|
982
|
+
const base = {
|
|
983
|
+
name: "settings.section",
|
|
984
|
+
id: FAMILY_SECTION_ID,
|
|
985
|
+
order: 40,
|
|
986
|
+
priority: 10,
|
|
987
|
+
label: () => "起子插件设置",
|
|
988
|
+
inject: () => ({
|
|
989
|
+
hooks: makeGuardFamilyTabsHooks(ctx.slots),
|
|
990
|
+
renderContributor
|
|
991
|
+
})
|
|
992
|
+
};
|
|
831
993
|
try {
|
|
832
994
|
ctx.slots.register({
|
|
833
|
-
|
|
834
|
-
id: FAMILY_SECTION_ID,
|
|
835
|
-
order: 40,
|
|
836
|
-
label: () => "起子插件设置",
|
|
837
|
-
inject: () => makeGuardFamilyTabsHooks(ctx.slots),
|
|
995
|
+
...base,
|
|
838
996
|
children: { [FAMILY_CHILD_KEY]: {
|
|
839
997
|
kind: "list",
|
|
840
998
|
scope: "root"
|
|
841
999
|
} }
|
|
842
1000
|
}, GuardFamilySection);
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
1001
|
+
console.log("[dsh-session-guard] family takeover registered (children declared)");
|
|
1002
|
+
} catch (error) {
|
|
1003
|
+
console.log(`[dsh-session-guard] children decl conflict (${error instanceof Error ? error.message : error}); registering without children`);
|
|
1004
|
+
try {
|
|
1005
|
+
ctx.slots.register(base, GuardFamilySection);
|
|
1006
|
+
console.log("[dsh-session-guard] family takeover registered (no children)");
|
|
1007
|
+
} catch (secondary) {
|
|
1008
|
+
console.log(`[dsh-session-guard] family takeover abandoned: ${secondary instanceof Error ? secondary.message : secondary}`);
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
1011
|
+
}, FAMILY_YIELD_MS);
|
|
1012
|
+
});
|
|
846
1013
|
});
|
|
847
1014
|
}
|
|
1015
|
+
/** 直接挂载面(≤0.1.5 壳无 renderSlot 的降级):从账本原始条目现挂激活卡。
|
|
1016
|
+
* entries 每代都带 component + inject(ui-slots SlotCore.entries 原始记录);
|
|
1017
|
+
* 任何失败(条目消失/抛错 inject)降级为 null,不炸接管节。 */
|
|
1018
|
+
function renderContributor(id) {
|
|
1019
|
+
try {
|
|
1020
|
+
const entry = ctx.slots.entries(FAMILY_CHILD_KEY).find((e) => e.options.id === id);
|
|
1021
|
+
if (entry === void 0 || typeof entry.component !== "function") return null;
|
|
1022
|
+
const injected = typeof entry.inject === "function" ? entry.inject() : void 0;
|
|
1023
|
+
return (0, react.createElement)(entry.component, injected ?? {});
|
|
1024
|
+
} catch {
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
848
1028
|
/** 接管节的账本投影 hooks(与 thinking-levels 的 FamilySectionInjected 同形)。 */
|
|
849
1029
|
function makeGuardFamilyTabsHooks(slots) {
|
|
850
1030
|
let version = -1;
|