zen-gitsync 2.16.7 → 2.16.8

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 (74) hide show
  1. package/package.json +1 -1
  2. package/src/cli/ai/modelSetup.js +594 -594
  3. package/src/cli/ai/modelSetup.test.js +496 -496
  4. package/src/cli/ai/platformGuard.js +200 -200
  5. package/src/cli/ai/platformGuard.test.js +253 -253
  6. package/src/gitCommit.js +484 -484
  7. package/src/ui/public/assets/AgentView-DSmUynN-.js +4 -0
  8. package/src/ui/public/assets/AppVersionBadge-CcFagCnq.js +2 -0
  9. package/src/ui/public/assets/BranchSelector-DcYVBQJh.js +1 -0
  10. package/src/ui/public/assets/CommitForm-CkTI8Xyz.js +6 -0
  11. package/src/ui/public/assets/CommonDialog-BFyhw0Qj.js +1 -0
  12. package/src/ui/public/assets/EditorView-DuHkBSc3.js +0 -0
  13. package/src/ui/public/assets/{FlowExecutionViewer-DjtFCci0.js → FlowExecutionViewer-C063Qjmk.js} +1 -1
  14. package/src/ui/public/assets/FlowOrchestrationWorkspace-BeJRId5G.js +21 -0
  15. package/src/ui/public/assets/LogList-GSeER4cv.js +15 -0
  16. package/src/ui/public/assets/MindmapView-p7szLCtQ.js +2 -0
  17. package/src/ui/public/assets/MonitorView-Be6KEUCe.js +1 -0
  18. package/src/ui/public/assets/ProjectStartupButton-F1k5Km2p.js +1 -0
  19. package/src/ui/public/assets/{RemoteRepoCard-Yxfc0pxB.js → RemoteRepoCard-CSMFlWDl.js} +2 -2
  20. package/src/ui/public/assets/SourceMapView-Dg5NP_gR.js +3 -0
  21. package/src/ui/public/assets/{SvgIcon-32uxY152.js → SvgIcon-CYZGHQAE.js} +1 -1
  22. package/src/ui/public/assets/UserInputNode-3OAXiGVh.js +1 -0
  23. package/src/ui/public/assets/WorkbenchView-8xyxdWAg.js +28 -0
  24. package/src/ui/public/assets/{_plugin-vue_export-helper-Dvk21zJS.js → _plugin-vue_export-helper-DgLDQTWK.js} +1 -1
  25. package/src/ui/public/assets/{configStore-Bmoxm5fk.js → configStore-D5t05s4G.js} +1 -1
  26. package/src/ui/public/assets/element-plus-CyGhXd7X.js +25 -0
  27. package/src/ui/public/assets/element-plus-D3Dn7nX0.css +1 -0
  28. package/src/ui/public/assets/{flow-mindmap-E8TqjpXN.js → flow-mindmap-CUvJaYn2.js} +1 -1
  29. package/src/ui/public/assets/index-DDGvh1mp.js +68 -0
  30. package/src/ui/public/assets/{socket-io-Bcae2P7B.js → socket-io-BSgn8Jx2.js} +1 -1
  31. package/src/ui/public/assets/vendor-DVvq7qtS.css +1 -0
  32. package/src/ui/public/assets/{vendor-D79HTB0H.js → vendor-jF--I87-.js} +85 -41
  33. package/src/ui/public/assets/{vue-flow-BYcesAAH.js → vue-flow-CWmfpfPN.js} +1 -1
  34. package/src/ui/public/favicon.svg +75 -75
  35. package/src/ui/public/index.html +28 -28
  36. package/src/ui/public/logo.svg +74 -74
  37. package/src/ui/server/middleware/requestLogger.js +53 -53
  38. package/src/ui/server/routes/branchStatus.js +105 -105
  39. package/src/ui/server/routes/fileOpen.js +3 -2
  40. package/src/ui/server/routes/fs.js +791 -791
  41. package/src/ui/server/routes/git/diff.js +426 -426
  42. package/src/ui/server/routes/git/diffUtils.js +149 -149
  43. package/src/ui/server/routes/git/stash.js +550 -550
  44. package/src/ui/server/routes/git/tags.js +176 -176
  45. package/src/ui/server/routes/git.js +181 -181
  46. package/src/ui/server/routes/gitOps.js +1255 -1255
  47. package/src/ui/server/routes/process.js +86 -86
  48. package/src/ui/server/routes/status.js +67 -67
  49. package/src/ui/server/routes/workbench/pdfText.js +145 -145
  50. package/src/ui/server/utils/createSavePortToFile.js +48 -48
  51. package/src/ui/server/utils/instanceRegistry.js +282 -282
  52. package/src/ui/server/utils/startServerOnAvailablePort.js +116 -116
  53. package/src/utils/index.js +1173 -1173
  54. package/src/ui/public/assets/AgentView-DEUMkOst.js +0 -4
  55. package/src/ui/public/assets/AppVersionBadge-oo55wrtg.js +0 -2
  56. package/src/ui/public/assets/BranchSelector--__REaeG.js +0 -1
  57. package/src/ui/public/assets/CommitForm-CpUP5zax.js +0 -6
  58. package/src/ui/public/assets/CommonDialog-DbPF7rKY.js +0 -1
  59. package/src/ui/public/assets/EditorView-LZ_WHmfb.js +0 -0
  60. package/src/ui/public/assets/FlowOrchestrationWorkspace-CKMLLhKM.js +0 -21
  61. package/src/ui/public/assets/LogList--c3ACWbL.js +0 -15
  62. package/src/ui/public/assets/MindmapView-B_VWP6CS.js +0 -2
  63. package/src/ui/public/assets/MonitorView-C9BEPazj.js +0 -1
  64. package/src/ui/public/assets/ProjectStartupButton-qLmpoh5m.js +0 -1
  65. package/src/ui/public/assets/SourceMapView-BXqR3S2Y.js +0 -3
  66. package/src/ui/public/assets/UserInputNode-B0xDrmk2.js +0 -1
  67. package/src/ui/public/assets/WorkbenchView-COtdz_io.js +0 -28
  68. package/src/ui/public/assets/ai-chat-ui-BsH9JBEz.js +0 -45
  69. package/src/ui/public/assets/ai-chat-ui-BwlJovCq.css +0 -1
  70. package/src/ui/public/assets/element-plus-CQrjg9us.css +0 -1
  71. package/src/ui/public/assets/element-plus-C_2iiXUS.js +0 -32
  72. package/src/ui/public/assets/index-Zn4T8oXe.js +0 -68
  73. package/src/ui/public/assets/vendor-CdOLCu7-.css +0 -1
  74. package/src/ui/server/.claude/codediff.txt +0 -8
@@ -1,75 +1,75 @@
1
- <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
2
- <defs>
3
- <radialGradient id="bgGrad" cx="38%" cy="32%" r="70%">
4
- <stop offset="0%" stop-color="#1e1b4b"/>
5
- <stop offset="100%" stop-color="#0d0b1e"/>
6
- </radialGradient>
7
- <linearGradient id="mainGrad" x1="0%" y1="0%" x2="0%" y2="100%">
8
- <stop offset="0%" stop-color="#c4b5fd"/>
9
- <stop offset="100%" stop-color="#7c3aed"/>
10
- </linearGradient>
11
- <linearGradient id="featGrad" x1="0%" y1="0%" x2="100%" y2="100%">
12
- <stop offset="0%" stop-color="#7dd3fc"/>
13
- <stop offset="100%" stop-color="#0284c7"/>
14
- </linearGradient>
15
- <filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
16
- <feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="blur"/>
17
- <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
18
- </filter>
19
- <filter id="softGlow" x="-20%" y="-20%" width="140%" height="140%">
20
- <feGaussianBlur in="SourceGraphic" stdDeviation="1.5" result="blur"/>
21
- <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
22
- </filter>
23
- </defs>
24
-
25
- <!-- 背景�?-->
26
- <circle cx="100" cy="100" r="96" fill="url(#bgGrad)"/>
27
-
28
- <!-- 同步外弧(上半,代表 sync 循环�?-->
29
- <path d="M 100,20 A 80,80 0 1,1 36,153"
30
- stroke="rgba(167,139,250,0.18)" stroke-width="4.5" fill="none" stroke-linecap="round"/>
31
- <!-- 同步外弧(下半) -->
32
- <path d="M 36,153 A 80,80 0 0,1 164,153"
33
- stroke="rgba(56,189,248,0.18)" stroke-width="4.5" fill="none" stroke-linecap="round"/>
34
- <!-- 同步箭头(上�?-->
35
- <path d="M 91,17 L 100,20 L 96,29"
36
- stroke="rgba(167,139,250,0.55)" stroke-width="2.5" fill="none"
37
- stroke-linecap="round" stroke-linejoin="round"/>
38
- <!-- 同步箭头(下�?-->
39
- <path d="M 163,145 L 164,154 L 155,151"
40
- stroke="rgba(56,189,248,0.55)" stroke-width="2.5" fill="none"
41
- stroke-linecap="round" stroke-linejoin="round"/>
42
-
43
- <!-- 主分支竖�?-->
44
- <line x1="76" y1="44" x2="76" y2="156"
45
- stroke="url(#mainGrad)" stroke-width="5.5" stroke-linecap="round"
46
- filter="url(#softGlow)"/>
47
-
48
- <!-- 特性分支曲线(分出�?-->
49
- <path d="M 76,80 C 98,80 130,96 130,120 L 130,140"
50
- stroke="url(#featGrad)" stroke-width="4.5" fill="none"
51
- stroke-linecap="round" filter="url(#softGlow)"/>
52
- <!-- 特性分支合并回主分�?-->
53
- <path d="M 130,140 C 130,156 105,159 76,155"
54
- stroke="url(#featGrad)" stroke-width="4.5" fill="none" stroke-linecap="round"/>
55
-
56
- <!-- 主分支节�?-->
57
- <circle cx="76" cy="48" r="11" fill="#13103a" stroke="#c4b5fd" stroke-width="3" filter="url(#glow)"/>
58
- <circle cx="76" cy="48" r="5" fill="#ddd6fe"/>
59
-
60
- <circle cx="76" cy="84" r="11" fill="#13103a" stroke="#a78bfa" stroke-width="3"/>
61
- <circle cx="76" cy="84" r="5" fill="#c4b5fd"/>
62
-
63
- <circle cx="76" cy="120" r="11" fill="#13103a" stroke="#8b5cf6" stroke-width="3"/>
64
- <circle cx="76" cy="120" r="5" fill="#a78bfa"/>
65
-
66
- <circle cx="76" cy="155" r="11" fill="#13103a" stroke="#7c3aed" stroke-width="3" filter="url(#glow)"/>
67
- <circle cx="76" cy="155" r="5" fill="#8b5cf6"/>
68
-
69
- <!-- 特性分支节�?-->
70
- <circle cx="130" cy="115" r="11" fill="#13103a" stroke="#38bdf8" stroke-width="3" filter="url(#glow)"/>
71
- <circle cx="130" cy="115" r="5" fill="#7dd3fc"/>
72
-
73
- <circle cx="130" cy="143" r="11" fill="#13103a" stroke="#0ea5e9" stroke-width="3" filter="url(#glow)"/>
74
- <circle cx="130" cy="143" r="5" fill="#38bdf8"/>
1
+ <svg width="200" height="200" viewBox="0 0 200 200" xmlns="http://www.w3.org/2000/svg">
2
+ <defs>
3
+ <radialGradient id="bgGrad" cx="38%" cy="32%" r="70%">
4
+ <stop offset="0%" stop-color="#1e1b4b"/>
5
+ <stop offset="100%" stop-color="#0d0b1e"/>
6
+ </radialGradient>
7
+ <linearGradient id="mainGrad" x1="0%" y1="0%" x2="0%" y2="100%">
8
+ <stop offset="0%" stop-color="#c4b5fd"/>
9
+ <stop offset="100%" stop-color="#7c3aed"/>
10
+ </linearGradient>
11
+ <linearGradient id="featGrad" x1="0%" y1="0%" x2="100%" y2="100%">
12
+ <stop offset="0%" stop-color="#7dd3fc"/>
13
+ <stop offset="100%" stop-color="#0284c7"/>
14
+ </linearGradient>
15
+ <filter id="glow" x="-40%" y="-40%" width="180%" height="180%">
16
+ <feGaussianBlur in="SourceGraphic" stdDeviation="2.5" result="blur"/>
17
+ <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
18
+ </filter>
19
+ <filter id="softGlow" x="-20%" y="-20%" width="140%" height="140%">
20
+ <feGaussianBlur in="SourceGraphic" stdDeviation="1.5" result="blur"/>
21
+ <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
22
+ </filter>
23
+ </defs>
24
+
25
+ <!-- 背景�?-->
26
+ <circle cx="100" cy="100" r="96" fill="url(#bgGrad)"/>
27
+
28
+ <!-- 同步外弧(上半,代表 sync 循环�?-->
29
+ <path d="M 100,20 A 80,80 0 1,1 36,153"
30
+ stroke="rgba(167,139,250,0.18)" stroke-width="4.5" fill="none" stroke-linecap="round"/>
31
+ <!-- 同步外弧(下半) -->
32
+ <path d="M 36,153 A 80,80 0 0,1 164,153"
33
+ stroke="rgba(56,189,248,0.18)" stroke-width="4.5" fill="none" stroke-linecap="round"/>
34
+ <!-- 同步箭头(上�?-->
35
+ <path d="M 91,17 L 100,20 L 96,29"
36
+ stroke="rgba(167,139,250,0.55)" stroke-width="2.5" fill="none"
37
+ stroke-linecap="round" stroke-linejoin="round"/>
38
+ <!-- 同步箭头(下�?-->
39
+ <path d="M 163,145 L 164,154 L 155,151"
40
+ stroke="rgba(56,189,248,0.55)" stroke-width="2.5" fill="none"
41
+ stroke-linecap="round" stroke-linejoin="round"/>
42
+
43
+ <!-- 主分支竖�?-->
44
+ <line x1="76" y1="44" x2="76" y2="156"
45
+ stroke="url(#mainGrad)" stroke-width="5.5" stroke-linecap="round"
46
+ filter="url(#softGlow)"/>
47
+
48
+ <!-- 特性分支曲线(分出�?-->
49
+ <path d="M 76,80 C 98,80 130,96 130,120 L 130,140"
50
+ stroke="url(#featGrad)" stroke-width="4.5" fill="none"
51
+ stroke-linecap="round" filter="url(#softGlow)"/>
52
+ <!-- 特性分支合并回主分�?-->
53
+ <path d="M 130,140 C 130,156 105,159 76,155"
54
+ stroke="url(#featGrad)" stroke-width="4.5" fill="none" stroke-linecap="round"/>
55
+
56
+ <!-- 主分支节�?-->
57
+ <circle cx="76" cy="48" r="11" fill="#13103a" stroke="#c4b5fd" stroke-width="3" filter="url(#glow)"/>
58
+ <circle cx="76" cy="48" r="5" fill="#ddd6fe"/>
59
+
60
+ <circle cx="76" cy="84" r="11" fill="#13103a" stroke="#a78bfa" stroke-width="3"/>
61
+ <circle cx="76" cy="84" r="5" fill="#c4b5fd"/>
62
+
63
+ <circle cx="76" cy="120" r="11" fill="#13103a" stroke="#8b5cf6" stroke-width="3"/>
64
+ <circle cx="76" cy="120" r="5" fill="#a78bfa"/>
65
+
66
+ <circle cx="76" cy="155" r="11" fill="#13103a" stroke="#7c3aed" stroke-width="3" filter="url(#glow)"/>
67
+ <circle cx="76" cy="155" r="5" fill="#8b5cf6"/>
68
+
69
+ <!-- 特性分支节�?-->
70
+ <circle cx="130" cy="115" r="11" fill="#13103a" stroke="#38bdf8" stroke-width="3" filter="url(#glow)"/>
71
+ <circle cx="130" cy="115" r="5" fill="#7dd3fc"/>
72
+
73
+ <circle cx="130" cy="143" r="11" fill="#13103a" stroke="#0ea5e9" stroke-width="3" filter="url(#glow)"/>
74
+ <circle cx="130" cy="143" r="5" fill="#38bdf8"/>
75
75
  </svg>
@@ -1,53 +1,53 @@
1
- // Copyright 2026 xz333221
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- //
15
- import logger from '../utils/logger.js'
16
-
17
- export function createRequestLogger({ chalk }) {
18
- return (req, res, next) => {
19
- const startTime = Date.now();
20
- const requestTime = new Date().toLocaleString('zh-CN', { hour12: false });
21
-
22
- res.on('finish', () => {
23
- const duration = Date.now() - startTime;
24
- const statusCode = res.statusCode;
25
-
26
- let statusColor = chalk.green;
27
- if (statusCode >= 400 && statusCode < 500) {
28
- statusColor = chalk.yellow;
29
- } else if (statusCode >= 500) {
30
- statusColor = chalk.red;
31
- }
32
-
33
- let durationColor = chalk.gray;
34
- if (duration > 1000) {
35
- durationColor = chalk.red;
36
- } else if (duration > 500) {
37
- durationColor = chalk.yellow;
38
- } else if (duration > 200) {
39
- durationColor = chalk.cyan;
40
- }
41
-
42
- logger.info(
43
- chalk.dim(`[${requestTime}]`),
44
- chalk.bold(req.method),
45
- req.url,
46
- statusColor(`[${statusCode}]`),
47
- durationColor(`${duration}ms`)
48
- );
49
- });
50
-
51
- next();
52
- };
53
- }
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ import logger from '../utils/logger.js'
16
+
17
+ export function createRequestLogger({ chalk }) {
18
+ return (req, res, next) => {
19
+ const startTime = Date.now();
20
+ const requestTime = new Date().toLocaleString('zh-CN', { hour12: false });
21
+
22
+ res.on('finish', () => {
23
+ const duration = Date.now() - startTime;
24
+ const statusCode = res.statusCode;
25
+
26
+ let statusColor = chalk.green;
27
+ if (statusCode >= 400 && statusCode < 500) {
28
+ statusColor = chalk.yellow;
29
+ } else if (statusCode >= 500) {
30
+ statusColor = chalk.red;
31
+ }
32
+
33
+ let durationColor = chalk.gray;
34
+ if (duration > 1000) {
35
+ durationColor = chalk.red;
36
+ } else if (duration > 500) {
37
+ durationColor = chalk.yellow;
38
+ } else if (duration > 200) {
39
+ durationColor = chalk.cyan;
40
+ }
41
+
42
+ logger.info(
43
+ chalk.dim(`[${requestTime}]`),
44
+ chalk.bold(req.method),
45
+ req.url,
46
+ statusColor(`[${statusCode}]`),
47
+ durationColor(`${duration}ms`)
48
+ );
49
+ });
50
+
51
+ next();
52
+ };
53
+ }
@@ -1,105 +1,105 @@
1
- // Copyright 2026 xz333221
2
- //
3
- // Licensed under the Apache License, Version 2.0 (the "License");
4
- // you may not use this file except in compliance with the License.
5
- // You may obtain a copy of the License at
6
- //
7
- // http://www.apache.org/licenses/LICENSE-2.0
8
- //
9
- // Unless required by applicable law or agreed to in writing, software
10
- // distributed under the License is distributed on an "AS IS" BASIS,
11
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
- // See the License for the specific language governing permissions and
13
- // limitations under the License.
14
- //
15
- import { asyncRoute, HttpError } from '../utils/asyncRoute.js'
16
-
17
- import logger from '../utils/logger.js'
18
-
19
- export function registerBranchStatusRoutes({
20
- app,
21
- execGitCommand,
22
- getIsGitRepo,
23
- getBranchStatusCache,
24
- setBranchStatusCache,
25
- getRecentPushStatus,
26
- setRecentPushStatus
27
- }) {
28
- // 获取当前分支 - 直接读取,不缓存(git symbolic-ref 极快,<5ms)
29
- app.get('/api/branch', asyncRoute(async (req, res) => {
30
- try {
31
- const { stdout } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
32
- res.json({ branch: stdout.trim() });
33
- } catch (error) {
34
- res.status(500).json({ error: error.message });
35
- }
36
- }));
37
-
38
- // 获取分支与远程的差异状态(领先/落后提交数)
39
- app.get('/api/branch-status', asyncRoute(async (req, res) => {
40
- try {
41
- if (!getIsGitRepo()) {
42
- return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
43
- }
44
-
45
- const now = Date.now();
46
- const forceRefresh = req.query.force === 'true';
47
-
48
- const recentPushStatus = getRecentPushStatus();
49
- const branchStatusCache = getBranchStatusCache();
50
-
51
- // push 后10秒内直接返回同步状态
52
- if (recentPushStatus.justPushed &&
53
- (now - recentPushStatus.pushTime) < recentPushStatus.validDuration) {
54
- logger.info('检测到最近推送过,直接返回同步状态');
55
- return res.json({
56
- hasUpstream: true,
57
- upstreamBranch: branchStatusCache.upstreamBranch || 'origin/main',
58
- ahead: 0,
59
- behind: 0
60
- });
61
- }
62
-
63
- // 5秒缓存分支名,防止短时间内重复执行 git symbolic-ref / git rev-parse
64
- const branchInfoCacheValid = !forceRefresh &&
65
- branchStatusCache.currentBranch &&
66
- branchStatusCache.upstreamBranch &&
67
- (now - branchStatusCache.lastUpdate) < branchStatusCache.cacheTimeout;
68
-
69
- let currentBranch, upstreamBranch;
70
-
71
- if (branchInfoCacheValid) {
72
- currentBranch = branchStatusCache.currentBranch;
73
- upstreamBranch = branchStatusCache.upstreamBranch;
74
- logger.info(`使用5秒缓存的分支名: ${currentBranch} -> ${upstreamBranch}`);
75
- } else {
76
- // 直接读取,不再走5分钟长缓存
77
- const { stdout: branchOut } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
78
- currentBranch = branchOut.trim();
79
-
80
- const { stdout: upstreamOut } = await execGitCommand(
81
- ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'],
82
- { ignoreError: true }
83
- );
84
- upstreamBranch = upstreamOut.trim() || null;
85
-
86
- if (!upstreamBranch) {
87
- setBranchStatusCache({ currentBranch: null, upstreamBranch: null, lastUpdate: 0, cacheTimeout: 5000 });
88
- return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
89
- }
90
-
91
- setBranchStatusCache({ currentBranch, upstreamBranch, lastUpdate: now, cacheTimeout: 5000 });
92
- }
93
-
94
- const { stdout: aheadBehindOutput } = await execGitCommand(
95
- ['rev-list', '--left-right', '--count', `${currentBranch}...${upstreamBranch}`]
96
- );
97
- const [ahead, behind] = aheadBehindOutput.trim().split('\t').map(Number);
98
-
99
- res.json({ hasUpstream: true, upstreamBranch, ahead, behind });
100
- } catch (error) {
101
- logger.error('获取分支状态失败:', error);
102
- res.status(500).json({ error: error.message });
103
- }
104
- }));
105
- }
1
+ // Copyright 2026 xz333221
2
+ //
3
+ // Licensed under the Apache License, Version 2.0 (the "License");
4
+ // you may not use this file except in compliance with the License.
5
+ // You may obtain a copy of the License at
6
+ //
7
+ // http://www.apache.org/licenses/LICENSE-2.0
8
+ //
9
+ // Unless required by applicable law or agreed to in writing, software
10
+ // distributed under the License is distributed on an "AS IS" BASIS,
11
+ // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ // See the License for the specific language governing permissions and
13
+ // limitations under the License.
14
+ //
15
+ import { asyncRoute, HttpError } from '../utils/asyncRoute.js'
16
+
17
+ import logger from '../utils/logger.js'
18
+
19
+ export function registerBranchStatusRoutes({
20
+ app,
21
+ execGitCommand,
22
+ getIsGitRepo,
23
+ getBranchStatusCache,
24
+ setBranchStatusCache,
25
+ getRecentPushStatus,
26
+ setRecentPushStatus
27
+ }) {
28
+ // 获取当前分支 - 直接读取,不缓存(git symbolic-ref 极快,<5ms)
29
+ app.get('/api/branch', asyncRoute(async (req, res) => {
30
+ try {
31
+ const { stdout } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
32
+ res.json({ branch: stdout.trim() });
33
+ } catch (error) {
34
+ res.status(500).json({ error: error.message });
35
+ }
36
+ }));
37
+
38
+ // 获取分支与远程的差异状态(领先/落后提交数)
39
+ app.get('/api/branch-status', asyncRoute(async (req, res) => {
40
+ try {
41
+ if (!getIsGitRepo()) {
42
+ return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
43
+ }
44
+
45
+ const now = Date.now();
46
+ const forceRefresh = req.query.force === 'true';
47
+
48
+ const recentPushStatus = getRecentPushStatus();
49
+ const branchStatusCache = getBranchStatusCache();
50
+
51
+ // push 后10秒内直接返回同步状态
52
+ if (recentPushStatus.justPushed &&
53
+ (now - recentPushStatus.pushTime) < recentPushStatus.validDuration) {
54
+ logger.info('检测到最近推送过,直接返回同步状态');
55
+ return res.json({
56
+ hasUpstream: true,
57
+ upstreamBranch: branchStatusCache.upstreamBranch || 'origin/main',
58
+ ahead: 0,
59
+ behind: 0
60
+ });
61
+ }
62
+
63
+ // 5秒缓存分支名,防止短时间内重复执行 git symbolic-ref / git rev-parse
64
+ const branchInfoCacheValid = !forceRefresh &&
65
+ branchStatusCache.currentBranch &&
66
+ branchStatusCache.upstreamBranch &&
67
+ (now - branchStatusCache.lastUpdate) < branchStatusCache.cacheTimeout;
68
+
69
+ let currentBranch, upstreamBranch;
70
+
71
+ if (branchInfoCacheValid) {
72
+ currentBranch = branchStatusCache.currentBranch;
73
+ upstreamBranch = branchStatusCache.upstreamBranch;
74
+ logger.info(`使用5秒缓存的分支名: ${currentBranch} -> ${upstreamBranch}`);
75
+ } else {
76
+ // 直接读取,不再走5分钟长缓存
77
+ const { stdout: branchOut } = await execGitCommand(['symbolic-ref', '--short', 'HEAD']);
78
+ currentBranch = branchOut.trim();
79
+
80
+ const { stdout: upstreamOut } = await execGitCommand(
81
+ ['rev-parse', '--abbrev-ref', '--symbolic-full-name', '@{u}'],
82
+ { ignoreError: true }
83
+ );
84
+ upstreamBranch = upstreamOut.trim() || null;
85
+
86
+ if (!upstreamBranch) {
87
+ setBranchStatusCache({ currentBranch: null, upstreamBranch: null, lastUpdate: 0, cacheTimeout: 5000 });
88
+ return res.json({ hasUpstream: false, ahead: 0, behind: 0 });
89
+ }
90
+
91
+ setBranchStatusCache({ currentBranch, upstreamBranch, lastUpdate: now, cacheTimeout: 5000 });
92
+ }
93
+
94
+ const { stdout: aheadBehindOutput } = await execGitCommand(
95
+ ['rev-list', '--left-right', '--count', `${currentBranch}...${upstreamBranch}`]
96
+ );
97
+ const [ahead, behind] = aheadBehindOutput.trim().split('\t').map(Number);
98
+
99
+ res.json({ hasUpstream: true, upstreamBranch, ahead, behind });
100
+ } catch (error) {
101
+ logger.error('获取分支状态失败:', error);
102
+ res.status(500).json({ error: error.message });
103
+ }
104
+ }));
105
+ }
@@ -407,7 +407,8 @@ export function registerFileOpenRoutes({
407
407
 
408
408
  // 检测本地工具是否已安装(供前端根据结果决定是否显示对应按钮)
409
409
  // 检测方式: spawn 'tool --version',exit 0 即视为已安装
410
- // 超时 3s 避免某个工具卡在 PATH 解析上时整个接口挂起
410
+ // 超时 15s:Claude CLI / opencode 等 Node 包装的工具首次冷启动
411
+ // 在 Windows 上经常 5-7s(磁盘 cache + Defender 扫描),3s 永远命中超时。
411
412
  app.get('/api/check-tools', asyncRoute(async (req, res) => {
412
413
  const checkCmd = (cmd) => new Promise((resolve) => {
413
414
  const child = spawn(cmd, ['--version'], {
@@ -424,7 +425,7 @@ export function registerFileOpenRoutes({
424
425
  };
425
426
  child.on('error', () => finish(false));
426
427
  child.on('exit', (code) => finish(code === 0));
427
- setTimeout(() => finish(false), 3000);
428
+ setTimeout(() => finish(false), 15000);
428
429
  });
429
430
 
430
431
  const [vscode, claude, codex, opencode] = await Promise.all([