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/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)。
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
- * 壳级 `shell.overlay` 席位渲染:右下角固定齿轮钮,点开固定定位浮层承载
623
- * SessionGuardCard(page 形态,默认展开)。配置存储全在 host 侧(settings
624
- * 服务),浮层与宿主设置页解耦——壳再老也不影响读写。有设置页的宿主上
625
- * 该齿轮与行配置卡/家族 tab 并存,作为全局快捷入口。
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);
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 tabs = (0, react.useSyncExternalStore)(props.hooks.tabs.subscribe, props.hooks.tabs.getSnapshot);
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
- * 接管宽限期:等宿主(以及排名更前的贡献方)先落座。下载量排名 guard(1522)
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 后回调;0.2.0 两代并存时以 configForms 先到为准
765
- * (mounted 位防双挂载,ui-slots 同 id 仲裁兜底)。
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
- /** 两张配置卡(family tab + bundle 详情页)挂载;scope 由双代腰产出。 */
781
- 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) {
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
- safe(`config-cards/${generation}`, () => {
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
- setTimeout(() => {
828
- if (ctx.slots.entries("settings.section").some((e) => e.options.id === FAMILY_SECTION_ID)) return;
829
- ctx.inject(["configForms"], () => {
830
- if (ctx.slots.entries("settings.section").some((e) => e.options.id === FAMILY_SECTION_ID)) return;
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
- name: "settings.section",
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
- } catch {}
844
- });
845
- }, FAMILY_HOST_GRACE_MS);
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;