dsh-github-flow 0.4.0 → 0.6.0
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/README.en.md +91 -12
- package/README.md +91 -12
- package/docs/UPGRADE-NOTES.md +9 -12
- package/docs/architecture.md +63 -29
- package/docs/images/1.png +0 -0
- package/lib/api/routes-actions.js +268 -5
- package/lib/api/routes-overview.js +147 -29
- package/lib/cache.d.ts +77 -4
- package/lib/cache.js +114 -7
- package/lib/client.d.ts +9 -4
- package/lib/client.js +5293 -942
- package/lib/commands/gh.d.ts +6 -1
- package/lib/commands/gh.js +144 -71
- package/lib/executor/error-classifier.d.ts +55 -0
- package/lib/executor/error-classifier.js +199 -0
- package/lib/executor/git-parser.d.ts +157 -1
- package/lib/executor/git-parser.js +513 -4
- package/lib/executor.d.ts +147 -4
- package/lib/executor.js +866 -77
- package/lib/main.js +28 -4
- package/lib/renderers/agent-task.d.ts +16 -0
- package/lib/renderers/agent-task.js +107 -0
- package/lib/renderers/codespace.d.ts +16 -0
- package/lib/renderers/codespace.js +103 -0
- package/lib/renderers/discussion.d.ts +16 -0
- package/lib/renderers/discussion.js +101 -0
- package/lib/renderers/markdown.d.ts +4 -0
- package/lib/renderers/markdown.js +4 -0
- package/lib/renderers/project.d.ts +20 -0
- package/lib/renderers/project.js +128 -0
- package/lib/renderers/skill.d.ts +30 -0
- package/lib/renderers/skill.js +180 -0
- package/lib/renderers/status.d.ts +5 -0
- package/lib/renderers/status.js +52 -0
- package/lib/telemetry.d.ts +70 -0
- package/lib/telemetry.js +161 -0
- package/lib/tools/agent-task.d.ts +54 -0
- package/lib/tools/agent-task.js +135 -0
- package/lib/tools/codespace.d.ts +51 -0
- package/lib/tools/codespace.js +154 -0
- package/lib/tools/discussion.d.ts +70 -0
- package/lib/tools/discussion.js +195 -0
- package/lib/tools/help.js +129 -14
- package/lib/tools/image-utils.d.ts +27 -0
- package/lib/tools/image-utils.js +73 -3
- package/lib/tools/project.d.ts +80 -0
- package/lib/tools/project.js +241 -0
- package/lib/tools/repo.js +13 -1
- package/lib/tools/skill.d.ts +24 -1
- package/lib/tools/skill.js +115 -5
- package/lib/tools/status.d.ts +3 -2
- package/lib/tools/status.js +9 -4
- package/lib/types.d.ts +336 -0
- package/lib/ui/commit-modal.js +144 -14
- package/lib/ui/help-modal.d.ts +21 -0
- package/lib/ui/help-modal.js +300 -0
- package/lib/ui/issue-modal.d.ts +14 -0
- package/lib/ui/issue-modal.js +366 -0
- package/lib/ui/markdown-lite.d.ts +66 -0
- package/lib/ui/markdown-lite.js +312 -0
- package/lib/ui/network-banner.d.ts +12 -0
- package/lib/ui/network-banner.js +35 -0
- package/lib/ui/pr-modal.d.ts +17 -0
- package/lib/ui/pr-modal.js +516 -0
- package/lib/ui/repo-badges.d.ts +43 -0
- package/lib/ui/repo-badges.js +82 -0
- package/lib/ui/repo-selector.d.ts +14 -0
- package/lib/ui/repo-selector.js +112 -0
- package/lib/ui/storage.d.ts +56 -0
- package/lib/ui/storage.js +163 -0
- package/lib/ui/styles.d.ts +1 -1
- package/lib/ui/styles.js +1219 -0
- package/lib/ui/sync-settings-modal.js +1 -1
- package/lib/ui/upgrade-card.js +39 -14
- package/lib/ui/utils.d.ts +35 -0
- package/lib/ui/utils.js +172 -0
- package/lib/ui/workspace-panel.js +198 -31
- package/lib/upgrade-manager.d.ts +5 -1
- package/lib/upgrade-manager.js +24 -1
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +2 -2
|
@@ -265,7 +265,7 @@ export function GlobalSyncSettingsModal(props) {
|
|
|
265
265
|
? "var(--dsw-alias-state-error-primary, #cf222e)"
|
|
266
266
|
: "var(--dsw-alias-label-secondary, #656d76)",
|
|
267
267
|
},
|
|
268
|
-
}, updateMsg.text) : null)) : activeSection === "about" ? h("div", null, 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 工作区与云端协作,提供
|
|
268
|
+
}, updateMsg.text) : null)) : activeSection === "about" ? h("div", null, 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 工作区与云端协作,提供 18 大领域 Agent 工具、人类 Slash 指令、Web 全景驾驶舱与一键跨平台升级。"), 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"))), h("div", {
|
|
269
269
|
className: "dsh-github-about-star-box",
|
|
270
270
|
style: {
|
|
271
271
|
display: "flex",
|
package/lib/ui/upgrade-card.js
CHANGED
|
@@ -50,14 +50,28 @@ export function UpgradeNotificationCard(props) {
|
|
|
50
50
|
}
|
|
51
51
|
else {
|
|
52
52
|
setStatus("failed");
|
|
53
|
-
var
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
53
|
+
var logText = String(json.log || "");
|
|
54
|
+
var isAgeViolation = json.errorType === "MINIMUM_RELEASE_AGE_VIOLATION" ||
|
|
55
|
+
logText.includes("ERR_PNPM_MINIMUM_RELEASE_AGE_VIOLATION") ||
|
|
56
|
+
logText.includes("minimumReleaseAge") ||
|
|
57
|
+
(typeof json.message === "string" && (json.message.includes("24 小时安全保护期") || json.message.includes("防投毒")));
|
|
58
|
+
if (isAgeViolation) {
|
|
59
|
+
// 命中 DSH 内置包管理器 24 小时防投毒策略:收起英文报错堆栈,呈现全中文专业自愈指引
|
|
60
|
+
setMsg("🛡️ 触发 DSH 新版本 24 小时安全保护期(npm 供应链防投毒策略)。检测到目标版本刚刚发布未满 24 小时,DSH 内置包管理器暂缓了自动升级以保障系统安全。\n\n" +
|
|
61
|
+
"【推荐操作】:\n" +
|
|
62
|
+
"1. 立即尝鲜:请先【完全退出关闭 DSH 应用程序】,然后在系统终端执行手动安装命令(点击右侧「📋 复制命令」获取);\n" +
|
|
63
|
+
"2. 稍后升级:无需任何操作,发版满 24 小时后直接在此点击「⚡ 一键自动升级」即可平滑完成。");
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
var logTail = "";
|
|
67
|
+
if (json.log && typeof json.log === "string") {
|
|
68
|
+
var lines = json.log.trim().split("\n").filter(function (l) { return l.trim(); });
|
|
69
|
+
if (lines.length > 0) {
|
|
70
|
+
logTail = "\n\n📝 日志末尾: " + lines.slice(-4).join(" ⏎ ").slice(0, 400);
|
|
71
|
+
}
|
|
58
72
|
}
|
|
73
|
+
setMsg((json.message || "升级命令执行失败,请在宿主终端手动执行。") + logTail);
|
|
59
74
|
}
|
|
60
|
-
setMsg((json.message || "升级命令执行失败,请在宿主终端手动执行。") + logTail);
|
|
61
75
|
}
|
|
62
76
|
}
|
|
63
77
|
catch (err) {
|
|
@@ -86,6 +100,7 @@ export function UpgradeNotificationCard(props) {
|
|
|
86
100
|
window.location.reload();
|
|
87
101
|
};
|
|
88
102
|
var h = React.createElement;
|
|
103
|
+
var isAgeViolation = msg.includes("24 小时安全保护期") || msg.includes("防投毒");
|
|
89
104
|
if (isRightbar) {
|
|
90
105
|
return h("div", {
|
|
91
106
|
className: "dsh-github-upgrade-strip",
|
|
@@ -95,13 +110,17 @@ export function UpgradeNotificationCard(props) {
|
|
|
95
110
|
borderRadius: "8px",
|
|
96
111
|
background: status === "success"
|
|
97
112
|
? "var(--dsw-alias-state-success-subtle, rgba(46, 160, 67, 0.12))"
|
|
98
|
-
:
|
|
113
|
+
: (isAgeViolation
|
|
114
|
+
? "rgba(217, 119, 6, 0.12)"
|
|
115
|
+
: "var(--dsw-alias-bg-module-platform, rgba(9, 105, 218, 0.08))"),
|
|
99
116
|
border: "1px solid " + (status === "success"
|
|
100
117
|
? "var(--dsw-alias-state-success-border, rgba(46, 160, 67, 0.35))"
|
|
101
|
-
:
|
|
118
|
+
: (isAgeViolation
|
|
119
|
+
? "rgba(217, 119, 6, 0.35)"
|
|
120
|
+
: "var(--dsw-alias-border-l2, rgba(9, 105, 218, 0.25))")),
|
|
102
121
|
fontSize: "12px",
|
|
103
122
|
},
|
|
104
|
-
}, h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "6px" } }, h("span", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success" ? "🎉 升级完成" : "🚀 发现新版 v" + updateInfo.latestVersion), h("button", {
|
|
123
|
+
}, h("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "6px" } }, h("span", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success" ? "🎉 升级完成" : (isAgeViolation ? "🛡️ 处于 24h 安全保护期" : "🚀 发现新版 v" + updateInfo.latestVersion)), h("button", {
|
|
105
124
|
onClick: handleDismiss,
|
|
106
125
|
title: "不再提醒此版本",
|
|
107
126
|
style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "14px", padding: 0 },
|
|
@@ -151,13 +170,17 @@ export function UpgradeNotificationCard(props) {
|
|
|
151
170
|
borderRadius: "8px",
|
|
152
171
|
background: status === "success"
|
|
153
172
|
? "linear-gradient(90deg, rgba(46, 160, 67, 0.15) 0%, rgba(9, 105, 218, 0.12) 100%)"
|
|
154
|
-
:
|
|
155
|
-
|
|
173
|
+
: (isAgeViolation
|
|
174
|
+
? "linear-gradient(90deg, rgba(217, 119, 6, 0.15) 0%, rgba(245, 158, 11, 0.12) 100%)"
|
|
175
|
+
: "linear-gradient(90deg, rgba(9, 105, 218, 0.12) 0%, rgba(137, 87, 229, 0.12) 100%)"),
|
|
176
|
+
border: "1px solid " + (status === "success" ? "rgba(46, 160, 67, 0.35)" : (isAgeViolation ? "rgba(217, 119, 6, 0.35)" : "rgba(9, 105, 218, 0.3)")),
|
|
156
177
|
fontSize: "13px",
|
|
157
178
|
},
|
|
158
|
-
}, h("div", { style: { display: "flex", alignItems: "center", gap: "10px" } }, h("span", { style: { fontSize: "20px" } }, status === "success" ? "🎉" : "🚀"), h("div", null, h("div", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success"
|
|
179
|
+
}, h("div", { style: { display: "flex", alignItems: "center", gap: "10px" } }, h("span", { style: { fontSize: "20px" } }, status === "success" ? "🎉" : (isAgeViolation ? "🛡️" : "🚀")), h("div", null, h("div", { style: { fontWeight: 600, color: "var(--dsw-alias-label-primary, #1f2328)" } }, status === "success"
|
|
159
180
|
? "升级完成!新版本 v" + updateInfo.latestVersion + " 已写入"
|
|
160
|
-
:
|
|
181
|
+
: (isAgeViolation
|
|
182
|
+
? "🛡️ 触发 DSH 新版本 24 小时安全保护期 (供应链防投毒策略)"
|
|
183
|
+
: "发现 dsh-github-flow 新版本 v" + updateInfo.latestVersion + "(当前版本: v" + updateInfo.currentVersion + ")")), h("div", { style: { fontSize: "12px", color: "var(--dsw-alias-label-secondary, #656d76)", marginTop: "2px" } }, msg || (status === "success"
|
|
161
184
|
? "💡 提示:DSH 后端驻留了旧版内存,需重启 DSH 服务彻底生效后端工具;若仅预览界面改动可先刷新网页。"
|
|
162
185
|
: "包含最新 GitHub 工具特性与性能优化,点击右侧可一键自动升级。")))), h("div", { style: { display: "flex", alignItems: "center", gap: "8px", flexShrink: 0 } }, status === "success"
|
|
163
186
|
? h("button", {
|
|
@@ -189,7 +212,9 @@ export function UpgradeNotificationCard(props) {
|
|
|
189
212
|
fontWeight: 600,
|
|
190
213
|
} : {}),
|
|
191
214
|
},
|
|
192
|
-
}, status === "failed"
|
|
215
|
+
}, status === "failed"
|
|
216
|
+
? (isAgeViolation ? "📋 复制命令 (需先退出DSH)" : "📋 复制命令手动升级")
|
|
217
|
+
: "📋 复制命令"), h("button", {
|
|
193
218
|
onClick: handleDismiss,
|
|
194
219
|
title: "不再提醒此版本",
|
|
195
220
|
style: { background: "none", border: "none", cursor: "pointer", color: "var(--dsw-alias-label-tertiary, #8c8c8c)", fontSize: "16px", padding: "0 4px" },
|
package/lib/ui/utils.d.ts
CHANGED
|
@@ -8,3 +8,38 @@ export declare function reviewDecisionBadge(decision: any): {
|
|
|
8
8
|
kind: "green" | "red" | "yellow";
|
|
9
9
|
} | null;
|
|
10
10
|
export declare function eventLabel(event: any): string;
|
|
11
|
+
export interface FetchRetryConfig {
|
|
12
|
+
maxRetries?: number;
|
|
13
|
+
baseDelayMs?: number;
|
|
14
|
+
onRetry?: (attempt: number, delayMs: number, reason: string) => void;
|
|
15
|
+
signal?: AbortSignal;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 带有阶梯退避重试(Backoff Retry)的通用异步拉取函数
|
|
19
|
+
*
|
|
20
|
+
* 核心设计理由与约束:
|
|
21
|
+
* 1. 应对代理节点不稳定、TLS 握手卡顿、短时网络抖动(半秒至两秒微断流);
|
|
22
|
+
* 2. 阶梯退避时序:第 1 次失败等 1s (1000ms),第 2 次等 2s (2000ms),第 3 次等 4s (4000ms),附加 0~200ms Jitter;
|
|
23
|
+
* 3. 智能区分软硬错误:401/403/404/400 确定性错误坚决不重试,仅在网络超时、不可达、或带有 networkError 时重试;
|
|
24
|
+
* 4. 严格响应外部 AbortSignal,组件卸载或用户手动打断时立即退出等待,绝无内存泄漏。
|
|
25
|
+
*/
|
|
26
|
+
export declare function fetchWithRetry(url: string, options?: RequestInit, config?: FetchRetryConfig): Promise<{
|
|
27
|
+
res: Response;
|
|
28
|
+
json: any;
|
|
29
|
+
}>;
|
|
30
|
+
export interface GitStatItem {
|
|
31
|
+
path: string;
|
|
32
|
+
total: number;
|
|
33
|
+
additions: number;
|
|
34
|
+
deletions: number;
|
|
35
|
+
isBinary: boolean;
|
|
36
|
+
rawRight?: string;
|
|
37
|
+
}
|
|
38
|
+
export declare function parseGitStatText(statText: string): {
|
|
39
|
+
files: GitStatItem[];
|
|
40
|
+
summary: {
|
|
41
|
+
filesChanged: number;
|
|
42
|
+
insertions: number;
|
|
43
|
+
deletions: number;
|
|
44
|
+
} | null;
|
|
45
|
+
} | null;
|
package/lib/ui/utils.js
CHANGED
|
@@ -64,3 +64,175 @@ export function eventLabel(event) {
|
|
|
64
64
|
return "🔀 PR";
|
|
65
65
|
return e || "";
|
|
66
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* 确定性失败的特征(客户端侧兜底判定)。
|
|
69
|
+
*
|
|
70
|
+
* 为什么这里还要再写一份:后端 GhExecutor 已统一分类并通过 errorKind 分流,
|
|
71
|
+
* 但浏览器端拿不到 errorKind,只能看到拼接后的错误文本。这类错误重试一万次
|
|
72
|
+
* 也不会变好(仓库就是关着 Issues),必须阻止阶梯退避把 7 秒白白烧掉。
|
|
73
|
+
* 与服务端共用同一组特征串,改动时两边同步。
|
|
74
|
+
*/
|
|
75
|
+
var DETERMINISTIC_HINT_RE = /repository has disabled|Could not resolve to a Repository|Branch not found|HTTP 403|Not Found \(HTTP 404\)|rate limit/i;
|
|
76
|
+
/**
|
|
77
|
+
* 带有阶梯退避重试(Backoff Retry)的通用异步拉取函数
|
|
78
|
+
*
|
|
79
|
+
* 核心设计理由与约束:
|
|
80
|
+
* 1. 应对代理节点不稳定、TLS 握手卡顿、短时网络抖动(半秒至两秒微断流);
|
|
81
|
+
* 2. 阶梯退避时序:第 1 次失败等 1s (1000ms),第 2 次等 2s (2000ms),第 3 次等 4s (4000ms),附加 0~200ms Jitter;
|
|
82
|
+
* 3. 智能区分软硬错误:401/403/404/400 确定性错误坚决不重试,仅在网络超时、不可达、或带有 networkError 时重试;
|
|
83
|
+
* 4. 严格响应外部 AbortSignal,组件卸载或用户手动打断时立即退出等待,绝无内存泄漏。
|
|
84
|
+
*/
|
|
85
|
+
export async function fetchWithRetry(url, options = {}, config = {}) {
|
|
86
|
+
var maxRetries = config.maxRetries !== undefined ? config.maxRetries : 3;
|
|
87
|
+
var baseDelayMs = config.baseDelayMs !== undefined ? config.baseDelayMs : 1000;
|
|
88
|
+
var signal = config.signal || options.signal;
|
|
89
|
+
var attempt = 0;
|
|
90
|
+
while (true) {
|
|
91
|
+
attempt++;
|
|
92
|
+
var currentRes = null;
|
|
93
|
+
var currentJson = null;
|
|
94
|
+
var failureReason = "";
|
|
95
|
+
var shouldRetry = false;
|
|
96
|
+
try {
|
|
97
|
+
if (signal && signal.aborted) {
|
|
98
|
+
throw new Error("Aborted");
|
|
99
|
+
}
|
|
100
|
+
currentRes = await fetch(url, { ...options, signal });
|
|
101
|
+
// 硬错误守卫:客户端确定性错误(如未授权、权限拒绝、不存在、参数错误)坚决不重试
|
|
102
|
+
if (currentRes.status === 401 || currentRes.status === 403 || currentRes.status === 404 || currentRes.status === 400) {
|
|
103
|
+
currentJson = await currentRes.json().catch(() => ({}));
|
|
104
|
+
return { res: currentRes, json: currentJson };
|
|
105
|
+
}
|
|
106
|
+
if (!currentRes.ok) {
|
|
107
|
+
failureReason = "HTTP " + currentRes.status + " " + currentRes.statusText;
|
|
108
|
+
shouldRetry = true;
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
currentJson = await currentRes.json().catch(() => null);
|
|
112
|
+
// 业务层软错误检测(后端返回了 200,但标记了 networkError 或 partialFailure)
|
|
113
|
+
var isNetworkError = Boolean(currentJson && (currentJson.partialFailure ||
|
|
114
|
+
currentJson.data?.partialFailure ||
|
|
115
|
+
currentJson.data?.networkError ||
|
|
116
|
+
currentJson.data?.auth?.networkError));
|
|
117
|
+
if (isNetworkError) {
|
|
118
|
+
failureReason = currentJson.error || currentJson.data?.networkErrorReason || "云端接口网络抖动";
|
|
119
|
+
// 后端已把「确定性失败」(仓库关了 Issues / 仓库不存在 / 无权限)从
|
|
120
|
+
// networkError 里剥离,只留真正的传输层故障。但历史版本或第三方实现
|
|
121
|
+
// 仍可能把确定性文本塞进同一条链路,这里再兜一道:命中即放弃重试。
|
|
122
|
+
shouldRetry = !DETERMINISTIC_HINT_RE.test(String(failureReason));
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
// 成功获取完整且无网络错误的数据
|
|
126
|
+
return { res: currentRes, json: currentJson };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
catch (err) {
|
|
131
|
+
if ((signal && signal.aborted) || err.name === "AbortError") {
|
|
132
|
+
throw err;
|
|
133
|
+
}
|
|
134
|
+
failureReason = err.message || "网络连接中断或超时";
|
|
135
|
+
shouldRetry = true;
|
|
136
|
+
}
|
|
137
|
+
// 判断是否已耗尽重试次数
|
|
138
|
+
if (!shouldRetry || attempt > maxRetries) {
|
|
139
|
+
if (currentRes) {
|
|
140
|
+
return { res: currentRes, json: currentJson || {} };
|
|
141
|
+
}
|
|
142
|
+
throw new Error(failureReason || "网络请求失败");
|
|
143
|
+
}
|
|
144
|
+
// 计算阶梯退避等待时间:baseDelayMs * 2^(attempt - 1) + Jitter 自适应微扰
|
|
145
|
+
var exponentialDelay = baseDelayMs * Math.pow(2, attempt - 1);
|
|
146
|
+
var maxJitter = Math.min(200, Math.floor(exponentialDelay * 0.2));
|
|
147
|
+
var jitter = Math.floor(Math.random() * (maxJitter + 1));
|
|
148
|
+
var totalDelay = exponentialDelay + jitter;
|
|
149
|
+
if (config.onRetry) {
|
|
150
|
+
config.onRetry(attempt, totalDelay, failureReason);
|
|
151
|
+
}
|
|
152
|
+
// 可中断的睡眠等待
|
|
153
|
+
await new Promise((resolve, reject) => {
|
|
154
|
+
var timer = setTimeout(resolve, totalDelay);
|
|
155
|
+
if (signal) {
|
|
156
|
+
signal.addEventListener("abort", () => {
|
|
157
|
+
clearTimeout(timer);
|
|
158
|
+
reject(new Error("Aborted"));
|
|
159
|
+
}, { once: true });
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
export function parseGitStatText(statText) {
|
|
165
|
+
if (!statText || typeof statText !== "string")
|
|
166
|
+
return null;
|
|
167
|
+
var lines = statText.split("\n");
|
|
168
|
+
var files = [];
|
|
169
|
+
var summary = null;
|
|
170
|
+
for (var i = 0; i < lines.length; i++) {
|
|
171
|
+
var line = lines[i].trim();
|
|
172
|
+
if (!line)
|
|
173
|
+
continue;
|
|
174
|
+
var summaryMatch = line.match(/(\d+)\s+files?\s+changed(?:,\s+(\d+)\s+insertions?\(\+\))?(?:,\s+(\d+)\s+deletions?\(-\))?/i);
|
|
175
|
+
if (summaryMatch) {
|
|
176
|
+
summary = {
|
|
177
|
+
filesChanged: parseInt(summaryMatch[1], 10),
|
|
178
|
+
insertions: parseInt(summaryMatch[2] || "0", 10),
|
|
179
|
+
deletions: parseInt(summaryMatch[3] || "0", 10),
|
|
180
|
+
};
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
var pipeIdx = line.indexOf("|");
|
|
184
|
+
if (pipeIdx > 0) {
|
|
185
|
+
var left = line.slice(0, pipeIdx).trim();
|
|
186
|
+
var right = line.slice(pipeIdx + 1).trim();
|
|
187
|
+
if (left.startsWith('"') && left.endsWith('"') && left.length >= 2) {
|
|
188
|
+
left = left.slice(1, -1);
|
|
189
|
+
}
|
|
190
|
+
if (/^Bin\b/i.test(right)) {
|
|
191
|
+
files.push({ path: left, total: 0, additions: 0, deletions: 0, isBinary: true, rawRight: right });
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
var numMatch = right.match(/^(\d+)(.*)$/);
|
|
195
|
+
if (numMatch) {
|
|
196
|
+
var total = parseInt(numMatch[1], 10);
|
|
197
|
+
var graph = numMatch[2].trim();
|
|
198
|
+
var plusCount = 0;
|
|
199
|
+
var minusCount = 0;
|
|
200
|
+
for (var j = 0; j < graph.length; j++) {
|
|
201
|
+
if (graph[j] === "+")
|
|
202
|
+
plusCount++;
|
|
203
|
+
else if (graph[j] === "-")
|
|
204
|
+
minusCount++;
|
|
205
|
+
}
|
|
206
|
+
var additions = 0;
|
|
207
|
+
var deletions = 0;
|
|
208
|
+
if (plusCount === 0 && minusCount === 0) {
|
|
209
|
+
additions = total;
|
|
210
|
+
}
|
|
211
|
+
else if (minusCount === 0) {
|
|
212
|
+
additions = total;
|
|
213
|
+
deletions = 0;
|
|
214
|
+
}
|
|
215
|
+
else if (plusCount === 0) {
|
|
216
|
+
additions = 0;
|
|
217
|
+
deletions = total;
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
if (plusCount + minusCount === total) {
|
|
221
|
+
additions = plusCount;
|
|
222
|
+
deletions = minusCount;
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
var addExact = Math.round((total * plusCount) / (plusCount + minusCount));
|
|
226
|
+
additions = addExact;
|
|
227
|
+
deletions = Math.max(0, total - addExact);
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
files.push({ path: left, total, additions, deletions, isBinary: false });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (files.length === 0 && !summary)
|
|
236
|
+
return null;
|
|
237
|
+
return { files, summary };
|
|
238
|
+
}
|