dsh-github-flow 0.1.8 → 0.2.1

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.
Files changed (69) hide show
  1. package/README.en.md +162 -58
  2. package/README.md +167 -60
  3. package/docs/UPGRADE-NOTES.md +93 -0
  4. package/docs/architecture.md +47 -17
  5. package/docs/images/1.png +0 -0
  6. package/docs/images/2.png +0 -0
  7. package/docs/images/3.png +0 -0
  8. package/docs/images/4.png +0 -0
  9. package/docs/npm-publish-guide.md +26 -3
  10. package/lib/api/route-helpers.d.ts +47 -0
  11. package/lib/api/route-helpers.js +134 -0
  12. package/lib/api/routes-actions.d.ts +23 -0
  13. package/lib/api/routes-actions.js +239 -0
  14. package/lib/api/routes-lifecycle.d.ts +9 -0
  15. package/lib/api/routes-lifecycle.js +126 -0
  16. package/lib/api/routes-overview.d.ts +11 -0
  17. package/lib/api/routes-overview.js +227 -0
  18. package/lib/api/routes-proxy.d.ts +18 -0
  19. package/lib/api/routes-proxy.js +63 -0
  20. package/lib/api/routes.d.ts +16 -6
  21. package/lib/api/routes.js +14 -504
  22. package/lib/client.js +785 -90
  23. package/lib/commands/gh.js +11 -2
  24. package/lib/executor.d.ts +17 -0
  25. package/lib/executor.js +146 -8
  26. package/lib/main.d.ts +1 -0
  27. package/lib/main.js +49 -9
  28. package/lib/proxy.d.ts +87 -0
  29. package/lib/proxy.js +275 -0
  30. package/lib/renderers/markdown.d.ts +29 -0
  31. package/lib/renderers/markdown.js +520 -0
  32. package/lib/tools/api.d.ts +4 -0
  33. package/lib/tools/api.js +14 -4
  34. package/lib/tools/help.d.ts +31 -0
  35. package/lib/tools/help.js +224 -0
  36. package/lib/tools/image-utils.d.ts +79 -0
  37. package/lib/tools/image-utils.js +247 -0
  38. package/lib/tools/issue.d.ts +40 -4
  39. package/lib/tools/issue.js +167 -19
  40. package/lib/tools/pr.d.ts +29 -4
  41. package/lib/tools/pr.js +144 -18
  42. package/lib/tools/release.d.ts +89 -0
  43. package/lib/tools/release.js +219 -0
  44. package/lib/tools/repo.d.ts +24 -4
  45. package/lib/tools/repo.js +114 -14
  46. package/lib/tools/run.d.ts +22 -1
  47. package/lib/tools/run.js +74 -14
  48. package/lib/tools/secret.d.ts +60 -0
  49. package/lib/tools/secret.js +175 -0
  50. package/lib/tools/skill.d.ts +63 -0
  51. package/lib/tools/skill.js +146 -0
  52. package/lib/types.d.ts +24 -1
  53. package/lib/update-settings.d.ts +18 -0
  54. package/lib/update-settings.js +60 -0
  55. package/lib/updater.d.ts +40 -1
  56. package/lib/updater.js +126 -9
  57. package/lib/upgrade-manager.d.ts +74 -1
  58. package/lib/upgrade-manager.js +356 -46
  59. package/lib/version.d.ts +1 -1
  60. package/lib/version.js +1 -1
  61. package/package.json +3 -2
  62. package/lib/client/icons.d.ts +0 -13
  63. package/lib/client/icons.js +0 -41
  64. package/lib/client/index.d.ts +0 -3
  65. package/lib/client/index.js +0 -36
  66. package/lib/client/panel.d.ts +0 -1
  67. package/lib/client/panel.js +0 -102
  68. package/lib/client/style.d.ts +0 -1
  69. package/lib/client/style.js +0 -555
package/lib/client.js CHANGED
@@ -5,6 +5,10 @@ window.__ModuleLoader__.load({
5
5
  var exports = module.exports;
6
6
  Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
7
7
  var React = require("react");
8
+ // 面板样式(内联维护):DSH 客户端加载器以经典脚本语义执行本产物(build.mjs 剥除
9
+ // export 声明即为佐证),模块级 import 在该语境是 SyntaxError。曾尝试把 CSS 拆为
10
+ // 独立模块经 import 引入,导致 DSH 重启时客户端加载崩溃并触发插件批量卸载——
11
+ // 已回滚。样式与组件的分离改由「同文件内分区注释」维持,不再跨文件。
8
12
  var GITHUB_PANEL_CSS = `
9
13
  /* ==========================================================================
10
14
  DSH GitHub Flow - 主题自适应样式系统 (Light / Dark 动态联动)
@@ -286,6 +290,40 @@ body[data-ds-dark-theme] .dsh-badge-purple {
286
290
  border-color: rgba(163, 113, 247, 0.3);
287
291
  }
288
292
 
293
+ /* 插件身份徽标:标识界面由第三方插件 dsh-github-flow 提供(非 DSH/GitHub 官方)。
294
+ * 为什么需要:界面以 GitHub 官方 Octocat 做功能图标,新用户易误认为官方功能;
295
+ * 徽标点击跳插件仓库,与设置「关于」页形成身份闭环。 */
296
+ .dsh-github-plugin-badge {
297
+ display: inline-flex;
298
+ align-items: center;
299
+ font-size: 10px;
300
+ font-weight: 500;
301
+ padding: 1px 7px;
302
+ border-radius: 10px;
303
+ line-height: 14px;
304
+ vertical-align: middle;
305
+ margin-left: 8px;
306
+ text-decoration: none !important;
307
+ background: rgba(125, 133, 144, 0.12);
308
+ color: #656d76;
309
+ border: 1px solid rgba(125, 133, 144, 0.25);
310
+ cursor: pointer;
311
+ transition: all 0.15s ease;
312
+ }
313
+ .dsh-github-plugin-badge:hover {
314
+ background: rgba(125, 133, 144, 0.2);
315
+ color: var(--dsw-alias-label-primary, #1f2328);
316
+ }
317
+ body[data-ds-dark-theme] .dsh-github-plugin-badge {
318
+ background: rgba(139, 148, 158, 0.18);
319
+ color: #8b949e;
320
+ border-color: rgba(139, 148, 158, 0.3);
321
+ }
322
+ body[data-ds-dark-theme] .dsh-github-plugin-badge:hover {
323
+ background: rgba(139, 148, 158, 0.28);
324
+ color: #f0f6fc;
325
+ }
326
+
289
327
  /* 7.5. 分类筛选药丸栏 */
290
328
  .dsh-github-pills {
291
329
  display: flex;
@@ -805,7 +843,7 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
805
843
  line-height: 1.4;
806
844
  }
807
845
 
808
- /* 16. 数据同步设置弹窗 (Sync Settings Modal) */
846
+ /* 16. 设置弹窗 (Settings Modal):左侧分区导航 + 右侧内容区 */
809
847
  .dsh-github-modal-overlay {
810
848
  position: fixed;
811
849
  top: 0;
@@ -820,14 +858,18 @@ body[data-ds-dark-theme] .dsh-github-steps-list {
820
858
  backdrop-filter: blur(2px);
821
859
  }
822
860
  .dsh-github-modal-dialog {
823
- width: 90%;
824
- max-width: 480px;
861
+ width: 92%;
862
+ max-width: 640px;
863
+ max-height: 86vh;
864
+ display: flex;
865
+ flex-direction: column;
825
866
  background: var(--dsw-alias-bg-base, #ffffff);
826
867
  border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
827
868
  border-radius: 12px;
828
869
  box-shadow: 0 16px 32px rgba(0, 0, 0, 0.18);
829
- padding: 24px;
870
+ padding: 0;
830
871
  box-sizing: border-box;
872
+ overflow: hidden;
831
873
  color: var(--dsw-alias-label-primary, #1f2328);
832
874
  font-family: var(--dsw-font-family, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif);
833
875
  }
@@ -836,6 +878,105 @@ body[data-ds-dark-theme] .dsh-github-modal-dialog {
836
878
  border-color: var(--dsw-alias-border-l1, #30363d);
837
879
  box-shadow: 0 16px 36px rgba(0, 0, 0, 0.45);
838
880
  }
881
+ /* 弹窗头部(标题 + 关闭) */
882
+ .dsh-github-modal-header {
883
+ display: flex;
884
+ justify-content: space-between;
885
+ align-items: center;
886
+ padding: 18px 22px 14px;
887
+ border-bottom: 1px solid var(--dsw-alias-border-l2, #d0d7de);
888
+ flex-shrink: 0;
889
+ }
890
+ body[data-ds-dark-theme] .dsh-github-modal-header {
891
+ border-bottom-color: var(--dsw-alias-border-l1, #30363d);
892
+ }
893
+ /* 分区导航 + 内容 双栏骨架:窄屏(<560px)自动折叠为顶部横向页签 */
894
+ .dsh-github-modal-body {
895
+ display: flex;
896
+ min-height: 0;
897
+ flex: 1;
898
+ }
899
+ .dsh-github-settings-nav {
900
+ flex-shrink: 0;
901
+ width: 148px;
902
+ padding: 14px 10px;
903
+ border-right: 1px solid var(--dsw-alias-border-l2, #d0d7de);
904
+ display: flex;
905
+ flex-direction: column;
906
+ gap: 4px;
907
+ box-sizing: border-box;
908
+ }
909
+ body[data-ds-dark-theme] .dsh-github-settings-nav {
910
+ border-right-color: var(--dsw-alias-border-l1, #30363d);
911
+ }
912
+ .dsh-github-settings-nav-item {
913
+ display: flex;
914
+ align-items: center;
915
+ gap: 8px;
916
+ padding: 8px 10px;
917
+ border-radius: 6px;
918
+ border: none;
919
+ background: none;
920
+ cursor: pointer;
921
+ font-size: 13px;
922
+ text-align: left;
923
+ color: var(--dsw-alias-label-secondary, #656d76);
924
+ transition: background 0.12s ease, color 0.12s ease;
925
+ }
926
+ .dsh-github-settings-nav-item:hover {
927
+ background: var(--dsw-alias-bg-layer-1, #f6f8fa);
928
+ color: var(--dsw-alias-label-primary, #1f2328);
929
+ }
930
+ .dsh-github-settings-nav-item.active {
931
+ background: rgba(9, 105, 218, 0.1);
932
+ color: var(--dsw-alias-link, #0969da);
933
+ font-weight: 600;
934
+ }
935
+ body[data-ds-dark-theme] .dsh-github-settings-nav-item:hover {
936
+ background: rgba(240, 246, 252, 0.06);
937
+ color: var(--dsw-alias-label-primary, #f0f6fc);
938
+ }
939
+ body[data-ds-dark-theme] .dsh-github-settings-nav-item.active {
940
+ background: rgba(88, 166, 255, 0.15);
941
+ color: #58a6ff;
942
+ }
943
+ .dsh-github-settings-content {
944
+ flex: 1;
945
+ min-width: 0;
946
+ padding: 18px 22px 20px;
947
+ overflow-y: auto;
948
+ box-sizing: border-box;
949
+ }
950
+ @media (max-width: 560px) {
951
+ .dsh-github-modal-body {
952
+ flex-direction: column;
953
+ }
954
+ .dsh-github-settings-nav {
955
+ width: 100%;
956
+ flex-direction: row;
957
+ border-right: none;
958
+ border-bottom: 1px solid var(--dsw-alias-border-l2, #d0d7e0);
959
+ overflow-x: auto;
960
+ }
961
+ body[data-ds-dark-theme] .dsh-github-settings-nav {
962
+ border-bottom-color: var(--dsw-alias-border-l1, #30363d);
963
+ }
964
+ .dsh-github-settings-nav-item {
965
+ white-space: nowrap;
966
+ }
967
+ }
968
+ /* 弹窗底部操作条 */
969
+ .dsh-github-modal-footer {
970
+ display: flex;
971
+ justify-content: flex-end;
972
+ gap: 8px;
973
+ padding: 14px 22px;
974
+ border-top: 1px solid var(--dsw-alias-border-l2, #d0d7de);
975
+ flex-shrink: 0;
976
+ }
977
+ body[data-ds-dark-theme] .dsh-github-modal-footer {
978
+ border-top-color: var(--dsw-alias-border-l1, #30363d);
979
+ }
839
980
  .dsh-github-radio-card {
840
981
  display: flex;
841
982
  align-items: flex-start;
@@ -903,6 +1044,37 @@ body[data-ds-dark-theme] .dsh-github-select option {
903
1044
  color: #f0f6fc;
904
1045
  }
905
1046
 
1047
+ /* 代理设置弹窗的文本输入框(浅色/深色双模式,设计变量对齐 select 保持视觉一致) */
1048
+ .dsh-github-input {
1049
+ padding: 7px 12px;
1050
+ border-radius: 6px;
1051
+ border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
1052
+ background: var(--dsw-alias-bg-base, #ffffff);
1053
+ color: var(--dsw-alias-label-primary, #1f2328);
1054
+ font-size: 13px;
1055
+ outline: none;
1056
+ box-sizing: border-box;
1057
+ color-scheme: light;
1058
+ transition: border-color 0.15s ease, box-shadow 0.15s ease;
1059
+ }
1060
+ .dsh-github-input:focus {
1061
+ border-color: var(--dsw-alias-link, #0969da);
1062
+ box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.15);
1063
+ }
1064
+ .dsh-github-input::placeholder {
1065
+ color: var(--dsw-alias-label-tertiary, #8c959f);
1066
+ }
1067
+ body[data-ds-dark-theme] .dsh-github-input {
1068
+ border-color: var(--dsw-alias-border-l1, #30363d);
1069
+ background: var(--dsw-alias-bg-layer-2, #1e1e22);
1070
+ color: var(--dsw-alias-label-primary, #f0f6fc);
1071
+ color-scheme: dark;
1072
+ }
1073
+ body[data-ds-dark-theme] .dsh-github-input:focus {
1074
+ border-color: #58a6ff;
1075
+ box-shadow: 0 0 0 3px rgba(88, 166, 255, 0.2);
1076
+ }
1077
+
906
1078
  /* 缓存信息展示框 */
907
1079
  .dsh-github-cache-info-box {
908
1080
  padding: 10px 12px;
@@ -920,6 +1092,45 @@ body[data-ds-dark-theme] .dsh-github-cache-info-box {
920
1092
  color: var(--dsw-alias-label-secondary, #8c8c8c);
921
1093
  }
922
1094
 
1095
+ /* 关于页求 Star 卡片:与 cache-info-box 同款双主题底色(浅色纸面/暗色深底) */
1096
+ .dsh-github-about-star-box {
1097
+ padding: 12px 14px;
1098
+ border-radius: 8px;
1099
+ background: var(--dsw-alias-bg-layer-1, #f6f8fa);
1100
+ border: 1px solid var(--dsw-alias-border-l2, #d0d7de);
1101
+ }
1102
+ body[data-ds-dark-theme] .dsh-github-about-star-box {
1103
+ background: var(--dsw-alias-bg-base, #161b22);
1104
+ border-color: var(--dsw-alias-border-l1, #30363d);
1105
+ }
1106
+
1107
+ /* 主操作按钮(链接色底):链接色底 + 白字,暗色用亮蓝(对齐 radio/链接的暗色强调色 58a6ff) */
1108
+ .dsh-github-primary-btn {
1109
+ padding: 5px 14px;
1110
+ font-weight: 600;
1111
+ white-space: nowrap;
1112
+ background: var(--dsw-alias-link, #0969da);
1113
+ border-color: var(--dsw-alias-link, #0969da);
1114
+ color: #ffffff;
1115
+ }
1116
+ .dsh-github-primary-btn:hover {
1117
+ background: var(--dsw-alias-link, #0969da);
1118
+ border-color: var(--dsw-alias-link, #0969da);
1119
+ color: #ffffff;
1120
+ opacity: 0.88;
1121
+ }
1122
+ body[data-ds-dark-theme] .dsh-github-primary-btn {
1123
+ background: #58a6ff;
1124
+ border-color: #58a6ff;
1125
+ color: #0d1117;
1126
+ }
1127
+ body[data-ds-dark-theme] .dsh-github-primary-btn:hover {
1128
+ background: #58a6ff;
1129
+ border-color: #58a6ff;
1130
+ color: #0d1117;
1131
+ opacity: 0.88;
1132
+ }
1133
+
923
1134
  /* 单选框点强调色 */
924
1135
  .dsh-github-radio {
925
1136
  accent-color: var(--dsw-alias-link, #0969da);
@@ -968,6 +1179,24 @@ body[data-ds-dark-theme] .dsh-github-radio {
968
1179
  }
969
1180
  }, renderOctocat(Math.round(size * 0.58), "#ffffff"));
970
1181
  }
1182
+ // 插件身份徽标(h 工厂注入):标注界面由第三方插件 dsh-github-flow 提供。
1183
+ // 点击不直接跳仓库,而是派发全局事件打开「设置-关于」弹窗(仓库/协议/Star
1184
+ // 都在里面),由各宿主面板监听并挂载弹窗——避免从任意界面突兀跳浏览器。
1185
+ function renderPluginBadge(h) {
1186
+ return h("button", {
1187
+ className: "dsh-github-plugin-badge",
1188
+ type: "button",
1189
+ title: "本界面由第三方插件 dsh-github-flow 提供(非 DSH / GitHub 官方)。点击查看关于。",
1190
+ onClick: function (e) {
1191
+ e.stopPropagation();
1192
+ e.preventDefault();
1193
+ try {
1194
+ window.dispatchEvent(new CustomEvent("dsh-github-open-about"));
1195
+ }
1196
+ catch (err) { /* 事件派发失败不阻断点击语义 */ }
1197
+ },
1198
+ }, "插件");
1199
+ }
971
1200
  // 认证与安装引导卡片组件(响应式自适应中央大面板与右侧栏)
972
1201
  function AuthGuideCard(props) {
973
1202
  var auth = props ? props.auth : null;
@@ -1012,15 +1241,17 @@ body[data-ds-dark-theme] .dsh-github-radio {
1012
1241
  });
1013
1242
  var json = await res.json();
1014
1243
  if (json.ok) {
1015
- setToastMsg("已唤起系统终端,请在弹出窗口中完成操作");
1016
- setTimeout(() => setToastMsg(null), 4000);
1244
+ // 措辞留余地:start 的成败偶受系统会话策略影响,未弹出时用户仍有手动出路
1245
+ setToastMsg("已唤起系统终端,请在弹出窗口中完成操作;若未弹出,可手动打开终端执行上方命令");
1246
+ setTimeout(() => setToastMsg(null), 6000);
1017
1247
  }
1018
1248
  else {
1019
- setToastMsg(json.message || "唤起失败,请在终端手动运行");
1249
+ // 失败如实展示:后端带回真实原因(如未找到终端/命令失败),并给出手动命令
1250
+ setToastMsg("❌ " + (json.message || "唤起失败,请手动打开终端执行上方命令"));
1020
1251
  }
1021
1252
  }
1022
1253
  catch (e) {
1023
- setToastMsg("无法连接后台服务,请在终端手动运行");
1254
+ setToastMsg("无法连接后台服务,请手动打开终端执行上方命令");
1024
1255
  }
1025
1256
  finally {
1026
1257
  setLaunching(false);
@@ -1076,9 +1307,14 @@ body[data-ds-dark-theme] .dsh-github-radio {
1076
1307
  className: "dsh-github-action-btn",
1077
1308
  onClick: function () {
1078
1309
  setToastMsg("正在快速检测登录状态...");
1079
- fetch("/api/github/auth?force=true")
1310
+ // 探测超时保护:后端 auth 探针本身 10s 超时,前端同样 10s 兜底——
1311
+ // 无超时时挂起会让 toast 永远停在"正在检测"且自愈刷新不触发
1312
+ var probeController = new AbortController();
1313
+ var probeTimer = setTimeout(function () { probeController.abort(); }, 10000);
1314
+ fetch("/api/github/auth?force=true", { signal: probeController.signal })
1080
1315
  .then(function (res) { return res.json(); })
1081
1316
  .then(function (json) {
1317
+ clearTimeout(probeTimer);
1082
1318
  if (json && json.ok && json.auth && json.auth.loggedIn) {
1083
1319
  setToastMsg("✅ 已识别到 GitHub CLI 登录态,正在拉取数据...");
1084
1320
  clearStoredGlobalOverview();
@@ -1090,6 +1326,8 @@ body[data-ds-dark-theme] .dsh-github-radio {
1090
1326
  }
1091
1327
  })
1092
1328
  .catch(function () {
1329
+ clearTimeout(probeTimer);
1330
+ setToastMsg("❌ 检测超时或后端不可达,请稍后重试");
1093
1331
  onRefresh();
1094
1332
  });
1095
1333
  },
@@ -1151,7 +1389,16 @@ body[data-ds-dark-theme] .dsh-github-radio {
1151
1389
  }
1152
1390
  else {
1153
1391
  setStatus("failed");
1154
- setMsg(json.message || "升级命令执行失败,请在宿主终端手动执行。");
1392
+ // 失败原因透传:后端 log 的末尾几行含真实报错(如 'dsh' 不是内部命令),
1393
+ // 只给「退出码异常」这类笼统话术用户无从下手
1394
+ var logTail = "";
1395
+ if (json.log && typeof json.log === "string") {
1396
+ var lines = json.log.trim().split("\n").filter(function (l) { return l.trim(); });
1397
+ if (lines.length > 0) {
1398
+ logTail = "\n\n📝 日志末尾: " + lines.slice(-4).join(" ⏎ ").slice(0, 400);
1399
+ }
1400
+ }
1401
+ setMsg((json.message || "升级命令执行失败,请在宿主终端手动执行。") + logTail);
1155
1402
  }
1156
1403
  }
1157
1404
  catch (err) {
@@ -1167,7 +1414,9 @@ body[data-ds-dark-theme] .dsh-github-radio {
1167
1414
  setDismissed(true);
1168
1415
  };
1169
1416
  var handleCopy = function () {
1170
- var cmd = updateInfo.upgradeCommand || "pnpm dsh plugin add dsh-github-flow@latest";
1417
+ // 兜底命令也必须带 --profile(官方 CLI 必填)——正常路径 upgradeCommand
1418
+ // 由后端按真实 profile 生成,此默认值仅在后端数据缺失时兜底
1419
+ var cmd = updateInfo.upgradeCommand || "dsh plugin --profile desktop add dsh-github-flow@latest";
1171
1420
  if (navigator.clipboard) {
1172
1421
  navigator.clipboard.writeText(cmd);
1173
1422
  alert("已复制升级命令到剪贴板:\n" + cmd);
@@ -1223,7 +1472,17 @@ body[data-ds-dark-theme] .dsh-github-radio {
1223
1472
  }, status === "upgrading" ? "升级中..." : "⚡ 一键升级"), h("button", {
1224
1473
  className: "dsh-github-btn-sm",
1225
1474
  onClick: handleCopy,
1226
- style: { fontSize: "11px", padding: "4px 8px" },
1475
+ style: {
1476
+ fontSize: "11px",
1477
+ padding: "4px 8px",
1478
+ // 失败态高亮:与驾驶舱横幅同一引导逻辑(失败文案指向复制动作)
1479
+ ...(status === "failed" ? {
1480
+ background: "var(--dsw-alias-link, #0969da)",
1481
+ color: "#ffffff",
1482
+ borderColor: "var(--dsw-alias-link, #0969da)",
1483
+ fontWeight: 600,
1484
+ } : {}),
1485
+ },
1227
1486
  }, "📋 复制命令")));
1228
1487
  }
1229
1488
  // 2. 全景驾驶舱大面板横幅模式
@@ -1267,8 +1526,17 @@ body[data-ds-dark-theme] .dsh-github-radio {
1267
1526
  }, status === "upgrading" ? "正在自动升级中..." : "⚡ 一键自动升级"), h("button", {
1268
1527
  className: "dsh-github-btn-sm",
1269
1528
  onClick: handleCopy,
1270
- style: { padding: "6px 12px" },
1271
- }, "📋 复制命令"), h("button", {
1529
+ style: {
1530
+ padding: "6px 12px",
1531
+ // 失败态高亮复制按钮:失败文案引导的动作就是它,视觉权重同步提升
1532
+ ...(status === "failed" ? {
1533
+ background: "var(--dsw-alias-link, #0969da)",
1534
+ color: "#ffffff",
1535
+ borderColor: "var(--dsw-alias-link, #0969da)",
1536
+ fontWeight: 600,
1537
+ } : {}),
1538
+ },
1539
+ }, status === "failed" ? "📋 复制命令手动升级" : "📋 复制命令"), h("button", {
1272
1540
  onClick: handleDismiss,
1273
1541
  title: "不再提醒此版本",
1274
1542
  style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "16px", padding: "0 4px" },
@@ -1337,7 +1605,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
1337
1605
  }, text);
1338
1606
  }
1339
1607
  // 渲染 GitHub 仓库信息与操作视图的核心函数
1340
- function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0) {
1608
+ function renderGithubRepoView(h, data, loading, error, onRefresh, isRightbar = false, updateInfo = null, syncStatus = "", lastSyncedAt = 0, onOpenSettings = null) {
1341
1609
  var auth = data ? data.auth : null;
1342
1610
  var repo = data ? data.repo : null;
1343
1611
  var prs = (data && data.pullRequests) || [];
@@ -1347,11 +1615,19 @@ body[data-ds-dark-theme] .dsh-github-radio {
1347
1615
  var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
1348
1616
  ? h(UpgradeNotificationCard, { updateInfo: effectiveUpdateInfo, isRightbar })
1349
1617
  : null;
1350
- var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(isRightbar ? 26 : 30), h("h2", null, isRightbar ? "GitHub 仓库" : "GitHub 工作台"), syncStatus ? h(SyncStatusBadge, { syncStatus, lastSyncedAt, isRightbar }) : null), h("button", {
1618
+ var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(isRightbar ? 26 : 30), h("h2", null, isRightbar ? "GitHub 仓库" : "GitHub 工作台"), renderPluginBadge(h), syncStatus ? h(SyncStatusBadge, { syncStatus, lastSyncedAt, isRightbar }) : null), h("div", { style: { display: "flex", alignItems: "center", gap: "6px" } }, h("button", {
1351
1619
  className: "dsh-github-refresh-btn",
1352
1620
  onClick: onRefresh,
1353
1621
  disabled: loading,
1354
- }, loading ? "刷新中..." : "🔄 刷新"));
1622
+ }, loading ? "刷新中..." : "🔄 刷新"),
1623
+ // 设置按钮:与全景主页复用同一设置弹窗(传入回调时渲染——目前仅右侧栏)。
1624
+ // 右侧栏此前没有设置入口,用户必须切到主页才能改同步/代理/更新设置。
1625
+ onOpenSettings ? h("button", {
1626
+ className: "dsh-github-btn-sm",
1627
+ onClick: onOpenSettings,
1628
+ title: "配置数据同步节奏、本地缓存、网络代理与插件信息",
1629
+ style: { padding: "4px 10px" },
1630
+ }, "⚙️") : null));
1355
1631
  // 仓库基础卡片
1356
1632
  var repoCardEl = h("div", { className: "dsh-github-card" }, h("div", { className: "dsh-github-card-title" }, "当前工作区 GitHub 仓库"), h("div", { className: "dsh-github-card-value" }, repo
1357
1633
  ? h("a", { href: repo.url, target: "_blank", className: "dsh-github-repo-link" }, repo.nameWithOwner)
@@ -1463,9 +1739,12 @@ body[data-ds-dark-theme] .dsh-github-radio {
1463
1739
  // 全局持久化缓存与偏好配置键名
1464
1740
  var GLOBAL_OVERVIEW_CACHE_KEY = "dsh_github_global_overview_snapshot_v1";
1465
1741
  var SYNC_SETTINGS_STORAGE_KEY = "dsh_github_sync_settings_v1";
1742
+ // 同步策略收敛为两态:'swr'(打开秒开旧数据+后台静默拉新)| 'manual'(仅手动刷新)。
1743
+ // 旧的第三态 'interval'(前端 setInterval 轮询)已删除:浏览器对后台标签页节流
1744
+ // 定时器、组件卸载即销毁,该模式在真实环境几乎从不按承诺工作(停留短于间隔则
1745
+ // 永不触发);其「打开即新鲜」的诉求由 SWR 挂载自愈覆盖,「持续同步」点刷新即可。
1466
1746
  var DEFAULT_SYNC_SETTINGS = {
1467
- strategy: "swr", // 'swr' (秒开展示旧数据,后台静默拉取) | 'manual' (仅手动刷新) | 'interval' (定时轮询)
1468
- intervalMs: 300000, // 默认 5 分钟 (300,000ms)
1747
+ strategy: "swr", // 'swr' | 'manual'
1469
1748
  };
1470
1749
  function getLocalSyncSettings() {
1471
1750
  try {
@@ -1473,7 +1752,14 @@ body[data-ds-dark-theme] .dsh-github-radio {
1473
1752
  if (!raw)
1474
1753
  return DEFAULT_SYNC_SETTINGS;
1475
1754
  var parsed = JSON.parse(raw);
1476
- return Object.assign({}, DEFAULT_SYNC_SETTINGS, parsed);
1755
+ var merged = Object.assign({}, DEFAULT_SYNC_SETTINGS, parsed);
1756
+ // 旧版本迁移:曾选过 'interval' 的用户自动回落 SWR(最接近原意图),
1757
+ // 并立即覆写存储防止每次读取都走迁移分支。
1758
+ if (merged.strategy === "interval") {
1759
+ merged.strategy = "swr";
1760
+ saveLocalSyncSettings(merged);
1761
+ }
1762
+ return merged;
1477
1763
  }
1478
1764
  catch (e) {
1479
1765
  return DEFAULT_SYNC_SETTINGS;
@@ -1628,10 +1914,10 @@ body[data-ds-dark-theme] .dsh-github-radio {
1628
1914
  window.removeEventListener("dsh-github-cache-cleared", onCacheCleared);
1629
1915
  };
1630
1916
  }, []);
1631
- // gh 数据拉取完毕后,闲时 1.5 秒异步检测升级,绝对不抢占主业务首屏资源
1917
+ // 检查更新与 gh 环境链路(安装/登录/业务数据)零依赖,挂载即并行发起:
1918
+ // 原先门控在 [data] 之后会串行等 5~19 秒,且 gh 冷启动失败时 data 恒空导致横幅永不出。
1919
+ // 仅 300ms 微延迟避让首屏渲染;并发由服务端 inFlightPromise + 24h 缓存合流去重。
1632
1920
  React.useEffect(() => {
1633
- if (!data)
1634
- return;
1635
1921
  var timer = setTimeout(function () {
1636
1922
  fetch("/api/github/check-update")
1637
1923
  .then(function (res) { return res.json(); })
@@ -1641,9 +1927,9 @@ body[data-ds-dark-theme] .dsh-github-radio {
1641
1927
  }
1642
1928
  })
1643
1929
  .catch(function () { });
1644
- }, 1500);
1930
+ }, 300);
1645
1931
  return function () { clearTimeout(timer); };
1646
- }, [data]);
1932
+ }, []);
1647
1933
  // 遵循全局规则的数据拉取核心函数 (仅依赖标量 cwd 与 sessionId,坚决去除 props 引用依赖,彻底终结死循环)
1648
1934
  var loadData = React.useCallback(async (forceRefresh = false) => {
1649
1935
  // 规则执行:若主页设置为「纯手动模式 (manual)」,且非用户点击强制刷新,且本地已有数据,坚决不打后台网络!
@@ -1655,9 +1941,15 @@ body[data-ds-dark-theme] .dsh-github-radio {
1655
1941
  }
1656
1942
  setSyncStatus(data ? "syncing" : "loading");
1657
1943
  setError(null);
1944
+ // 与全局面板同规格的超时保护:任一 fetch 挂起(如系统代理劫持回环请求)时
1945
+ // 45 秒 abort,loading 必然清除——未安装 gh 的用户能拿到明确的错误出口
1946
+ // 而非永久转圈。此前本函数完全没有 AbortController,挂起即永久 loading。
1947
+ var controller = new AbortController();
1948
+ var timedOut = false;
1949
+ var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 45000);
1658
1950
  try {
1659
1951
  if (forceRefresh) {
1660
- await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
1952
+ await fetch("/api/github/refresh", { method: "POST", signal: controller.signal }).catch(() => { });
1661
1953
  }
1662
1954
  var url = "/api/github/overview";
1663
1955
  var params = [];
@@ -1670,7 +1962,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
1670
1962
  if (params.length > 0) {
1671
1963
  url += "?" + params.join("&");
1672
1964
  }
1673
- var res = await fetch(url);
1965
+ var res = await fetch(url, { signal: controller.signal });
1674
1966
  if (!res.ok) {
1675
1967
  var errText = await res.text().catch(() => "");
1676
1968
  var msg = `后端服务未就绪 (${res.status}): ${errText || res.statusText}`;
@@ -1701,17 +1993,26 @@ body[data-ds-dark-theme] .dsh-github-radio {
1701
1993
  }
1702
1994
  catch (err) {
1703
1995
  if (data) {
1996
+ // 后台网络故障时,旧数据依然完整可用
1704
1997
  setSyncStatus("failed");
1705
1998
  }
1706
1999
  else {
1707
- setError(err.message || "网络异常");
2000
+ if (timedOut || err.name === "AbortError") {
2001
+ setError("获取超时(45 秒):本机 gh 命令响应过慢或网络代理劫持了回环请求。可稍后重试,或在「⚙️ 设置 → 🌐 网络代理」确认代理已放行 127.0.0.1。");
2002
+ }
2003
+ else {
2004
+ setError(err.message || "网络异常");
2005
+ }
1708
2006
  }
1709
2007
  }
1710
2008
  finally {
2009
+ clearTimeout(timer);
1711
2010
  setLoading(false);
1712
2011
  }
1713
2012
  }, [cwd, sessionId, data, syncSettings.strategy]);
1714
- // 挂载与策略调度:与主页规则 100% 保持一致
2013
+ // 挂载与策略调度:与主页规则 100% 保持一致。
2014
+ // SWR 分支不判 data:挂载 300ms 后台静默拉新,昨天的持久化快照当场自愈——
2015
+ // 「打开就能看到最新」是 SWR 的定义,不依赖用户停留时长。
1715
2016
  React.useEffect(() => {
1716
2017
  if (syncSettings.strategy === "swr") {
1717
2018
  var timer = setTimeout(function () { loadData(false); }, 300);
@@ -1721,15 +2022,8 @@ body[data-ds-dark-theme] .dsh-github-radio {
1721
2022
  loadData(false);
1722
2023
  }
1723
2024
  }, [cwd, sessionId, syncSettings.strategy]);
1724
- // 定时轮询调度:与主页完全一致
1725
- React.useEffect(() => {
1726
- if (syncSettings.strategy !== "interval" || !syncSettings.intervalMs || syncSettings.intervalMs <= 0)
1727
- return;
1728
- var timer = setInterval(function () {
1729
- loadData(false);
1730
- }, syncSettings.intervalMs);
1731
- return function () { clearInterval(timer); };
1732
- }, [syncSettings.strategy, syncSettings.intervalMs, loadData]);
2025
+ // (原定时轮询调度已随 'interval' 策略一并移除:前端 setInterval 受浏览器
2026
+ // 后台节流且随组件卸载销毁,无法兑现「定时自动更新」承诺)
1733
2027
  // 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
1734
2028
  React.useEffect(() => {
1735
2029
  var onFocus = () => {
@@ -1747,8 +2041,56 @@ body[data-ds-dark-theme] .dsh-github-radio {
1747
2041
  window.addEventListener("focus", onFocus);
1748
2042
  return () => window.removeEventListener("focus", onFocus);
1749
2043
  }, [data, loadData]);
2044
+ // 设置弹窗(右侧栏独立实例):插件徽标 → 直开「关于」分区;⚙️ 按钮 → 默认
2045
+ // 「数据同步」。与全景主页复用同一弹窗组件,同步设置经事件总线实时对齐两侧。
2046
+ var [aboutModalOpen, setAboutModalOpen] = React.useState(false);
2047
+ var [rightbarModalOpen, setRightbarModalOpen] = React.useState(false);
2048
+ var [rightbarModalSection, setRightbarModalSection] = React.useState("sync");
2049
+ var containerRef = React.useRef(null);
2050
+ React.useEffect(() => {
2051
+ // 多宿主同挂载防叠层:全景主页与右侧栏可能同时在 DOM 中,徽标事件若被
2052
+ // 两处同时响应会出现双层弹窗。只有「自身容器可见」的宿主才响应——
2053
+ // 徽标所在面板必然是用户当前看到的那个。
2054
+ var onOpenAbout = () => {
2055
+ var el = containerRef.current;
2056
+ if (!el)
2057
+ return;
2058
+ var rect = el.getBoundingClientRect();
2059
+ var visible = rect.width > 0 && rect.height > 0;
2060
+ if (visible)
2061
+ setAboutModalOpen(true);
2062
+ };
2063
+ window.addEventListener("dsh-github-open-about", onOpenAbout);
2064
+ return () => window.removeEventListener("dsh-github-open-about", onOpenAbout);
2065
+ }, []);
2066
+ var openRightbarSettings = () => {
2067
+ setRightbarModalSection("sync");
2068
+ setRightbarModalOpen(true);
2069
+ };
1750
2070
  var h = React.createElement;
1751
- return h("div", { className: "dsh-github-rightbar-container" }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true, updateInfo, syncStatus, lastSyncedAt));
2071
+ return h("div", { className: "dsh-github-rightbar-container", ref: containerRef }, ...renderGithubRepoView(h, data, loading, error, () => loadData(true), true, updateInfo, syncStatus, lastSyncedAt, openRightbarSettings),
2072
+ // 弹窗一:插件徽标触发的「关于」单页展示
2073
+ h(GlobalSyncSettingsModal, {
2074
+ isOpen: aboutModalOpen,
2075
+ initialSection: "about",
2076
+ onClose: () => setAboutModalOpen(false),
2077
+ currentSettings: syncSettings,
2078
+ onSave: function () { },
2079
+ onClearCache: function () { setData(null); },
2080
+ }),
2081
+ // 弹窗二:⚙️ 按钮触发的完整设置(与主页同一弹窗组件,分区可自由切换)
2082
+ h(GlobalSyncSettingsModal, {
2083
+ isOpen: rightbarModalOpen,
2084
+ initialSection: rightbarModalSection,
2085
+ onClose: () => setRightbarModalOpen(false),
2086
+ currentSettings: syncSettings,
2087
+ onSave: function (newSettings) { setSyncSettings(newSettings); },
2088
+ onClearCache: function () {
2089
+ setData(null);
2090
+ setLastSyncedAt(0);
2091
+ setSyncStatus("loading");
2092
+ },
2093
+ }));
1752
2094
  }
1753
2095
  // 2. 右侧栏 Tab 标题组件
1754
2096
  function GithubRightbarTitle() {
@@ -1764,19 +2106,78 @@ body[data-ds-dark-theme] .dsh-github-radio {
1764
2106
  const active = Boolean(props && props.active);
1765
2107
  return React.createElement("div", {
1766
2108
  className: "dsh-github-icon" + (active ? " active" : ""),
1767
- title: "GitHub 全景驾驶舱",
2109
+ title: "GitHub 全景驾驶舱(dsh-github-flow 插件)",
1768
2110
  }, React.createElement("div", {
1769
2111
  className: "dsh-github-icon-inner" + (active ? " active" : "")
1770
2112
  }, renderOctocat(18, "currentColor")));
1771
2113
  }
1772
- // 全景驾驶舱「⚙️ 数据同步与缓存设置」模态弹窗组件
2114
+ // 全景驾驶舱「⚙️ 设置」模态弹窗组件:左侧分区导航(数据同步 / 网络代理 / 更新设置 / 关于)+ 右侧内容区。
2115
+ // 为什么改多分区:弹窗承载能力从单一「同步偏好」扩展为多项插件设置(后续可继续加),
2116
+ // 单页滚动堆叠会让用户找不到入口;分区导航让每项设置拥有独立心智空间与保存动作。
2117
+ // initialSection:宿主可指定初始分区——插件徽标点击时直开「关于」,单独展示身份信息。
1773
2118
  function GlobalSyncSettingsModal(props) {
1774
2119
  if (!props.isOpen)
1775
2120
  return null;
2121
+ // ---- 当前激活分区:sync / proxy / update / about ----
2122
+ var [activeSection, setActiveSection] = React.useState(props.initialSection || "sync");
1776
2123
  var [strategy, setStrategy] = React.useState(props.currentSettings.strategy || "swr");
1777
- var [intervalMs, setIntervalMs] = React.useState(props.currentSettings.intervalMs || 300000);
1778
2124
  var [cacheMeta, setCacheMeta] = React.useState(getCacheStorageMeta());
1779
2125
  var [clearTip, setClearTip] = React.useState("");
2126
+ // ---- 更新设置分区状态 ----
2127
+ // 打开弹窗时从后端拉取真实模式(权威源是 update-settings.json 而非 localStorage),
2128
+ // 与代理分区同范式:避免多窗口/外部手改文件导致 UI 与实际生效配置漂移。
2129
+ var [updateMode, setUpdateMode] = React.useState("official");
2130
+ var [updateBusy, setUpdateBusy] = React.useState(false);
2131
+ var [updateMsg, setUpdateMsg] = React.useState(null);
2132
+ // ---- 关于分区:当前运行版本号(弹窗打开时轻量拉取,失败静默回落占位)----
2133
+ var [aboutVersion, setAboutVersion] = React.useState(null);
2134
+ React.useEffect(function () {
2135
+ if (!props.isOpen)
2136
+ return;
2137
+ fetch("/api/github/update-settings")
2138
+ .then(function (r) { return r.json(); })
2139
+ .then(function (j) {
2140
+ if (j && j.ok && j.data && j.data.mode) {
2141
+ setUpdateMode(j.data.mode);
2142
+ }
2143
+ })
2144
+ .catch(function () { });
2145
+ // 版本号:check-update 命中 24h 缓存时零成本;失败静默(关于页显示占位版本)
2146
+ fetch("/api/github/check-update")
2147
+ .then(function (r) { return r.json(); })
2148
+ .then(function (j) {
2149
+ if (j && j.ok && j.data && j.data.currentVersion)
2150
+ setAboutVersion(j.data.currentVersion);
2151
+ })
2152
+ .catch(function () { });
2153
+ }, [props.isOpen]);
2154
+ // ---- 网络代理分区状态 ----
2155
+ // 打开弹窗时从后端拉取真实代理状态(权威源是 proxy.json 而非 localStorage),
2156
+ // 避免多窗口/外部手改文件导致的 UI 与实际生效配置漂移。
2157
+ var [proxyEnabled, setProxyEnabled] = React.useState(false);
2158
+ var [proxyUrl, setProxyUrl] = React.useState("");
2159
+ var [proxyNoProxy, setProxyNoProxy] = React.useState("localhost,127.0.0.1");
2160
+ var [proxyApplyNpm, setProxyApplyNpm] = React.useState(true);
2161
+ var [proxyBusy, setProxyBusy] = React.useState(false);
2162
+ var [proxyMsg, setProxyMsg] = React.useState(null);
2163
+ var [proxyProbe, setProxyProbe] = React.useState(null);
2164
+ React.useEffect(function () {
2165
+ if (!props.isOpen)
2166
+ return;
2167
+ fetch("/api/github/proxy")
2168
+ .then(function (r) { return r.json(); })
2169
+ .then(function (j) {
2170
+ if (j && j.ok && j.state) {
2171
+ setProxyEnabled(Boolean(j.state.config && j.state.config.url));
2172
+ setProxyUrl((j.state.config && j.state.config.url) || "");
2173
+ setProxyNoProxy((j.state.config && j.state.config.noProxy) || "localhost,127.0.0.1");
2174
+ setProxyApplyNpm(j.state.config ? j.state.config.applyToPackageManager !== false : true);
2175
+ if (j.state.lastProbe)
2176
+ setProxyProbe(j.state.lastProbe);
2177
+ }
2178
+ })
2179
+ .catch(function () { });
2180
+ }, [props.isOpen]);
1780
2181
  // 键盘 Escape 快捷关闭支持
1781
2182
  React.useEffect(function () {
1782
2183
  var onKeyDown = function (e) {
@@ -1787,11 +2188,59 @@ body[data-ds-dark-theme] .dsh-github-radio {
1787
2188
  window.addEventListener("keydown", onKeyDown);
1788
2189
  return function () { window.removeEventListener("keydown", onKeyDown); };
1789
2190
  }, [props.onClose]);
2191
+ // initialSection 同步:弹窗实例常驻(return null 在 hooks 后,不卸载),
2192
+ // useState 初值只在首挂载取一次——宿主切换入口重开时(如齿轮→关→徽标),
2193
+ // 必须显式把 activeSection 对齐到新的 initialSection,否则停留在旧分区。
2194
+ React.useEffect(function () {
2195
+ if (props.isOpen && props.initialSection) {
2196
+ setActiveSection(props.initialSection);
2197
+ }
2198
+ }, [props.isOpen, props.initialSection]);
2199
+ // 更新源保存:POST 后端(写盘 + 热切换 checker + 立即用新源真探)。
2200
+ // 返回的 probe 结果如实展示:探测失败提示网络问题而非假报「已生效无异常」。
2201
+ var handleUpdateSave = function () {
2202
+ setUpdateBusy(true);
2203
+ setUpdateMsg(null);
2204
+ fetch("/api/github/update-settings", {
2205
+ method: "POST",
2206
+ headers: { "Content-Type": "application/json" },
2207
+ body: JSON.stringify({ mode: updateMode }),
2208
+ })
2209
+ .then(function (r) { return r.json(); })
2210
+ .then(function (j) {
2211
+ setUpdateBusy(false);
2212
+ if (j && j.ok && j.data) {
2213
+ var d = j.data;
2214
+ var text = "";
2215
+ if (d.mode === "off") {
2216
+ text = "✅ 已关闭自动检查更新(/gh upgrade 与一键升级仍可用),无需重启";
2217
+ }
2218
+ else if (d.probe && d.probe.latestVersion) {
2219
+ text = "✅ 已切换至" + d.description + ",当前最新版本 v" + d.probe.latestVersion
2220
+ + (d.probe.hasUpdate ? "(有可用更新)" : "(已是最新)") + ",无需重启";
2221
+ }
2222
+ else {
2223
+ // 探测失败如实告知:设置已生效但新源暂不可达(可能是网络或源同步问题)
2224
+ text = "✅ 已切换至" + d.description + ",但新源暂时不可达(版本探测失败,稍后会自动重试);设置已生效,无需重启";
2225
+ }
2226
+ if (d.persisted === false) {
2227
+ text += ";⚠️ 设置写盘失败,重启后需重新选择";
2228
+ }
2229
+ setUpdateMsg({ kind: "ok", text: text });
2230
+ }
2231
+ else {
2232
+ setUpdateMsg({ kind: "err", text: (j && j.message) || "保存失败" });
2233
+ }
2234
+ })
2235
+ .catch(function (e) {
2236
+ setUpdateBusy(false);
2237
+ setUpdateMsg({ kind: "err", text: "网络请求失败: " + (e && e.message ? e.message : e) });
2238
+ });
2239
+ };
1790
2240
  var h = React.createElement;
1791
2241
  var handleSave = function () {
1792
2242
  var updated = {
1793
2243
  strategy: strategy,
1794
- intervalMs: Number(intervalMs),
1795
2244
  };
1796
2245
  saveLocalSyncSettings(updated);
1797
2246
  props.onSave(updated);
@@ -1804,13 +2253,88 @@ body[data-ds-dark-theme] .dsh-github-radio {
1804
2253
  setClearTip("本地缓存已彻底清空!");
1805
2254
  setTimeout(function () { setClearTip(""); }, 2000);
1806
2255
  };
2256
+ // 代理保存:POST 后端后立即全局生效(gh 子进程/升级链路 env + npm 探测 dispatcher)。
2257
+ // 保存成功即提示「无需重启」,与「设置完成之后全局生效」的语义对齐。
2258
+ var handleProxySave = function () {
2259
+ setProxyBusy(true);
2260
+ setProxyMsg(null);
2261
+ fetch("/api/github/proxy", {
2262
+ method: "POST",
2263
+ headers: { "Content-Type": "application/json" },
2264
+ body: JSON.stringify({
2265
+ url: proxyEnabled ? proxyUrl.trim() : "",
2266
+ noProxy: proxyNoProxy,
2267
+ applyToPackageManager: proxyApplyNpm,
2268
+ }),
2269
+ })
2270
+ .then(function (r) { return r.json(); })
2271
+ .then(function (j) {
2272
+ setProxyBusy(false);
2273
+ if (j && j.ok) {
2274
+ setProxyMsg({
2275
+ kind: "ok",
2276
+ text: j.state && j.state.config && j.state.config.url
2277
+ ? "🌐 代理已保存并全局生效(驾驶舱 / 右侧栏 / Agent 工具 / 升级链路),无需重启"
2278
+ : "已切换为系统默认网络(直连或系统环境代理),无需重启",
2279
+ });
2280
+ if (j.state && j.state.lastProbe)
2281
+ setProxyProbe(j.state.lastProbe);
2282
+ }
2283
+ else {
2284
+ setProxyMsg({ kind: "err", text: (j && j.message) || "保存失败" });
2285
+ }
2286
+ })
2287
+ .catch(function (e) {
2288
+ setProxyBusy(false);
2289
+ setProxyMsg({ kind: "err", text: "网络请求失败: " + (e && e.message ? e.message : e) });
2290
+ });
2291
+ };
2292
+ // 连通性探测:验证经当前配置访问 GitHub API 的可达性(2.5s 超时由后端控制)
2293
+ var handleProxyProbe = function () {
2294
+ setProxyBusy(true);
2295
+ setProxyMsg(null);
2296
+ fetch("/api/github/proxy/probe", { method: "POST" })
2297
+ .then(function (r) { return r.json(); })
2298
+ .then(function (j) {
2299
+ setProxyBusy(false);
2300
+ if (j && j.ok && j.probe) {
2301
+ setProxyProbe(j.probe);
2302
+ setProxyMsg(j.probe.ok
2303
+ ? { kind: "ok", text: "✅ GitHub API 可达 (" + j.probe.latencyMs + "ms)" }
2304
+ : { kind: "err", text: "❌ " + (j.probe.error || "探测失败") });
2305
+ }
2306
+ else {
2307
+ setProxyMsg({ kind: "err", text: (j && j.message) || "探测失败" });
2308
+ }
2309
+ })
2310
+ .catch(function (e) {
2311
+ setProxyBusy(false);
2312
+ setProxyMsg({ kind: "err", text: "探测请求失败: " + (e && e.message ? e.message : e) });
2313
+ });
2314
+ };
1807
2315
  return h("div", { className: "dsh-github-modal-overlay", onClick: props.onClose }, h("div", {
1808
2316
  className: "dsh-github-modal-dialog",
1809
2317
  onClick: function (e) { e.stopPropagation(); },
1810
- }, h("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: "16px" } }, h("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 } }, "⚙️ 数据同步与缓存偏好"), h("button", {
2318
+ },
2319
+ // 弹窗头部:标题 + 关闭按钮
2320
+ h("div", { className: "dsh-github-modal-header" }, h("h3", { style: { margin: 0, fontSize: "16px", fontWeight: 600 } }, "⚙️ 设置"), h("button", {
1811
2321
  style: { background: "none", border: "none", cursor: "pointer", fontSize: "18px", color: "var(--dsw-alias-label-secondary, #656d76)" },
1812
2322
  onClick: props.onClose,
1813
- }, "✕")), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "配置 GitHub 全景驾驶舱的数据拉取节奏。启用秒开模式可利用本地持久化缓存消除每次打开时的漫长等待。"),
2323
+ }, "✕")),
2324
+ // 双栏骨架:左侧分区导航 + 右侧内容区(窄屏自动折叠为横向页签)
2325
+ h("div", { className: "dsh-github-modal-body" }, h("div", { className: "dsh-github-settings-nav" }, h("button", {
2326
+ className: "dsh-github-settings-nav-item" + (activeSection === "sync" ? " active" : ""),
2327
+ onClick: function () { setActiveSection("sync"); },
2328
+ }, "🔄 数据同步"), h("button", {
2329
+ className: "dsh-github-settings-nav-item" + (activeSection === "proxy" ? " active" : ""),
2330
+ onClick: function () { setActiveSection("proxy"); },
2331
+ }, "🌐 网络代理"), h("button", {
2332
+ className: "dsh-github-settings-nav-item" + (activeSection === "update" ? " active" : ""),
2333
+ onClick: function () { setActiveSection("update"); },
2334
+ }, "⬆️ 更新设置"), h("button", {
2335
+ className: "dsh-github-settings-nav-item" + (activeSection === "about" ? " active" : ""),
2336
+ onClick: function () { setActiveSection("about"); },
2337
+ }, "ℹ️ 关于")), h("div", { className: "dsh-github-settings-content" }, activeSection === "sync" ? h("div", null, h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "配置 GitHub 全景驾驶舱的数据拉取节奏。启用秒开模式可利用本地持久化缓存消除每次打开时的漫长等待。"),
1814
2338
  // 刷新策略选择
1815
2339
  h("div", { style: { marginBottom: "16px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" } }, "数据刷新策略"),
1816
2340
  // 策略 A: SWR 秒开
@@ -1822,18 +2346,10 @@ body[data-ds-dark-theme] .dsh-github-radio {
1822
2346
  h("div", {
1823
2347
  className: "dsh-github-radio-card" + (strategy === "manual" ? " active" : ""),
1824
2348
  onClick: function () { setStrategy("manual"); },
1825
- }, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "manual", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "📋 仅在手动点击时拉取 (Manual Only)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "打开只浏览本地缓存,绝不自动触发后台 gh 进程,最省电、最省资源。"))),
1826
- // 策略 C: 定时轮询
1827
- h("div", {
1828
- className: "dsh-github-radio-card" + (strategy === "interval" ? " active" : ""),
1829
- onClick: function () { setStrategy("interval"); },
1830
- }, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "interval", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "⏱️ 定时自动轮询 (Auto Poll)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "驾驶舱打开期间,按照下方设定的时间间隔在后台自动轮询。")))),
1831
- // 自动拉取间隔下拉
1832
- h("div", { style: { marginBottom: "18px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "6px" } }, "后台自动拉取间隔"), h("select", {
1833
- className: "dsh-github-select",
1834
- value: intervalMs,
1835
- onChange: function (e) { setIntervalMs(Number(e.target.value)); },
1836
- }, h("option", { value: 60000 }, "1 分钟 (高频刷新)"), h("option", { value: 300000 }, "5 分钟 (默认推荐)"), h("option", { value: 900000 }, "15 分钟"), h("option", { value: 1800000 }, "30 分钟 (推荐节能)"), h("option", { value: 3600000 }, "1 小时"), h("option", { value: 0 }, "从不自动刷新 (仅手动)"))),
2349
+ }, h("input", { type: "radio", className: "dsh-github-radio", checked: strategy === "manual", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "📋 仅在手动点击时拉取 (Manual Only)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "打开只浏览本地缓存,绝不自动触发后台 gh 进程,最省电、最省资源;想更新时点击「🔄 刷新」即可。")))
2350
+ // (第三种「定时自动轮询」策略已移除:前端定时器受浏览器后台节流且随
2351
+ // 组件卸载销毁,无法兑现定时更新承诺;SWR 挂载自愈已覆盖「打开即新鲜」)
2352
+ ),
1837
2353
  // 本地缓存信息与清理
1838
2354
  h("div", {
1839
2355
  className: "dsh-github-cache-info-box",
@@ -1843,13 +2359,126 @@ body[data-ds-dark-theme] .dsh-github-radio {
1843
2359
  className: "dsh-github-btn-sm",
1844
2360
  style: { padding: "3px 8px", fontSize: "11px", color: "var(--dsw-alias-state-error-primary, #cf222e)" },
1845
2361
  onClick: handleClear,
1846
- }, "🗑️ 清空缓存") : null), clearTip ? h("div", { style: { color: "var(--dsw-alias-state-success, #2ea043)", marginTop: "4px", fontWeight: 500 } }, clearTip) : null),
1847
- // 操作按钮
1848
- h("div", { style: { display: "flex", justifyContent: "flex-end", gap: "8px" } }, h("button", {
2362
+ }, "🗑️ 清空缓存") : null), clearTip ? h("div", { style: { color: "var(--dsw-alias-state-success, #2ea043)", marginTop: "4px", fontWeight: 500 } }, clearTip) : null)) : activeSection === "update" ? h("div", null,
2363
+ // ⬆️ 更新设置分区:选择版本检测的 npm 源(或关闭自动检查)。
2364
+ // 检测与升级流量均遵循「🌐 网络代理」分区设置——代理是全局的。
2365
+ h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "选择插件新版本检测所用的 npm 源。检测与升级流量均遵循「🌐 网络代理」分区设置:启用 http/https 代理时自动经代理访问;socks5 代理下 npm 检测为直连(gh 与升级子进程链路仍走代理)。无论选择哪个源,安装与升级流程本身不受影响。"), h("div", { style: { marginBottom: "16px" } }, h("div", { style: { fontSize: "13px", fontWeight: 600, marginBottom: "8px" } }, "更新源"),
2366
+ // 源 A: npm 官方(默认)
2367
+ h("div", {
2368
+ className: "dsh-github-radio-card" + (updateMode === "official" ? " active" : ""),
2369
+ onClick: function () { setUpdateMode("official"); },
2370
+ }, h("input", { type: "radio", className: "dsh-github-radio", checked: updateMode === "official", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "🌍 npm 官方源 (推荐)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "版本全球首发最及时,刚发布的新版立即可见;国内直连可能较慢。"))),
2371
+ // 源 B: 国内镜像
2372
+ h("div", {
2373
+ className: "dsh-github-radio-card" + (updateMode === "mirror" ? " active" : ""),
2374
+ onClick: function () { setUpdateMode("mirror"); },
2375
+ }, h("input", { type: "radio", className: "dsh-github-radio", checked: updateMode === "mirror", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "🇨🇳 国内镜像 (npmmirror)"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "阿里云镜像,国内访问快;同步存在分钟级延迟,刚发布的新版可能暂时查不到。"))),
2376
+ // 源 C: 不检查更新
2377
+ h("div", {
2378
+ className: "dsh-github-radio-card" + (updateMode === "off" ? " active" : ""),
2379
+ onClick: function () { setUpdateMode("off"); },
2380
+ }, h("input", { type: "radio", className: "dsh-github-radio", checked: updateMode === "off", readOnly: true }), h("div", null, h("div", { style: { fontWeight: 600, fontSize: "13px" } }, "⛔ 不检查更新"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "关闭自动检查与新版提示横幅;/gh upgrade 命令与一键升级仍然可用。")))),
2381
+ // 保存按钮 + 结果反馈(按钮复用 star-btn 的双主题链接色样式:内联色
2382
+ // 会覆盖 btn-sm 暗色规则,且暗色下亮蓝底刺眼——与关于页修复同源)
2383
+ h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" } }, h("button", {
2384
+ className: "dsh-github-btn-sm dsh-github-primary-btn",
2385
+ disabled: updateBusy,
2386
+ onClick: handleUpdateSave,
2387
+ }, updateBusy ? "验证中..." : "💾 保存并全局应用"), updateMsg ? h("span", {
2388
+ style: {
2389
+ fontSize: "12px",
2390
+ lineHeight: "1.5",
2391
+ color: updateMsg.kind === "ok"
2392
+ ? "var(--dsw-alias-state-success, #2ea043)"
2393
+ : updateMsg.kind === "err"
2394
+ ? "var(--dsw-alias-state-error-primary, #cf222e)"
2395
+ : "var(--dsw-alias-label-secondary, #656d76)",
2396
+ },
2397
+ }, updateMsg.text) : null)) : activeSection === "about" ? h("div", null,
2398
+ // ℹ️ 关于分区:项目信息/仓库地址/开源协议/求 Star。纯静态展示,零状态零请求。
2399
+ h("div", { style: { display: "flex", alignItems: "center", gap: "10px", marginBottom: "14px" } }, renderOctocatBadge(40), h("div", null, h("div", { style: { fontSize: "15px", fontWeight: 600 } }, "DSH GitHub Flow"), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, "当前版本: v" + (aboutVersion || "…")))), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.6" } }, "让 DeepSeek Harness Agent 拥有操作 GitHub 的原生手脚。原生复用宿主机 gh CLI 安全凭据,深度打通本地 Git 工作区与云端协作,提供 9 大领域 Agent 工具、人类 Slash 指令、Web 全景驾驶舱与一键跨平台升级。"),
2400
+ // 信息条目
2401
+ h("div", { style: { display: "flex", flexDirection: "column", gap: "8px", marginBottom: "14px" } }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "仓库地址"), h("a", { href: "https://github.com/ItQianChen/dsh-github-flow", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "github.com/ItQianChen/dsh-github-flow")), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "npm 包"), h("a", { href: "https://www.npmjs.com/package/dsh-github-flow", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "npmjs.com/package/dsh-github-flow")), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", fontSize: "12px" } }, h("span", { style: { minWidth: "64px", color: "var(--dsw-alias-label-secondary, #656d76)" } }, "开源协议"), h("a", { href: "https://github.com/ItQianChen/dsh-github-flow/blob/master/LICENSE", target: "_blank", rel: "noopener noreferrer", style: { color: "var(--dsw-alias-link, #0969da)" } }, "MIT License"))),
2402
+ // 求 Star 卡片
2403
+ // 主题兼容:不用内联色——暗色主题的覆盖只能作用于 class 选择器;
2404
+ // 且此前的 canvas-subtle/border-muted 并非宿主真实令牌(全项目无他处使用),
2405
+ // 永远回落亮色值导致暗色下一块刺眼亮底。改用 cache-info-box 同款双主题 class。
2406
+ h("div", {
2407
+ className: "dsh-github-about-star-box",
2408
+ style: {
2409
+ display: "flex",
2410
+ alignItems: "center",
2411
+ gap: "10px",
2412
+ },
2413
+ }, h("span", { style: { fontSize: "18px" } }, "⭐"), h("div", { style: { flex: 1, fontSize: "12px", lineHeight: "1.5" } }, "如果这个插件对你的日常研发有帮助,欢迎到 GitHub 仓库点个 Star 支持一下——你的 Star 是持续迭代的动力!也欢迎提交 Issue 与 PR。"), h("a", {
2414
+ href: "https://github.com/ItQianChen/dsh-github-flow",
2415
+ target: "_blank",
2416
+ rel: "noopener noreferrer",
2417
+ className: "dsh-github-btn-sm dsh-github-primary-btn",
2418
+ }, "🌟 去点 Star"))) : h("div", null,
2419
+ // 🌐 网络代理分区:设置保存即全局生效(驾驶舱/右侧栏/Agent 工具/升级链路),
2420
+ // 未启用时回落系统环境代理或直连(「默认走系统设置」语义)。
2421
+ h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginBottom: "14px", lineHeight: "1.5" } }, "设置后插件的 GitHub 数据链路(本页面、右侧栏、Agent 工具调用、版本升级)全部经代理访问;未启用时默认使用系统环境代理或直连。保存后立即生效,无需重启。"), h("label", { style: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "10px", cursor: "pointer", fontSize: "13px" } }, h("input", {
2422
+ type: "checkbox",
2423
+ checked: proxyEnabled,
2424
+ onChange: function (e) { setProxyEnabled(Boolean(e.target.checked)); },
2425
+ }), "启用代理"), proxyEnabled ? h("div", null, h("div", { style: { fontSize: "12px", marginBottom: "4px" } }, "代理地址"), h("input", {
2426
+ className: "dsh-github-input",
2427
+ style: { width: "100%", boxSizing: "border-box", padding: "6px 10px", fontSize: "13px", marginBottom: "10px" },
2428
+ placeholder: "http://127.0.0.1:7890 或 socks5h://127.0.0.1:1080(DNS 由代理解析)",
2429
+ value: proxyUrl,
2430
+ onChange: function (e) { setProxyUrl(e.target.value); },
2431
+ }), h("div", { style: { fontSize: "12px", marginBottom: "4px" } }, "NO_PROXY 排除列表(逗号分隔,localhost 与 127.0.0.1 强制包含)"), h("input", {
2432
+ className: "dsh-github-input",
2433
+ style: { width: "100%", boxSizing: "border-box", padding: "6px 10px", fontSize: "12px", marginBottom: "10px" },
2434
+ value: proxyNoProxy,
2435
+ onChange: function (e) { setProxyNoProxy(e.target.value); },
2436
+ }), h("label", { style: { display: "flex", alignItems: "center", gap: "8px", marginBottom: "8px", cursor: "pointer", fontSize: "12px" } }, h("input", {
2437
+ type: "checkbox",
2438
+ checked: proxyApplyNpm,
2439
+ onChange: function (e) { setProxyApplyNpm(Boolean(e.target.checked)); },
2440
+ }), "同时应用于 npm 版本探测与一键升级")) : null,
2441
+ // 操作与反馈行:探测/保存按钮 + 结果胶囊
2442
+ h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexWrap: "wrap" } }, h("button", {
1849
2443
  className: "dsh-github-btn-sm",
1850
- style: { padding: "6px 14px" },
1851
- onClick: props.onClose,
1852
- }, "取消"), h("button", {
2444
+ style: { padding: "5px 12px", fontSize: "12px" },
2445
+ disabled: proxyBusy || !proxyEnabled,
2446
+ onClick: handleProxyProbe,
2447
+ title: "经当前配置探测 GitHub API 连通性(约 2.5 秒)",
2448
+ }, "🧪 测试连通性"), h("button", {
2449
+ className: "dsh-github-btn-sm",
2450
+ style: {
2451
+ padding: "5px 14px",
2452
+ fontSize: "12px",
2453
+ background: "var(--dsw-alias-link, #0969da)",
2454
+ color: "#ffffff",
2455
+ borderColor: "var(--dsw-alias-link, #0969da)",
2456
+ fontWeight: 600,
2457
+ },
2458
+ disabled: proxyBusy,
2459
+ onClick: handleProxySave,
2460
+ }, proxyBusy ? "处理中..." : "💾 保存并全局应用"), proxyProbe ? h("span", {
2461
+ style: {
2462
+ fontSize: "12px",
2463
+ fontWeight: 500,
2464
+ color: proxyProbe.ok ? "var(--dsw-alias-state-success, #2ea043)" : "var(--dsw-alias-state-error-primary, #cf222e)",
2465
+ },
2466
+ title: proxyProbe.error || "",
2467
+ }, proxyProbe.ok ? "✅ " + proxyProbe.latencyMs + "ms" : "❌ 探测失败") : null), proxyMsg ? h("div", {
2468
+ style: {
2469
+ marginTop: "8px",
2470
+ fontSize: "12px",
2471
+ lineHeight: "1.5",
2472
+ color: proxyMsg.kind === "ok"
2473
+ ? "var(--dsw-alias-state-success, #2ea043)"
2474
+ : proxyMsg.kind === "err"
2475
+ ? "var(--dsw-alias-state-error-primary, #cf222e)"
2476
+ : "var(--dsw-alias-label-secondary, #656d76)",
2477
+ },
2478
+ }, proxyMsg.text) : null))),
2479
+ // 底部操作条:sync 分区提供主保存动作;proxy/update 分区有自己的「保存并全局应用」,
2480
+ // 底部统一为「完成」关闭弹窗,避免多个保存按钮的语义冲突。
2481
+ h("div", { className: "dsh-github-modal-footer" }, activeSection === "sync" ? h("button", {
1853
2482
  className: "dsh-github-btn-sm",
1854
2483
  style: {
1855
2484
  padding: "6px 16px",
@@ -1859,7 +2488,11 @@ body[data-ds-dark-theme] .dsh-github-radio {
1859
2488
  fontWeight: 600,
1860
2489
  },
1861
2490
  onClick: handleSave,
1862
- }, "保存并应用"))));
2491
+ }, "保存同步设置") : null, h("button", {
2492
+ className: "dsh-github-btn-sm",
2493
+ style: { padding: "6px 14px" },
2494
+ onClick: props.onClose,
2495
+ }, activeSection === "sync" ? "取消" : "完成"))));
1863
2496
  }
1864
2497
  function GithubWorkspacePanel() {
1865
2498
  // 1. 读取本地偏好配置与历史持久化快照 (0ms 纯内存读取)
@@ -1870,16 +2503,35 @@ body[data-ds-dark-theme] .dsh-github-radio {
1870
2503
  var [loading, setLoading] = React.useState(!initialSnapshot);
1871
2504
  var [syncStatus, setSyncStatus] = React.useState(initialSnapshot ? "cached" : "loading");
1872
2505
  var [lastSyncedAt, setLastSyncedAt] = React.useState(initialSnapshot?.savedAt || 0);
2506
+ // 弹窗开闭 + 初始分区分离:插件徽标点击直开「关于」分区(initialSection=about),
2507
+ // 而齿轮按钮仍默认打开「数据同步」——同一弹窗两种入口语义。
1873
2508
  var [isSettingsModalOpen, setIsSettingsModalOpen] = React.useState(false);
2509
+ var [settingsInitialSection, setSettingsInitialSection] = React.useState("sync");
1874
2510
  var [error, setError] = React.useState(null);
1875
2511
  var [updateInfo, setUpdateInfo] = React.useState(null);
1876
2512
  var [activeTab, setActiveTab] = React.useState("workspaces"); // 'workspaces' | 'repos' | 'mywork'
1877
2513
  var [searchQuery, setSearchQuery] = React.useState("");
1878
2514
  var [issueFilter, setIssueFilter] = React.useState("all"); // 'all' | 'reported' | 'assigned' | 'created'
1879
- // gh 业务数据就绪后,闲时 1.5 秒异步静默检测升级,绝不影响首屏
2515
+ // 代理生效状态(标题栏 🌐 徽章数据源):挂载时读一次 + 弹窗关闭时刷新
2516
+ //(保存发生在弹窗内,关闭时机足以让徽章与最新配置对齐,无需轮询)
2517
+ var [proxyActive, setProxyActive] = React.useState(null);
2518
+ var refreshProxyBadge = React.useCallback(function () {
2519
+ fetch("/api/github/proxy")
2520
+ .then(function (r) { return r.json(); })
2521
+ .then(function (j) {
2522
+ if (j && j.ok && j.state && j.state.config) {
2523
+ setProxyActive({ enabled: Boolean(j.state.config.url), url: j.state.config.url || "" });
2524
+ }
2525
+ else {
2526
+ setProxyActive({ enabled: false, url: "" });
2527
+ }
2528
+ })
2529
+ .catch(function () { });
2530
+ }, []);
2531
+ React.useEffect(function () { refreshProxyBadge(); }, [refreshProxyBadge]);
2532
+ // 检查更新与 gh 业务数据零依赖,挂载即并行发起(与右侧栏同策略):
2533
+ // 并发由服务端 inFlightPromise + 24h 缓存合流去重,无请求风暴风险。
1880
2534
  React.useEffect(() => {
1881
- if (!data)
1882
- return;
1883
2535
  var timer = setTimeout(function () {
1884
2536
  fetch("/api/github/check-update")
1885
2537
  .then(function (res) { return res.json(); })
@@ -1889,9 +2541,31 @@ body[data-ds-dark-theme] .dsh-github-radio {
1889
2541
  }
1890
2542
  })
1891
2543
  .catch(function () { });
1892
- }, 1500);
2544
+ }, 300);
1893
2545
  return function () { clearTimeout(timer); };
1894
- }, [data]);
2546
+ }, []);
2547
+ // 插件徽标事件:标题上的「插件」徽标点击 → 直开设置弹窗的「关于」分区。
2548
+ // 可见性守卫:右侧栏与主页可能同挂载,只有自身容器可见的宿主响应(防双层弹窗)。
2549
+ var panelRef = React.useRef(null);
2550
+ React.useEffect(() => {
2551
+ var onOpenAbout = () => {
2552
+ var el = panelRef.current;
2553
+ if (!el)
2554
+ return;
2555
+ var rect = el.getBoundingClientRect();
2556
+ if (rect.width > 0 && rect.height > 0) {
2557
+ setSettingsInitialSection("about");
2558
+ setIsSettingsModalOpen(true);
2559
+ }
2560
+ };
2561
+ window.addEventListener("dsh-github-open-about", onOpenAbout);
2562
+ return () => window.removeEventListener("dsh-github-open-about", onOpenAbout);
2563
+ }, []);
2564
+ // 齿轮按钮:默认打开「数据同步」分区(既有语义不变)
2565
+ var openSettingsDefault = () => {
2566
+ setSettingsInitialSection("sync");
2567
+ setIsSettingsModalOpen(true);
2568
+ };
1895
2569
  var loadData = React.useCallback(async (forceRefresh = false) => {
1896
2570
  // 如果是纯手动模式,且非显式强制刷新,且本地已有旧数据,直接跳过后台拉取
1897
2571
  if (!forceRefresh && syncSettings.strategy === "manual" && data) {
@@ -1904,8 +2578,10 @@ body[data-ds-dark-theme] .dsh-github-radio {
1904
2578
  var timedOut = false;
1905
2579
  var timer = setTimeout(function () { timedOut = true; controller.abort(); }, 45000);
1906
2580
  try {
2581
+ // refresh 预置请求必须共享同一 signal:它一旦挂起(如系统代理劫持回环请求),
2582
+ // 后续 overview fetch 永远执行不到,loading 永不清除——无 gh 用户正是被此卡死。
1907
2583
  if (forceRefresh)
1908
- await fetch("/api/github/refresh", { method: "POST" }).catch(() => { });
2584
+ await fetch("/api/github/refresh", { method: "POST", signal: controller.signal }).catch(() => { });
1909
2585
  var res = await fetch("/api/github/global-overview", { signal: controller.signal });
1910
2586
  if (!res.ok) {
1911
2587
  var errText = await res.text().catch(() => "");
@@ -1948,7 +2624,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
1948
2624
  }
1949
2625
  else {
1950
2626
  if (timedOut || err.name === "AbortError") {
1951
- setError("获取超时(30 秒):本机 gh 命令响应过慢,通常是网络或工作区数量较多所致。可稍后重试,或减少同时打开的工作区。");
2627
+ setError("获取超时(45 秒):本机 gh 命令响应过慢或网络代理劫持了回环请求。可稍后重试,或在「⚙️ 设置 → 🌐 网络代理」确认代理已放行 127.0.0.1。");
1952
2628
  }
1953
2629
  else {
1954
2630
  setError("无法连接后端服务:" + (err.message || err));
@@ -1960,7 +2636,9 @@ body[data-ds-dark-theme] .dsh-github-radio {
1960
2636
  setLoading(false);
1961
2637
  }
1962
2638
  }, [data, syncSettings]);
1963
- // 挂载加载调度:SWR 模式下初次延迟 300ms 后台平滑发起静默同步
2639
+ // 挂载加载调度:SWR 模式下初次延迟 300ms 后台平滑发起静默同步。
2640
+ // 无论本地是否有旧快照都拉:昨天的持久化快照在挂载后 300ms 内自愈,
2641
+ // 「打开驾驶舱即新鲜」由 SWR 定义本身保证,而非依赖用户停留或手动刷新。
1964
2642
  React.useEffect(() => {
1965
2643
  if (syncSettings.strategy === "swr") {
1966
2644
  var timer = setTimeout(function () { loadData(false); }, 300);
@@ -1970,15 +2648,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
1970
2648
  loadData(false);
1971
2649
  }
1972
2650
  }, [syncSettings.strategy]);
1973
- // 定时轮询调度
1974
- React.useEffect(() => {
1975
- if (syncSettings.strategy !== "interval" || !syncSettings.intervalMs || syncSettings.intervalMs <= 0)
1976
- return;
1977
- var timer = setInterval(function () {
1978
- loadData(false);
1979
- }, syncSettings.intervalMs);
1980
- return function () { clearInterval(timer); };
1981
- }, [syncSettings.strategy, syncSettings.intervalMs, loadData]);
2651
+ // (原定时轮询调度已随 'interval' 策略一并移除)
1982
2652
  // 窗口切回自动探测自愈:优先轻量快速探针,检测到已登录立即自愈
1983
2653
  React.useEffect(() => {
1984
2654
  var onFocus = () => {
@@ -1998,18 +2668,37 @@ body[data-ds-dark-theme] .dsh-github-radio {
1998
2668
  return () => window.removeEventListener("focus", onFocus);
1999
2669
  }, [data, loadData]);
2000
2670
  var h = React.createElement;
2001
- var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱"), h(SyncStatusBadge, { syncStatus, lastSyncedAt })), h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, h("button", {
2671
+ var headerEl = h("div", { className: "dsh-github-header" }, h("div", { className: "dsh-github-title" }, renderOctocatBadge(32), h("h2", null, "GitHub 全景驾驶舱"), renderPluginBadge(h), h(SyncStatusBadge, { syncStatus, lastSyncedAt }),
2672
+ // 代理生效徽章:启用时可见,hover 展示代理地址,让用户随时确认「当前确实在走代理」
2673
+ proxyActive && proxyActive.enabled
2674
+ ? h("span", {
2675
+ style: {
2676
+ display: "inline-flex",
2677
+ alignItems: "center",
2678
+ gap: "4px",
2679
+ fontSize: "11px",
2680
+ fontWeight: 500,
2681
+ padding: "2px 8px",
2682
+ borderRadius: "999px",
2683
+ background: "rgba(9, 105, 218, 0.1)",
2684
+ border: "1px solid rgba(9, 105, 218, 0.35)",
2685
+ color: "var(--dsw-alias-link, #0969da)",
2686
+ cursor: "default",
2687
+ },
2688
+ title: "插件网络代理已启用: " + proxyActive.url + "(GitHub 数据链路与工具调用经此代理;点击 ⚙️ 可修改)",
2689
+ }, "🌐 代理")
2690
+ : null), h("div", { style: { display: "flex", alignItems: "center", gap: "8px" } }, h("button", {
2002
2691
  className: "dsh-github-refresh-btn",
2003
2692
  onClick: () => loadData(true),
2004
2693
  disabled: loading,
2005
2694
  }, loading ? "同步中..." : "🔄 刷新全局数据"), h("button", {
2006
2695
  className: "dsh-github-btn-sm",
2007
- onClick: () => setIsSettingsModalOpen(true),
2008
- title: "配置数据拉取节奏与本地缓存偏好",
2696
+ onClick: openSettingsDefault,
2697
+ title: "配置数据同步节奏、本地缓存、网络代理与插件信息",
2009
2698
  style: { padding: "6px 12px" },
2010
- }, "⚙️ 同步设置")));
2699
+ }, "⚙️ 设置")));
2011
2700
  if (loading && !data) {
2012
- return h("div", { className: "dsh-github-panel" }, headerEl, h("div", { className: "dsh-github-empty" }, "正在加载全局 GitHub 账号与工作区数据..."));
2701
+ return h("div", { className: "dsh-github-panel", ref: panelRef }, headerEl, h("div", { className: "dsh-github-empty" }, "正在加载全局 GitHub 账号与工作区数据..."));
2013
2702
  }
2014
2703
  var effectiveUpdateInfo = updateInfo || data?.updateInfo;
2015
2704
  var upgradeBannerEl = effectiveUpdateInfo && effectiveUpdateInfo.hasUpdate
@@ -2017,7 +2706,12 @@ body[data-ds-dark-theme] .dsh-github-radio {
2017
2706
  : null;
2018
2707
  var settingsModalEl = h(GlobalSyncSettingsModal, {
2019
2708
  isOpen: isSettingsModalOpen,
2020
- onClose: () => setIsSettingsModalOpen(false),
2709
+ initialSection: settingsInitialSection,
2710
+ onClose: () => {
2711
+ setIsSettingsModalOpen(false);
2712
+ // 弹窗内可能刚保存过代理配置,关闭时同步刷新标题栏徽章
2713
+ refreshProxyBadge();
2714
+ },
2021
2715
  currentSettings: syncSettings,
2022
2716
  onSave: (newSettings) => {
2023
2717
  setSyncSettings(newSettings);
@@ -2030,7 +2724,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
2030
2724
  });
2031
2725
  // 错误拦截阻断:若无本地数据且请求失败,直接呈现错误卡片,杜绝把接口异常伪装成「未安装 CLI」
2032
2726
  if (!data && error) {
2033
- return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, h("div", {
2727
+ return h("div", { className: "dsh-github-panel", ref: panelRef }, headerEl, upgradeBannerEl, h("div", {
2034
2728
  style: {
2035
2729
  padding: "24px",
2036
2730
  borderRadius: "8px",
@@ -2140,7 +2834,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
2140
2834
  })));
2141
2835
  }
2142
2836
  }
2143
- return h("div", { className: "dsh-github-panel" }, headerEl, upgradeBannerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl, settingsModalEl);
2837
+ return h("div", { className: "dsh-github-panel", ref: panelRef }, headerEl, upgradeBannerEl, errorEl, statsCardsEl, authGuideEl, tabsEl, contentEl, settingsModalEl);
2144
2838
  }
2145
2839
  var inject = ["slots", "sidebarRightTabs", "sessions", "layout", "theme"];
2146
2840
  function apply(ctx) {
@@ -2172,6 +2866,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
2172
2866
  id: "dsh-github-flow",
2173
2867
  kind: "github",
2174
2868
  priority: "plugin",
2869
+ // 标题保持简洁,身份由面板内徽标标注(Tab 体系不支持内嵌徽标元素)
2175
2870
  title: () => "GitHub 仓库",
2176
2871
  guide: [
2177
2872
  {
@@ -2179,7 +2874,7 @@ body[data-ds-dark-theme] .dsh-github-radio {
2179
2874
  commandId: "workspace.github",
2180
2875
  order: 40,
2181
2876
  title: () => "GitHub 仓库",
2182
- description: () => "查看与操作当前工作区目录下的 GitHub 仓库",
2877
+ description: () => "查看与操作当前工作区目录下的 GitHub 仓库(由 dsh-github-flow 插件提供)",
2183
2878
  icon: GithubArtwork,
2184
2879
  },
2185
2880
  ],