dsh-session-guard 4.0.2 → 4.1.11

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/cordis.patch.yml CHANGED
@@ -1,10 +1,10 @@
1
- # dsh-session-guard bundle patch:一行装配进 profile。
2
- #
3
- # host 半(src/index.js)注册 sessionGuard 服务(冗余端口,供 input-traffic 适配)、
4
- # 高峰自动会话门(taskControl.pause/resume,缺失时回退锁等待队列)、
5
- # /session-guard/* 路由与 设置→插件→session-guard 子板块(简单开关)。
6
- # 浏览器半(src/client/index.js,经 package.json dsh.client 声明)提供
7
- # 高峰状态徽标 + 无 input-traffic 时的回退冻结按钮。
8
- - insert:
9
- - id: session-guard
10
- name: dsh-session-guard
1
+ # dsh-session-guard bundle patch:一行装配进 profile。
2
+ #
3
+ # host 半(src/index.js)注册 sessionGuard 服务(冗余端口,供 input-traffic 适配)、
4
+ # 高峰自动会话门(taskControl.pause/resume,缺失时回退锁等待队列)、
5
+ # /session-guard/* 路由与 设置→插件→session-guard 子板块(简单开关)。
6
+ # 浏览器半(src/client/index.js,经 package.json dsh.client 声明)提供
7
+ # 高峰状态徽标 + 无 input-traffic 时的回退冻结按钮。
8
+ - insert:
9
+ - id: session-guard
10
+ name: dsh-session-guard
package/dsh.plugin.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "id": "dsh-external/dsh-session-guard",
3
- "version": "4.0.0",
3
+ "version": "4.1.11",
4
4
  "main": "./src/index.js",
5
5
  "description": "高峰自动会话门:step 级门控 + 周末模式 + 高峰自动暂停 + 官方源二维判定 + 会话级冻结 + 后端自动重试。",
6
6
  "engines": {
package/lib/client.js CHANGED
@@ -66,11 +66,17 @@ window.__ModuleLoader__.load({
66
66
  * 轮询 host 的 /session-guard/status(全局当前阶段),显示 高峰/谷时/周末;
67
67
  * 高峰期按二维判定区分「只拦官方」与「全部暂停」(文案见 badge-text.ts)。
68
68
  * 仅展示,不做任何队列/会话动作;冻结按钮由 input-traffic 经桥接管(D6/D8)。
69
+ *
70
+ * 自适应退避(universal-rc):视口宽度不足(< PEAK_BADGE_MIN_VIEWPORT,窄窗/
71
+ * 分屏)或新空会话(sessionId 未落盘为 undefined)→ 整体退避隐藏。
69
72
  */
70
73
  const POLL_MS$1 = 15e3;
74
+ /** 视口宽度退避阈值:低于此值隐藏徽标(窄窗/分屏下输入区放不下)。 */
75
+ const PEAK_BADGE_MIN_VIEWPORT = 960;
71
76
  /** 状态徽标:轮询全局阶段,显示 高峰/谷时/周末(enabled 关闭或请求失败时静默隐藏)。 */
72
77
  function StatusBadge({ sessionId }) {
73
78
  const [status, setStatus] = (0, react.useState)(null);
79
+ const [viewportNarrow, setViewportNarrow] = (0, react.useState)(() => typeof window !== "undefined" && window.innerWidth < PEAK_BADGE_MIN_VIEWPORT);
74
80
  injectClientCss();
75
81
  (0, react.useEffect)(() => {
76
82
  if (!sessionId) return;
@@ -88,6 +94,12 @@ window.__ModuleLoader__.load({
88
94
  clearInterval(timer);
89
95
  };
90
96
  }, [sessionId]);
97
+ (0, react.useEffect)(() => {
98
+ const onResize = () => setViewportNarrow(window.innerWidth < PEAK_BADGE_MIN_VIEWPORT);
99
+ window.addEventListener("resize", onResize);
100
+ return () => window.removeEventListener("resize", onResize);
101
+ }, []);
102
+ if (viewportNarrow) return null;
91
103
  if (!status || !status.enabled) return null;
92
104
  const cls = status.phase === "peak" ? "sg-peak" : status.phase === "weekend" ? "sg-weekend" : "sg-off";
93
105
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
@@ -603,6 +615,82 @@ window.__ModuleLoader__.load({
603
615
  });
604
616
  }
605
617
  //#endregion
618
+ //#region src/client/shell-overlay.tsx
619
+ /**
620
+ * dsh-session-guard — 无设置页宿主(0.1.0/0.1.1)的兜底配置入口。
621
+ *
622
+ * 壳级 `shell.overlay` 席位渲染:右下角固定齿轮钮,点开固定定位浮层承载
623
+ * SessionGuardCard(page 形态,默认展开)。配置存储全在 host 侧(settings
624
+ * 服务),浮层与宿主设置页解耦——壳再老也不影响读写。有设置页的宿主上
625
+ * 该齿轮与行配置卡/家族 tab 并存,作为全局快捷入口。
626
+ */
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
+ });
692
+ }
693
+ //#endregion
606
694
  //#region src/client/family-section.tsx
607
695
  /**
608
696
  * dsh-session-guard — 家族节接管组件(fallback host section)。
@@ -616,17 +704,24 @@ window.__ModuleLoader__.load({
616
704
  * 与 thinking-levels 的 FamilySettingsSection 的差别仅在不需要 thinking-
617
705
  * levels 自己的卡(宿主不在了,它的卡也不在):渲染循环没有 own-tab 特例。
618
706
  */
707
+ const NO_TABS = [];
619
708
  function GuardFamilySection(props) {
620
- const tabs = (0, react.useSyncExternalStore)(props.hooks.tabs.subscribe, props.hooks.tabs.getSnapshot);
709
+ const raw = props.hooks?.tabs;
710
+ const [polled, setPolled] = (0, react.useState)(() => raw?.getSnapshot() ?? NO_TABS);
711
+ (0, react.useEffect)(() => {
712
+ if (props.useTabs !== void 0 || raw === void 0) return;
713
+ return raw.subscribe(() => setPolled(raw.getSnapshot()));
714
+ });
715
+ const tabs = props.useTabs !== void 0 ? props.useTabs((value) => value) : polled;
621
716
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
622
717
  style: {
623
718
  display: "grid",
624
719
  gap: "12px"
625
720
  },
626
- children: tabs.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: props.renderSlot("dsh-family.tab", {}, {
721
+ children: tabs.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", { children: props.renderSlot ? props.renderSlot("dsh-family.tab", {}, {
627
722
  only: row.id,
628
723
  fallback: null
629
- }) }, row.id))
724
+ }) : props.renderContributor?.(row.id) ?? null }, row.id))
630
725
  });
631
726
  }
632
727
  //#endregion
@@ -641,6 +736,9 @@ window.__ModuleLoader__.load({
641
736
  * 桥接 host 会话门;本插件客户端不注册任何按钮,避免与 input-traffic 冲突。
642
737
  * - 0.1.7:旧的插件设置卡已随席位删除 —— 设置表单由 host
643
738
  * 侧 Config 的 `.volatile()` 字段自动生成。
739
+ * - 跨版本(universal-rc):持久设置经 configForms(0.1.7+)/settingsScope(≤0.1.5)
740
+ * 双代腰产出同形 scope;无设置页宿主(0.1.0/0.1.1)经 shell.overlay 浮窗
741
+ * 齿轮提供配置入口(配置存储全在 host 侧,浮层与宿主设置页解耦)。
644
742
  *
645
743
  * 构建:tsdown → lib/client.js(__ModuleLoader__.load 注册,与 input-traffic 同构)。
646
744
  */
@@ -648,60 +746,140 @@ window.__ModuleLoader__.load({
648
746
  const FAMILY_SECTION_ID = "dsh-family";
649
747
  /** 家族子席位 key(与 thinking-levels 的声明严格一致)。 */
650
748
  const FAMILY_CHILD_KEY = "dsh-family.tab";
749
+ /** 让位宽限:声明批内 TL 的注册微秒级落账,此延迟后查账本足以分辨「TL 在场」。 */
750
+ const FAMILY_YIELD_MS = 500;
651
751
  /**
652
- * 接管宽限期:等宿主(以及排名更前的贡献方)先落座。下载量排名 guard(1522)
653
- * 高于 session-steward(1090) → guard 宽限期更短、先尝试接管;并发冲突由
654
- * ui-slots 的「同 id 同 priority 重复注册抛错」仲裁,后到者放弃即可。
752
+ * 客户端所需服务:slots(状态徽标)+ locale。持久设置走双代腰
753
+ * (registerSettingsFaces):configForms 为 0.1.7+ 独有,顶层注入会在
754
+ * 0.1.0/0.1.1(无此服务)上让整段 apply 静默死。
655
755
  */
656
- const FAMILY_HOST_GRACE_MS = 2e3;
657
- /** 客户端所需服务:slots(状态徽标)+ locale + configForms(共用 tab 设置卡)。 */
658
- const inject = [
659
- "slots",
660
- "locale",
661
- "configForms"
662
- ];
663
- function apply(ctx) {
664
- ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
665
- name: "conversation.input.right",
666
- id: "session-guard-pause",
667
- order: 20,
668
- locale: "session-guard"
669
- }, PauseButton));
670
- ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
671
- name: "conversation.input.right",
672
- id: "session-guard-status",
673
- order: 40,
674
- locale: "session-guard"
675
- }, StatusBadge));
756
+ const inject = ["slots", "locale"];
757
+ /** 单面注册护栏:某条宿主线上未知槽名/缺服务的同步异常不得带走其它面。 */
758
+ function safe(tag, fn) {
759
+ try {
760
+ fn();
761
+ } catch (error) {
762
+ console.warn(`dsh-session-guard: client surface '${tag}' failed:`, error);
763
+ }
764
+ }
765
+ /**
766
+ * 双代持久设置腰(移植 input-traffic registerSettingsFaces):两个 scoped
767
+ * 子注入各自产出同形 scope 后回调;0.2.0 两代并存时以 configForms 先到为准
768
+ * (mounted 位防双挂载,ui-slots 同 id 仲裁兜底)。
769
+ */
770
+ function registerSettingsFaces(ctx, onScope) {
771
+ let mounted = false;
772
+ ctx.inject(["configForms"], (sctx) => {
773
+ if (mounted) return;
774
+ mounted = true;
775
+ onScope(sctx.configForms.get("session-guard"), "configForms");
776
+ });
777
+ ctx.inject(["settingsScope"], (sctx) => {
778
+ if (mounted) return;
779
+ mounted = true;
780
+ onScope(sctx.settingsScope.bind({ namespace: "session-guard" }), "settingsScope");
781
+ });
782
+ }
783
+ /** 两张配置卡(family tab + bundle 详情页)挂载;scope 由双代腰产出。 */
784
+ function mountConfigCards(ctx, scope) {
676
785
  ctx.slots.inject("dsh-family.tab", () => ctx.slots.register({
677
786
  name: "dsh-family.tab",
678
787
  id: "session-guard",
679
788
  order: 20,
680
789
  label: "会话守护门禁",
681
790
  locale: "session-guard",
682
- inject: () => ({ scope: ctx.configForms.get("session-guard") })
791
+ inject: () => ({ scope })
683
792
  }, SessionGuardCard));
684
793
  ctx.slots.inject("plugins.bundle.config", () => ctx.slots.register({
685
794
  name: "plugins.bundle.config",
686
795
  key: "dsh-session-guard",
687
- inject: () => ({ scope: ctx.configForms.get("session-guard") })
796
+ inject: () => ({ scope })
688
797
  }, SessionGuardCard));
689
- setTimeout(() => {
690
- if (ctx.slots.entries("settings.section").some((e) => e.options.id === FAMILY_SECTION_ID)) return;
691
- try {
692
- ctx.slots.register({
693
- name: "settings.section",
694
- id: FAMILY_SECTION_ID,
695
- order: 40,
696
- label: () => "起子插件设置",
697
- inject: () => makeGuardFamilyTabsHooks(ctx.slots),
698
- children: { [FAMILY_CHILD_KEY]: {
699
- kind: "list",
700
- scope: "root"
701
- } }
702
- }, GuardFamilySection);
703
- } catch {}
704
- }, FAMILY_HOST_GRACE_MS);
798
+ }
799
+ function apply(ctx) {
800
+ safe("conversation.input.right/pause", () => ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
801
+ name: "conversation.input.right",
802
+ id: "session-guard-pause",
803
+ order: 20,
804
+ locale: "session-guard"
805
+ }, PauseButton)));
806
+ safe("conversation.input.right/status", () => ctx.slots.inject("conversation.input.right", () => ctx.slots.register({
807
+ name: "conversation.input.right",
808
+ id: "session-guard-status",
809
+ order: 40,
810
+ locale: "session-guard"
811
+ }, StatusBadge)));
812
+ 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 }))));
828
+ });
829
+ safe("family-section-election", () => {
830
+ console.log("[dsh-session-guard] election: registering settings.section inject (apply-time)");
831
+ ctx.slots.inject("settings.section", () => {
832
+ console.log("[dsh-session-guard] election: factory fired (settings.section declared)");
833
+ setTimeout(() => {
834
+ if (ctx.slots.entries("settings.section").some((e) => e.options.id === FAMILY_SECTION_ID)) {
835
+ console.log("[dsh-session-guard] election: dsh-family already seated — stand down");
836
+ return;
837
+ }
838
+ const base = {
839
+ name: "settings.section",
840
+ id: FAMILY_SECTION_ID,
841
+ order: 40,
842
+ priority: 10,
843
+ label: () => "起子插件设置",
844
+ inject: () => ({
845
+ hooks: makeGuardFamilyTabsHooks(ctx.slots),
846
+ renderContributor
847
+ })
848
+ };
849
+ try {
850
+ ctx.slots.register({
851
+ ...base,
852
+ children: { [FAMILY_CHILD_KEY]: {
853
+ kind: "list",
854
+ scope: "root"
855
+ } }
856
+ }, GuardFamilySection);
857
+ console.log("[dsh-session-guard] family takeover registered (children declared)");
858
+ } catch (error) {
859
+ console.log(`[dsh-session-guard] children decl conflict (${error instanceof Error ? error.message : error}); registering without children`);
860
+ try {
861
+ ctx.slots.register(base, GuardFamilySection);
862
+ console.log("[dsh-session-guard] family takeover registered (no children)");
863
+ } catch (secondary) {
864
+ console.log(`[dsh-session-guard] family takeover abandoned: ${secondary instanceof Error ? secondary.message : secondary}`);
865
+ }
866
+ }
867
+ }, FAMILY_YIELD_MS);
868
+ });
869
+ });
870
+ }
871
+ /** 直接挂载面(≤0.1.5 壳无 renderSlot 的降级):从账本原始条目现挂激活卡。
872
+ * entries 每代都带 component + inject(ui-slots SlotCore.entries 原始记录);
873
+ * 任何失败(条目消失/抛错 inject)降级为 null,不炸接管节。 */
874
+ function renderContributor(id) {
875
+ try {
876
+ const entry = ctx.slots.entries(FAMILY_CHILD_KEY).find((e) => e.options.id === id);
877
+ if (entry === void 0 || typeof entry.component !== "function") return null;
878
+ const injected = typeof entry.inject === "function" ? entry.inject() : void 0;
879
+ return (0, react.createElement)(entry.component, injected ?? {});
880
+ } catch {
881
+ return null;
882
+ }
705
883
  }
706
884
  /** 接管节的账本投影 hooks(与 thinking-levels 的 FamilySectionInjected 同形)。 */
707
885
  function makeGuardFamilyTabsHooks(slots) {