dsh-github-flow 0.6.0 → 0.7.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.
- package/README.en.md +102 -15
- package/README.md +105 -15
- package/docs/UPGRADE-NOTES.md +12 -11
- package/docs/architecture.md +67 -14
- package/lib/api/route-helpers.d.ts +19 -0
- package/lib/api/route-helpers.js +54 -0
- package/lib/api/routes-actions.js +126 -2
- package/lib/api/routes-lifecycle.js +8 -1
- package/lib/api/routes-overview.d.ts +26 -0
- package/lib/api/routes-overview.js +276 -34
- package/lib/api/routes-proxy.js +8 -1
- package/lib/api/routes.d.ts +11 -1
- package/lib/api/routes.js +15 -2
- package/lib/cache.d.ts +0 -21
- package/lib/cache.js +0 -37
- package/lib/client.d.ts +2 -1
- package/lib/client.js +2108 -658
- package/lib/destructive.d.ts +59 -0
- package/lib/destructive.js +285 -0
- package/lib/executor/args-guard.d.ts +17 -0
- package/lib/executor/args-guard.js +23 -0
- package/lib/executor/failure-anchors.d.ts +52 -0
- package/lib/executor/failure-anchors.js +116 -0
- package/lib/executor/pr-review.d.ts +95 -0
- package/lib/executor/pr-review.js +175 -0
- package/lib/executor/run-log.d.ts +36 -0
- package/lib/executor/run-log.js +78 -0
- package/lib/executor.d.ts +204 -3
- package/lib/executor.js +670 -28
- package/lib/github-service.d.ts +1 -1
- package/lib/main.d.ts +92 -18
- package/lib/main.js +186 -95
- package/lib/renderers/common.d.ts +0 -3
- package/lib/renderers/common.js +8 -44
- package/lib/renderers/markdown.d.ts +1 -0
- package/lib/renderers/markdown.js +1 -0
- package/lib/renderers/pr.js +26 -1
- package/lib/renderers/task.d.ts +9 -0
- package/lib/renderers/task.js +120 -0
- package/lib/review-rules.d.ts +60 -0
- package/lib/review-rules.js +204 -0
- package/lib/shared/state-badges.d.ts +42 -0
- package/lib/shared/state-badges.js +102 -0
- package/lib/task-primitives.d.ts +109 -0
- package/lib/task-primitives.js +194 -0
- package/lib/tool-tiers.d.ts +57 -0
- package/lib/tool-tiers.js +168 -0
- package/lib/tools/agent-task.d.ts +4 -0
- package/lib/tools/agent-task.js +22 -9
- package/lib/tools/api.d.ts +4 -0
- package/lib/tools/api.js +48 -12
- package/lib/tools/body-input.d.ts +58 -0
- package/lib/tools/body-input.js +123 -0
- package/lib/tools/cache.js +42 -40
- package/lib/tools/codespace.js +4 -12
- package/lib/tools/discussion.d.ts +4 -0
- package/lib/tools/discussion.js +33 -17
- package/lib/tools/gist.d.ts +1 -39
- package/lib/tools/gist.js +4 -14
- package/lib/tools/help.js +65 -13
- package/lib/tools/issue.d.ts +4 -0
- package/lib/tools/issue.js +37 -38
- package/lib/tools/label.js +4 -15
- package/lib/tools/pr.d.ts +51 -0
- package/lib/tools/pr.js +204 -46
- package/lib/tools/project.js +4 -13
- package/lib/tools/release.d.ts +4 -0
- package/lib/tools/release.js +26 -26
- package/lib/tools/repo.js +4 -4
- package/lib/tools/ruleset.js +9 -6
- package/lib/tools/run.js +76 -24
- package/lib/tools/secret.js +11 -17
- package/lib/tools/skill.js +18 -22
- package/lib/tools/status.js +4 -4
- package/lib/tools/task.d.ts +223 -0
- package/lib/tools/task.js +464 -0
- package/lib/types.d.ts +183 -1
- package/lib/ui/commit-modal.js +108 -4
- package/lib/ui/diff-utils.d.ts +51 -0
- package/lib/ui/diff-utils.js +155 -0
- package/lib/ui/file-diff-modal.d.ts +33 -0
- package/lib/ui/file-diff-modal.js +250 -0
- package/lib/ui/help-modal.d.ts +1 -1
- package/lib/ui/help-modal.js +24 -12
- package/lib/ui/issue-modal.js +7 -3
- package/lib/ui/pr-modal.js +50 -11
- package/lib/ui/run-modal.d.ts +26 -0
- package/lib/ui/run-modal.js +486 -0
- package/lib/ui/styles.d.ts +1 -1
- package/lib/ui/styles.js +376 -44
- package/lib/ui/sync-settings-modal.js +1 -1
- package/lib/ui/workspace-panel.js +78 -23
- package/lib/version.d.ts +1 -1
- package/lib/version.js +1 -1
- package/package.json +9 -2
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件 Diff 预览弹窗(未提交清单 / 提交详情共用)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么合成一个组件:
|
|
5
|
+
* 1. 未提交清单原本把整个 section 换成「二级视图」(`activeDiffFile ? 二级视图 : 列表`),
|
|
6
|
+
* 观感像另开一页;改成弹窗后列表留在原位,返回成本从一次重渲染变成一次 Esc;
|
|
7
|
+
* 2. 提交详情也要「点文件看 diff」,两处只是**数据来源**不同(一个是 `/api/github/file-diff`,
|
|
8
|
+
* 一个是整包 patch 切出来的一段),外壳/配色/错误态/复制逻辑完全同构,分两份必然漂移。
|
|
9
|
+
*
|
|
10
|
+
* 404 降级保留:桌面端未重启时新路由不存在,先回退到常驻的 `/api/github/action`,
|
|
11
|
+
* 再不行才显式提示「重启桌面端」——这是 file-diff 链路既有的自愈策略。
|
|
12
|
+
*/
|
|
13
|
+
export function FileDiffModal(props) {
|
|
14
|
+
var h = React.createElement;
|
|
15
|
+
var f = props.file;
|
|
16
|
+
var selfFetch = props.diffText === undefined || props.diffText === null;
|
|
17
|
+
var [fetched, setFetched] = React.useState(null);
|
|
18
|
+
var [fetching, setFetching] = React.useState(selfFetch);
|
|
19
|
+
var [fetchError, setFetchError] = React.useState(null);
|
|
20
|
+
var [copied, setCopied] = React.useState(false);
|
|
21
|
+
React.useEffect(function () {
|
|
22
|
+
var onKeyDown = function (e) {
|
|
23
|
+
if (e.key === "Escape" || e.keyCode === 27)
|
|
24
|
+
props.onClose();
|
|
25
|
+
};
|
|
26
|
+
window.addEventListener("keydown", onKeyDown);
|
|
27
|
+
return function () { window.removeEventListener("keydown", onKeyDown); };
|
|
28
|
+
}, [props.onClose]);
|
|
29
|
+
React.useEffect(function () {
|
|
30
|
+
if (!selfFetch)
|
|
31
|
+
return;
|
|
32
|
+
setFetching(true);
|
|
33
|
+
setFetchError(null);
|
|
34
|
+
setFetched(null);
|
|
35
|
+
var run = async function () {
|
|
36
|
+
var primaryUrl = "/api/github/file-diff?file=" + encodeURIComponent(f.path) + (props.cwd ? "&cwd=" + encodeURIComponent(props.cwd) : "");
|
|
37
|
+
var readJson = async function (res) {
|
|
38
|
+
var text = await res.text().catch(function () { return ""; });
|
|
39
|
+
var json = null;
|
|
40
|
+
if (text && (text.trim().charAt(0) === "{" || text.trim().charAt(0) === "[")) {
|
|
41
|
+
try {
|
|
42
|
+
json = JSON.parse(text);
|
|
43
|
+
}
|
|
44
|
+
catch (e) { }
|
|
45
|
+
}
|
|
46
|
+
return { ok: res.ok, status: res.status, text: text, json: json };
|
|
47
|
+
};
|
|
48
|
+
try {
|
|
49
|
+
var p1 = await readJson(await fetch(primaryUrl));
|
|
50
|
+
if (p1.ok && p1.json && p1.json.ok && p1.json.data) {
|
|
51
|
+
setFetched(p1.json.data);
|
|
52
|
+
setFetching(false);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
// 主路由 404:后端常驻进程还没载入新路由 → 先退到既有 action 通道
|
|
56
|
+
if (p1.status === 404) {
|
|
57
|
+
var r2 = await fetch("/api/github/action", {
|
|
58
|
+
method: "POST",
|
|
59
|
+
headers: { "Content-Type": "application/json" },
|
|
60
|
+
body: JSON.stringify({ action: "get_file_diff", file: f.path, cwd: props.cwd || undefined }),
|
|
61
|
+
}).catch(function () { return null; });
|
|
62
|
+
if (r2) {
|
|
63
|
+
var p2 = await readJson(r2);
|
|
64
|
+
if (p2.ok && p2.json && p2.json.data) {
|
|
65
|
+
setFetched(p2.json.data);
|
|
66
|
+
setFetching(false);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
setFetching(false);
|
|
71
|
+
setFetchError("后端对比接口未激活 (404)。请重启 DSH 桌面主程序以载入最新对比引擎;您也可点击上方「在 DSH 中打开」直接预览文件源码。");
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
setFetching(false);
|
|
75
|
+
setFetchError(p1.json && p1.json.error ? p1.json.error : (p1.text || ("查询差异失败 (HTTP " + p1.status + ")")));
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
setFetching(false);
|
|
79
|
+
setFetchError((err && err.message) || "网络请求异常");
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
run();
|
|
83
|
+
}, [f.path, props.cwd, selfFetch]);
|
|
84
|
+
var diffText = selfFetch ? (fetched && fetched.diff) || "" : String(props.diffText || "");
|
|
85
|
+
var isBinary = Boolean(props.file.isBinary || (fetched && fetched.isBinary));
|
|
86
|
+
var additions = props.file.additions !== undefined ? props.file.additions : (fetched ? fetched.additions : undefined);
|
|
87
|
+
var deletions = props.file.deletions !== undefined ? props.file.deletions : (fetched ? fetched.deletions : undefined);
|
|
88
|
+
var loading = props.loading === true || (selfFetch && fetching);
|
|
89
|
+
var error = props.error || (selfFetch ? fetchError : null);
|
|
90
|
+
var status = String(props.file.status || "");
|
|
91
|
+
var statusColor = status === "??" ? "var(--dsw-alias-state-success-primary, #1a7f37)"
|
|
92
|
+
: status.indexOf("D") >= 0 ? "var(--dsw-alias-state-error-primary, #cf222e)"
|
|
93
|
+
: "var(--dsw-alias-state-attention-primary, #9a6700)";
|
|
94
|
+
var statusText = status === "??" ? "未跟踪新增"
|
|
95
|
+
: status.indexOf("D") >= 0 ? "已删除"
|
|
96
|
+
: status.indexOf("R") >= 0 ? "已重命名"
|
|
97
|
+
: status ? "已修改" : "";
|
|
98
|
+
var copyDiff = function () {
|
|
99
|
+
if (!diffText)
|
|
100
|
+
return;
|
|
101
|
+
if (navigator && navigator.clipboard && navigator.clipboard.writeText) {
|
|
102
|
+
navigator.clipboard.writeText(diffText).then(function () {
|
|
103
|
+
setCopied(true);
|
|
104
|
+
setTimeout(function () { setCopied(false); }, 2000);
|
|
105
|
+
}).catch(function () { });
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
var renderLines = function () {
|
|
109
|
+
var lines = diffText.split("\n");
|
|
110
|
+
/**
|
|
111
|
+
* hunk 状态位:`--- ` / `+++ ` 既是文件头,也可能是正文(Markdown 的 `---` 分隔线
|
|
112
|
+
* 被删时补丁行是 `----`)。只按行首前缀判断会把正文染成灰色小字,且与真实增删不符。
|
|
113
|
+
* git 保证文件头只出现在第一个 `@@` 之前,据此切分才可靠。
|
|
114
|
+
*/
|
|
115
|
+
var inHunk = false;
|
|
116
|
+
return lines.map(function (line, idx) {
|
|
117
|
+
if (line.indexOf("diff --git") === 0)
|
|
118
|
+
inHunk = false;
|
|
119
|
+
var isHunk = line.indexOf("@@") === 0;
|
|
120
|
+
if (isHunk)
|
|
121
|
+
inHunk = true;
|
|
122
|
+
var isAdd = inHunk && !isHunk && line.charAt(0) === "+";
|
|
123
|
+
var isDel = inHunk && !isHunk && line.charAt(0) === "-";
|
|
124
|
+
var isHeader = !inHunk && !isHunk;
|
|
125
|
+
var lineStyle = { display: "flex", padding: "0 8px", whiteSpace: "pre", minWidth: "100%", boxSizing: "border-box" };
|
|
126
|
+
if (isAdd) {
|
|
127
|
+
lineStyle.backgroundColor = "var(--dsw-alias-state-success-subtle, rgba(46,160,67,0.15))";
|
|
128
|
+
lineStyle.color = "var(--dsw-alias-state-success-primary, #1a7f37)";
|
|
129
|
+
}
|
|
130
|
+
else if (isDel) {
|
|
131
|
+
lineStyle.backgroundColor = "var(--dsw-alias-state-error-subtle, rgba(248,81,73,0.15))";
|
|
132
|
+
lineStyle.color = "var(--dsw-alias-state-error-primary, #cf222e)";
|
|
133
|
+
}
|
|
134
|
+
else if (isHunk) {
|
|
135
|
+
lineStyle.backgroundColor = "var(--dsw-alias-bg-subtle, rgba(0,0,0,0.04))";
|
|
136
|
+
lineStyle.color = "var(--dsw-alias-label-secondary, #656d76)";
|
|
137
|
+
lineStyle.fontWeight = 600;
|
|
138
|
+
lineStyle.margin = "2px 0";
|
|
139
|
+
}
|
|
140
|
+
else if (isHeader) {
|
|
141
|
+
lineStyle.color = "var(--dsw-alias-label-muted, #8c959f)";
|
|
142
|
+
lineStyle.fontSize = "10px";
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
lineStyle.color = "var(--dsw-alias-label-primary, #1f2328)";
|
|
146
|
+
}
|
|
147
|
+
return h("div", { key: idx, style: lineStyle }, line || " ");
|
|
148
|
+
});
|
|
149
|
+
};
|
|
150
|
+
return h("div", {
|
|
151
|
+
className: "dsh-github-modal-overlay",
|
|
152
|
+
onClick: function (e) {
|
|
153
|
+
// 叠在提交详情弹窗之上时,遮罩点击必须止住冒泡:
|
|
154
|
+
// 外层 overlay 的 onClick 是「关闭提交详情」,不拦就会一次点击关掉两层
|
|
155
|
+
if (e && typeof e.stopPropagation === "function")
|
|
156
|
+
e.stopPropagation();
|
|
157
|
+
props.onClose();
|
|
158
|
+
},
|
|
159
|
+
style: { zIndex: props.zIndex === undefined ? 100001 : props.zIndex },
|
|
160
|
+
}, h("div", {
|
|
161
|
+
className: "dsh-github-modal-dialog",
|
|
162
|
+
role: "dialog",
|
|
163
|
+
"aria-label": "文件差异预览",
|
|
164
|
+
style: { width: "94%", maxWidth: "920px", maxHeight: "88vh", display: "flex", flexDirection: "column", overflow: "hidden" },
|
|
165
|
+
onClick: function (e) { e.stopPropagation(); },
|
|
166
|
+
},
|
|
167
|
+
// 头部:状态 + 路径 + 增删统计 + 操作按钮
|
|
168
|
+
h("div", { className: "dsh-github-modal-header", style: { flexShrink: 0, gap: "8px" } }, h("div", { style: { display: "flex", alignItems: "center", gap: "8px", minWidth: 0, flex: 1 } }, h("h3", { style: { margin: 0, fontSize: "14px", fontWeight: 600, flexShrink: 0 } }, "文件差异"), status
|
|
169
|
+
? h("span", {
|
|
170
|
+
className: "dsh-github-badge",
|
|
171
|
+
style: { backgroundColor: statusColor + "22", color: statusColor, fontWeight: 600, fontSize: "11px", flexShrink: 0 },
|
|
172
|
+
}, status + " " + statusText)
|
|
173
|
+
: null, additions !== undefined && deletions !== undefined
|
|
174
|
+
? h("div", { style: { display: "flex", gap: "4px", fontSize: "11px", fontFamily: "monospace", flexShrink: 0 } }, h("span", { style: { color: "var(--dsw-alias-state-success-primary, #1a7f37)", fontWeight: 600 } }, "+" + additions), h("span", { style: { color: "var(--dsw-alias-state-error-primary, #cf222e)", fontWeight: 600 } }, "-" + deletions))
|
|
175
|
+
: null, props.subtitle
|
|
176
|
+
? h("span", { style: { fontSize: "11px", opacity: 0.7, flexShrink: 0, fontFamily: "monospace" } }, props.subtitle)
|
|
177
|
+
: null), h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 } }, diffText
|
|
178
|
+
? h("button", {
|
|
179
|
+
className: "dsh-github-btn-sm",
|
|
180
|
+
style: { padding: "3px 8px", fontSize: "11px", cursor: "pointer" },
|
|
181
|
+
onClick: copyDiff,
|
|
182
|
+
title: "复制 Unified Diff 原文",
|
|
183
|
+
}, copied ? "✓ 已复制" : "📋 复制 Diff")
|
|
184
|
+
: null, props.onOpenInDsh
|
|
185
|
+
? h("button", {
|
|
186
|
+
className: "dsh-github-btn-sm",
|
|
187
|
+
style: { padding: "3px 8px", fontSize: "11px", cursor: "pointer" },
|
|
188
|
+
onClick: function () { if (props.onOpenInDsh)
|
|
189
|
+
props.onOpenInDsh(f.path); },
|
|
190
|
+
title: "在 DSH 侧边栏打开该文件完整源码",
|
|
191
|
+
}, "📄 在 DSH 中打开")
|
|
192
|
+
: null, h("button", {
|
|
193
|
+
style: { background: "none", border: "none", cursor: "pointer", fontSize: "18px", color: "var(--dsw-alias-label-secondary, #656d76)", padding: "0 4px" },
|
|
194
|
+
onClick: props.onClose,
|
|
195
|
+
title: "关闭 (Esc)",
|
|
196
|
+
}, "✕"))),
|
|
197
|
+
// 正文
|
|
198
|
+
h("div", {
|
|
199
|
+
className: "dsh-github-modal-body",
|
|
200
|
+
style: { padding: "12px 16px 16px", overflowY: "auto", flex: 1, minHeight: 0, display: "flex", flexDirection: "column", gap: "8px", boxSizing: "border-box" },
|
|
201
|
+
}, h("div", {
|
|
202
|
+
style: {
|
|
203
|
+
fontSize: "11px",
|
|
204
|
+
fontFamily: "monospace",
|
|
205
|
+
color: "var(--dsw-alias-label-secondary, #656d76)",
|
|
206
|
+
wordBreak: "break-all",
|
|
207
|
+
padding: "3px 6px",
|
|
208
|
+
backgroundColor: "var(--dsw-alias-bg-subtle, rgba(0,0,0,0.03))",
|
|
209
|
+
borderRadius: "4px",
|
|
210
|
+
flexShrink: 0,
|
|
211
|
+
},
|
|
212
|
+
title: f.path,
|
|
213
|
+
}, f.path), loading
|
|
214
|
+
? h("div", { style: { padding: "32px 0", textAlign: "center", opacity: 0.65, fontSize: "12px" } }, "⏳ 正在计算 Git 代码比对差异...")
|
|
215
|
+
: error
|
|
216
|
+
? h("div", {
|
|
217
|
+
style: {
|
|
218
|
+
padding: "12px 14px",
|
|
219
|
+
color: "var(--dsw-alias-label-primary, #1f2328)",
|
|
220
|
+
fontSize: "12px",
|
|
221
|
+
lineHeight: "1.6",
|
|
222
|
+
backgroundColor: "var(--dsw-alias-bg-subtle, rgba(0,0,0,0.03))",
|
|
223
|
+
borderRadius: "6px",
|
|
224
|
+
},
|
|
225
|
+
}, h("div", { style: { color: "var(--dsw-alias-state-error-primary, #cf222e)", fontWeight: 600, marginBottom: "6px" } }, "⚠️ " + error))
|
|
226
|
+
: isBinary
|
|
227
|
+
? h("div", { style: { padding: "32px 0", textAlign: "center", opacity: 0.8, fontSize: "12px" } }, "📦 该文件为二进制格式(图片或编译产物),无法直接展示文本差异。")
|
|
228
|
+
: !diffText
|
|
229
|
+
? h("div", { style: { padding: "32px 0", textAlign: "center", opacity: 0.7, fontSize: "12px" } }, "该文件在本次变更中没有可展示的文本差异。")
|
|
230
|
+
: h("div", {
|
|
231
|
+
className: "dsh-github-inline-diff-view",
|
|
232
|
+
style: { display: "flex", flexDirection: "column", gap: "8px", minHeight: "120px" },
|
|
233
|
+
}, h("div", {
|
|
234
|
+
className: "dsh-github-inline-diff-content",
|
|
235
|
+
style: {
|
|
236
|
+
margin: 0,
|
|
237
|
+
padding: "4px 0",
|
|
238
|
+
fontFamily: "ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, monospace",
|
|
239
|
+
fontSize: "11px",
|
|
240
|
+
lineHeight: "1.5",
|
|
241
|
+
overflowX: "auto",
|
|
242
|
+
maxHeight: "62vh",
|
|
243
|
+
borderRadius: "6px",
|
|
244
|
+
border: "1px solid var(--dsw-alias-border-subtle, rgba(0,0,0,0.08))",
|
|
245
|
+
backgroundColor: "var(--dsw-alias-bg-canvas, #ffffff)",
|
|
246
|
+
},
|
|
247
|
+
}, renderLines())), props.note
|
|
248
|
+
? h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", lineHeight: "1.6" } }, "⚠️ " + props.note)
|
|
249
|
+
: null)));
|
|
250
|
+
}
|
package/lib/ui/help-modal.d.ts
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* 【内容与代码同步的纪律】
|
|
12
12
|
* 本文件里出现的工具数量、action 数量、Tab 名称、按钮文案,全部以当前代码为准:
|
|
13
|
-
* 工具矩阵 = src/main.ts 的
|
|
13
|
+
* 工具矩阵 = src/main.ts 的 19 处 ctx.tools.register;
|
|
14
14
|
* Tab 布局 = src/client.ts 的 TAB_DEFS 与 src/ui/workspace-panel.ts 的四个标签;
|
|
15
15
|
* 若后续增删工具或 Tab,必须同步改这里,否则用户会照着过时说明去找不存在的入口。
|
|
16
16
|
*/
|
package/lib/ui/help-modal.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* 【内容与代码同步的纪律】
|
|
12
12
|
* 本文件里出现的工具数量、action 数量、Tab 名称、按钮文案,全部以当前代码为准:
|
|
13
|
-
* 工具矩阵 = src/main.ts 的
|
|
13
|
+
* 工具矩阵 = src/main.ts 的 19 处 ctx.tools.register;
|
|
14
14
|
* Tab 布局 = src/client.ts 的 TAB_DEFS 与 src/ui/workspace-panel.ts 的四个标签;
|
|
15
15
|
* 若后续增删工具或 Tab,必须同步改这里,否则用户会照着过时说明去找不存在的入口。
|
|
16
16
|
*/
|
|
@@ -49,7 +49,7 @@ export function GithubHelpModal(props) {
|
|
|
49
49
|
{ id: "quickstart", label: "🚀 3分钟极速上手" },
|
|
50
50
|
{ id: "prompts", label: "🤖 Agent 提示词实战" },
|
|
51
51
|
{ id: "panels", label: "🖥️ 两大界面操作图解" },
|
|
52
|
-
{ id: "tools", label: "🧰
|
|
52
|
+
{ id: "tools", label: "🧰 19 大工具速查" },
|
|
53
53
|
{ id: "faq", label: "🔑 常见自愈排错" },
|
|
54
54
|
];
|
|
55
55
|
// 2. 渲染各 Tab 独立视图
|
|
@@ -78,7 +78,7 @@ export function GithubHelpModal(props) {
|
|
|
78
78
|
return h("div", { key: i, className: "dsh-github-help-card", style: { display: "flex", gap: "14px", alignItems: "flex-start" } }, h("div", { className: "dsh-github-help-step-badge" }, s.step), h("div", { style: { flex: 1 } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "4px" } }, s.title), h("div", { className: "dsh-github-help-desc", style: { marginBottom: "6px" } }, s.desc), h("div", { style: { fontSize: "12px", lineHeight: "1.6", whiteSpace: "pre-line", color: "inherit" } }, s.actionText)));
|
|
79
79
|
}),
|
|
80
80
|
// 能力总览:让用户三秒钟知道这插件到底能干什么,而不是去翻 README
|
|
81
|
-
h("div", { className: "dsh-github-help-card", style: { marginTop: "4px" } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, "📦 能力总览:
|
|
81
|
+
h("div", { className: "dsh-github-help-card", style: { marginTop: "4px" } }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, "📦 能力总览:19 大工具 · 106 个业务动作 · 2 套可视化工作台"), h("div", { style: { fontSize: "12px", lineHeight: "1.8", color: "var(--dsw-alias-label-secondary, #8b949e)" } }, "Agent 侧可直接调用 19 个 github_* 工具(PR 审查合并、Issue 任务流转、Actions 排障、免 clone 读文件、发版交付、Secrets 管理、Skills 生态、标签与规则集、构建缓存、Gist、跨仓待办大盘、Copilot 智能体、Discussions、Projects v2、Codespaces、运行时遥测与元指南)。", h("br"), "人类侧在每个会话右侧栏拥有 5 个 Tab(Git / 未提交 / PR / Issues / Actions),在最左侧 Octocat 入口拥有 4 个标签的全景驾驶舱。PR / Issue / Actions 的卡片点击都能在线查看详情——Actions 看到的是 job / step 树与失败步骤,失败时还能按需拉一段日志,不用切浏览器。")));
|
|
82
82
|
}
|
|
83
83
|
if (activeTab === "prompts") {
|
|
84
84
|
var promptCards = [
|
|
@@ -92,7 +92,7 @@ export function GithubHelpModal(props) {
|
|
|
92
92
|
title: "🚨 场景二:CI / Actions 门禁报错秒级排查与自动修复",
|
|
93
93
|
desc: "构建挂了不用在网页反复翻几十页日志,直接吩咐 Agent 定位报错步骤并就地改代码。",
|
|
94
94
|
prompt: "检查当前仓库最近一次 CI 失败的原因,精确定位报错步骤并帮我尝试修复代码",
|
|
95
|
-
aiAction: "Agent
|
|
95
|
+
aiAction: "Agent 会调 github_task(action:'diagnose_ci') 或 github_run(action:'log_failed'):先定位失败 job/step,再按框架锚点萃取日志(pytest / node / Go / tsc / npm),最后汇总疑似相关文件。日志被截断时它会明确告诉你「总数未知」,不要当成「就这些失败」。",
|
|
96
96
|
},
|
|
97
97
|
{
|
|
98
98
|
title: "📌 场景三:早会与每日任务待办盘点",
|
|
@@ -160,21 +160,25 @@ export function GithubHelpModal(props) {
|
|
|
160
160
|
title: "2. 会话右侧栏面板 (Sidebar Panel) —— 深度协同当前工作区 · 5 个 Tab",
|
|
161
161
|
items: [
|
|
162
162
|
"打开入口:位于每个会话工作区的右侧抽屉栏,深度锁定当前会话的物理 Git 仓库;",
|
|
163
|
-
"仓库身份与切换:顶部展示当前关联仓库、Fork 血缘行与权限徽章(只读/可写);若配置了多个 remote,可通过 RepoSelectorDropdown 下拉一键切换基准仓库;", "Git Tab:三段式提交时间线(待推送 / 待拉取 / 最近),默认 15 条、下滑触底自动加载;点任意提交行弹出完整 Commit
|
|
164
|
-
"未提交 Tab:实时监听本地未提交文件与状态码(?? 新增 / M 修改 / D 删除 / R
|
|
163
|
+
"仓库身份与切换:顶部展示当前关联仓库、Fork 血缘行与权限徽章(只读/可写);若配置了多个 remote,可通过 RepoSelectorDropdown 下拉一键切换基准仓库;", "Git Tab:三段式提交时间线(待推送 / 待拉取 / 最近),默认 15 条、下滑触底自动加载;点任意提交行弹出完整 Commit 详情;提交详情里点「📁 文件变更统计」的任意文件行,可再叠一层该文件的 Unified Diff 预览(Esc 先关 Diff、再关详情);",
|
|
164
|
+
"未提交 Tab:实时监听本地未提交文件与状态码(?? 新增 / M 修改 / D 删除 / R 重命名),点击文件弹出该文件的 Unified Diff 弹窗(清单保留原位,可复制原文或在 DSH 中打开源码);",
|
|
165
165
|
"PR Tab:展示全部 PR 并按状态过滤;带三态 CI 门禁、冲突检测、审核判定与 Draft 标示;点击卡片在线唤起 PR 审阅工作台(描述 / 评论 / 文件 Diff 双栏比对);",
|
|
166
166
|
"Issues Tab:展示全部 Issue 并按状态过滤;点击卡片在线查看详情(Markdown 正文 / 评论时间线 / 标签色块 / 里程碑 / 关联 PR / 隐藏评论标注);",
|
|
167
|
-
"Actions Tab
|
|
167
|
+
"Actions Tab:最近工作流运行记录与触发方式;点击卡片在线查看本次运行的 job / step 树(每步结论与耗时,失败步骤高亮,默认展开第一个失败 job),失败时可按需提取日志锚点片段(一次最多 3 个失败 job、服务端批次预算 90 秒);",
|
|
168
168
|
"动静解耦架构:修改本地代码只定向查询本地 .git 核心元数据(耗时仅 5ms),绝对不发起远端网络请求,杜绝网络风暴。",
|
|
169
169
|
],
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
|
-
title: "3.
|
|
172
|
+
title: "3. 详情弹窗的通用操作习惯",
|
|
173
173
|
items: [
|
|
174
|
-
"PR / Issue
|
|
174
|
+
"PR / Issue / Actions 三个弹窗都支持:按 Esc 或点击遮罩关闭、右上角「GitHub ↗」直达官方页面;",
|
|
175
|
+
"文件 Diff 弹窗可以叠在提交详情之上:Esc 或点遮罩先关 Diff,再按一次才关提交详情;弹窗内可「📋 复制 Diff」原文或「📄 在 DSH 中打开」查看文件源码;",
|
|
176
|
+
"Diff 弹窗对正文里的 `---` / `+++` 不会误判成文件头(按 hunk 解析),重命名文件按新路径展示、二进制文件给明确提示;",
|
|
175
177
|
"列表卡片上的「GitHub ↗」按钮是独立的浏览器外跳入口,点它不会触发弹窗(已做事件隔离);",
|
|
176
178
|
"PR 弹窗的文件区可拖拽分隔条调宽、双击或点 ◀ 折叠;布局偏好会记在本地,下次打开保持原样;",
|
|
177
|
-
"长评论默认折叠(显示「⌄ 展开全部」),PR/Issue
|
|
179
|
+
"长评论默认折叠(显示「⌄ 展开全部」),PR/Issue 正文默认展开——避免一屏被一段超长文字占满;",
|
|
180
|
+
"Actions 弹窗按 job 折叠/展开步骤,默认展开第一个失败 job;「📄 提取失败日志」只在存在失败时出现,点一次拉一次(慢链路不做自动重试,失败可手动重试);",
|
|
181
|
+
"日志片段是按失败锚点萃取过的——看到的是「失败块 + 上下文」或尾部 200 行,不是完整日志;要全文请点「GitHub ↗」。",
|
|
178
182
|
],
|
|
179
183
|
},
|
|
180
184
|
].map(function (sec, idx) {
|
|
@@ -188,7 +192,7 @@ export function GithubHelpModal(props) {
|
|
|
188
192
|
items: [
|
|
189
193
|
{ name: "github_pr", desc: "PR 全生命周期:列表/详情/diff/checks/create/merge/review/ready/update_branch/revert/preview/checkout,含行级评论与附件上传" },
|
|
190
194
|
{ name: "github_issue", desc: "Issue 任务流转:list/view/create/comment/edit/close/reopen/develop/transfer/pin/lock/delete/status,含截图查看 view_images" },
|
|
191
|
-
{ name: "github_run", desc: "Actions 排障:list
|
|
195
|
+
{ name: "github_run", desc: "Actions 排障:list、view(run + job/step 树)、log_failed(失败步骤日志秒级提取)/rerun/cancel/trigger/download 与 workflow 启停" },
|
|
192
196
|
{ name: "github_repo", desc: "仓库元数据、免 clone 读文件(read_file/read_dir)、跨仓检索(code/issues/prs/commits)、Fork 同步" },
|
|
193
197
|
],
|
|
194
198
|
},
|
|
@@ -222,7 +226,7 @@ export function GithubHelpModal(props) {
|
|
|
222
226
|
],
|
|
223
227
|
},
|
|
224
228
|
];
|
|
225
|
-
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "
|
|
229
|
+
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "19 个 Agent 工具、合计 106 个业务动作(不含元指南的 22 个查询主题与 api 逃生门的 4 个 HTTP method)。你不必记住工具名——直接用自然语言描述目标,Agent 会自己挑工具。下表用于你了解能力边界,以及排查「为什么 Agent 说做不到」:"), toolGroups.map(function (g, gi) {
|
|
226
230
|
return h("div", { key: gi, className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "8px" } }, g.group), g.items.map(function (t, ti) {
|
|
227
231
|
return h("div", { key: ti, style: { display: "flex", gap: "10px", alignItems: "flex-start", padding: "5px 0", borderTop: ti === 0 ? "none" : "1px solid var(--dsw-alias-border-l1, rgba(255,255,255,0.06))" } }, h("code", {
|
|
228
232
|
style: {
|
|
@@ -262,6 +266,14 @@ export function GithubHelpModal(props) {
|
|
|
262
266
|
q: "Q6: 为什么改了代码,插件界面要重启 DSH 才生效?",
|
|
263
267
|
a: "如果你是在开发这个插件本身:后端 HTTP 路由是在插件 `apply()` 时一次性注册到宿主的 WebServer 上的,`pnpm run build` 只更新磁盘产物,不会给已运行的进程热注册新路由。改完代码需重启 DSH 才能挂上新端点。\n若只是普通使用,请忽略此条——正常升级走一键升级即可。",
|
|
264
268
|
},
|
|
269
|
+
{
|
|
270
|
+
q: "Q7: Actions 弹窗里点「提取失败日志」很久没反应,或报超时?",
|
|
271
|
+
a: "GitHub 的日志接口是「先打包再下载」,实测单次 4–40 秒,走代理时还可能被直接重置——这是上游链路特性,不是插件卡死。当前实现有三道闸:\n- 服务端单 job 上限 60 秒、整批总预算 90 秒;超预算的 job 会明确列出「未拉取,可单独重试」,不会静默丢掉;\n- 客户端 100 秒自动中止,避免按钮永远停在「正在下载日志…」;\n- 失败后弹窗内给「🔁 重试」,不做自动重试(重试会把等待时间叠加)。\n急的话点右上角「GitHub ↗」看完整日志,或让 Agent 调 `github_task(action:'diagnose_ci', run_id:'…')` 直接拿结论包。",
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
q: "Q8: 为什么 Actions 弹窗只能看 3 个失败 job 的日志?",
|
|
275
|
+
a: "一次点击串行拉 3 个 job 已经是 60–90 秒量级;矩阵构建下失败 job 可能有十几个,全拉会把一次点击变成几分钟。当前策略:只取前 3 个失败 job,并在按钮下方明示「共 N 个,其余请到 GitHub 查看」。需要逐个深挖时,让 Agent 用 `github_run(action:'log_failed', run_id:'…')` 或 `github_task(action:'diagnose_ci')` 分批处理更合适。",
|
|
276
|
+
},
|
|
265
277
|
];
|
|
266
278
|
return h("div", null, h("div", { className: "dsh-github-help-desc", style: { fontSize: "13px", marginBottom: "14px" } }, "常见环境、权限与网络问题的排查与自愈指引:"), faqs.map(function (f, idx) {
|
|
267
279
|
return h("div", { key: idx, className: "dsh-github-help-card" }, h("div", { className: "dsh-github-help-title", style: { marginBottom: "6px", fontSize: "13px" } }, f.q), h("div", { className: "dsh-github-help-desc", style: { lineHeight: "1.6", whiteSpace: "pre-line" } }, f.a));
|
package/lib/ui/issue-modal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { issueStateBadge } from '../shared/state-badges.js';
|
|
1
2
|
/**
|
|
2
3
|
* GitHub 关闭原因的状态语义映射。
|
|
3
4
|
* 实测 `stateReason` 取值域:COMPLETED / NOT_PLANNED / REOPENED / ''(空串)。
|
|
@@ -201,11 +202,14 @@ export function IssueDetailModal(props) {
|
|
|
201
202
|
var stateBadge = function (d) {
|
|
202
203
|
if (!d)
|
|
203
204
|
return null;
|
|
204
|
-
|
|
205
|
+
// 状态→色调来自 shared/state-badges(与右侧栏 / CLI 输出同一份映射)
|
|
206
|
+
var b = issueStateBadge(d.state);
|
|
207
|
+
if (!b)
|
|
208
|
+
return null;
|
|
205
209
|
return h("span", {
|
|
206
|
-
className: "dsh-github-badge
|
|
210
|
+
className: "dsh-github-badge dsh-badge-" + b.tone,
|
|
207
211
|
style: { fontSize: "10px", padding: "1px 6px", flexShrink: 0 },
|
|
208
|
-
},
|
|
212
|
+
}, b.label);
|
|
209
213
|
};
|
|
210
214
|
/**
|
|
211
215
|
* 元数据条:只渲染真实存在的字段。
|
package/lib/ui/pr-modal.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prStateBadge } from '../shared/state-badges.js';
|
|
1
2
|
/**
|
|
2
3
|
* 可折叠的 Markdown 内容块。
|
|
3
4
|
*
|
|
@@ -336,18 +337,36 @@ export function PrDetailModal(props) {
|
|
|
336
337
|
},
|
|
337
338
|
}, data ? `#${data.number} ${data.title}` : `PR #${prNumber}`), data
|
|
338
339
|
? h("span", {
|
|
340
|
+
// 色调来自 shared/state-badges(与右侧栏 / CLI 输出同一份映射);
|
|
341
|
+
// 文案保持原样(Draft 用大驼峰、其余直接展示 GitHub 原始 state)。
|
|
342
|
+
// 未知状态沿用旧实现的绿色兜底(旧代码的 else 分支就是 green)。
|
|
339
343
|
className: "dsh-github-badge " +
|
|
340
|
-
(
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
: data.state === "CLOSED"
|
|
345
|
-
? "dsh-badge-red"
|
|
346
|
-
: "dsh-badge-green"),
|
|
344
|
+
(function () {
|
|
345
|
+
var b = prStateBadge(data.state, data.isDraft);
|
|
346
|
+
return "dsh-badge-" + (b && b.tone !== "neutral" ? b.tone : "green");
|
|
347
|
+
})(),
|
|
347
348
|
style: { fontSize: "10px", padding: "1px 6px" },
|
|
348
349
|
}, data.isDraft ? "Draft" : data.state)
|
|
349
350
|
: null), data
|
|
350
|
-
? h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap" } }, h("span", null, `👤 @${data.author.login}`), data.createdAt ? h("span", null, `· 提单于 ${formatRelativeTime(data.createdAt)}`) : null, h("span", { style: { fontFamily: "monospace", opacity: 0.85 } }, `(${data.headRefName} → ${data.baseRefName})`)
|
|
351
|
+
? h("div", { style: { fontSize: "11px", color: "var(--dsw-alias-label-secondary, #656d76)", display: "flex", gap: "6px", alignItems: "center", flexWrap: "wrap" } }, h("span", null, `👤 @${data.author.login}`), data.createdAt ? h("span", null, `· 提单于 ${formatRelativeTime(data.createdAt)}`) : null, h("span", { style: { fontFamily: "monospace", opacity: 0.85 } }, `(${data.headRefName} → ${data.baseRefName})`),
|
|
352
|
+
/*
|
|
353
|
+
* 审查锚点:分支名会移动,SHA 不会。
|
|
354
|
+
* 点开这个弹窗时先看这一枚 chip,就能确认「我看到的是不是最新一次推送」;
|
|
355
|
+
* title 里放完整 SHA,方便复制去精确引用。老服务端没有该字段时不渲染。
|
|
356
|
+
*/
|
|
357
|
+
typeof data.headRefOid === "string" && data.headRefOid.length >= 7
|
|
358
|
+
? h("span", {
|
|
359
|
+
title: `headRefOid: ${data.headRefOid}`,
|
|
360
|
+
style: {
|
|
361
|
+
fontFamily: "monospace",
|
|
362
|
+
fontSize: "10px",
|
|
363
|
+
padding: "1px 6px",
|
|
364
|
+
borderRadius: "10px",
|
|
365
|
+
border: "1px solid var(--dsw-alias-border-l2, rgba(31,35,40,0.15))",
|
|
366
|
+
opacity: 0.9,
|
|
367
|
+
},
|
|
368
|
+
}, `🔖 ${data.headRefOid.slice(0, 7)}`)
|
|
369
|
+
: null)
|
|
351
370
|
: null)), h("div", { style: { display: "flex", alignItems: "center", gap: "6px", flexShrink: 0 } }, data?.url
|
|
352
371
|
? h("button", {
|
|
353
372
|
className: "dsh-github-btn-sm",
|
|
@@ -390,7 +409,10 @@ export function PrDetailModal(props) {
|
|
|
390
409
|
}, `💬 描述与讨论 (${data.comments ? data.comments.length : 0})`), h("button", {
|
|
391
410
|
className: `dsh-github-pr-tab-btn ${activeTab === "files" ? "active" : ""}`,
|
|
392
411
|
onClick: () => setActiveTab("files"),
|
|
393
|
-
},
|
|
412
|
+
},
|
|
413
|
+
// 截断时显示 N/M:M 来自 changedFiles(权威总数),N 是 gh 实际返回的条数。
|
|
414
|
+
// 只显示 N 会让「被截断」看起来像「就这么多」——这是 cli/cli#12522 的静默截断。
|
|
415
|
+
`📁 变动文件 (${data.files ? data.files.length : 0}${data.filesTruncated && data.changedFiles ? '/' + data.changedFiles : ''} 文件 · +${data.additions || 0} / -${data.deletions || 0})${data.filesTruncated ? ' ⚠️' : ''}`)),
|
|
394
416
|
// Tab 1: 描述与讨论
|
|
395
417
|
activeTab === "conversation"
|
|
396
418
|
? h("div", { className: "dsh-github-modal-body dsh-github-pr-conversation-pane" },
|
|
@@ -461,7 +483,9 @@ export function PrDetailModal(props) {
|
|
|
461
483
|
onClick: toggleFilePane,
|
|
462
484
|
style: { padding: "2px 8px", fontSize: "10px", flexShrink: 0 },
|
|
463
485
|
title: "展开左侧文件列表",
|
|
464
|
-
},
|
|
486
|
+
},
|
|
487
|
+
// 折叠态同样要透出截断事实(N/M),否则展开前的第一眼就被骗了
|
|
488
|
+
`📁 文件列表 (${(data.files && data.files.length) || 0}${data.filesTruncated && data.changedFiles ? '/' + data.changedFiles : ''})`)
|
|
465
489
|
: null, h("span", { style: { fontFamily: "monospace", fontWeight: 600, fontSize: "11px", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" } }, selectedFilePath), data.fileDiffs && data.fileDiffs[selectedFilePath]
|
|
466
490
|
? h("button", {
|
|
467
491
|
className: "dsh-github-btn-sm",
|
|
@@ -469,7 +493,9 @@ export function PrDetailModal(props) {
|
|
|
469
493
|
style: { padding: "2px 8px", fontSize: "10px" },
|
|
470
494
|
}, copied ? "✓ 已复制" : "📋 复制文件 Diff")
|
|
471
495
|
: null), h("div", { style: { flex: 1, overflowY: "auto", minHeight: 0 } },
|
|
472
|
-
//
|
|
496
|
+
// 两种「看不到 diff」的原因必须分开说,否则用户不知道该做什么:
|
|
497
|
+
// ① diffTruncated:整段 diff 被输出阈值裁掉(去 GitHub 看完整内容)
|
|
498
|
+
// ② diffIncomplete:这个文件在清单里但没取到切片(换一个文件/重试)
|
|
473
499
|
data.diffTruncated
|
|
474
500
|
? h("div", {
|
|
475
501
|
style: {
|
|
@@ -483,6 +509,19 @@ export function PrDetailModal(props) {
|
|
|
483
509
|
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
484
510
|
},
|
|
485
511
|
}, "⚠️ 该 PR 的 diff 体量过大已触达输出上限,部分文件可能无法展示差异。可点击右上角「↗ 浏览器打开」查看完整内容。")
|
|
512
|
+
: null, data.diffIncomplete
|
|
513
|
+
? h("div", {
|
|
514
|
+
style: {
|
|
515
|
+
margin: "8px",
|
|
516
|
+
padding: "8px 12px",
|
|
517
|
+
borderRadius: "6px",
|
|
518
|
+
fontSize: "11px",
|
|
519
|
+
lineHeight: 1.5,
|
|
520
|
+
background: "rgba(210, 153, 34, 0.12)",
|
|
521
|
+
border: "1px solid rgba(210, 153, 34, 0.35)",
|
|
522
|
+
color: "var(--dsw-alias-state-attention-primary, #9a6700)",
|
|
523
|
+
},
|
|
524
|
+
}, "⚠️ 有文件未能取到 diff 正文(二进制、重命名或超出切片范围)。这些文件显示为空不代表没有改动,请到 GitHub 核对。")
|
|
486
525
|
: null, data.fileDiffs && data.fileDiffs[selectedFilePath]
|
|
487
526
|
? renderDiffContent(data.fileDiffs[selectedFilePath])
|
|
488
527
|
: renderDiffContent(""),
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Actions run / job / step 的耗时格式化(导出以便单测直接覆盖)。
|
|
3
|
+
*
|
|
4
|
+
* 为什么不用 formatRelativeTime:那个函数答的是「发生在多久前」,这里是「跑了多久」;
|
|
5
|
+
* 而且 run → job → step 三层共用同一套精度规则(<60s → `12s`、<60m → `3m20s`、
|
|
6
|
+
* 其余 `1h05m`),分钟/秒补零才能让三层的列宽稳定对齐。
|
|
7
|
+
* 任一端缺失或不可解析一律返回空串:排队中/已跳过的 job 没有 startedAt/completedAt,
|
|
8
|
+
* 硬算只会渲染出「0s」这种假精确值,比不显示更误导。
|
|
9
|
+
*/
|
|
10
|
+
export declare function formatRunDuration(startISO?: string, endISO?: string): string;
|
|
11
|
+
export interface RunDetailModalProps {
|
|
12
|
+
runId: number;
|
|
13
|
+
repoNameWithOwner?: string;
|
|
14
|
+
cwd?: string | null;
|
|
15
|
+
onClose: () => void;
|
|
16
|
+
showToast?: (msg: string) => void;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Actions Run 详情弹窗(只读:run 元信息 + job/step 树 + 失败日志片段)。
|
|
20
|
+
*
|
|
21
|
+
* 单列纵向:run 的信息天然是「job 列表 + 一条日志流」的阅读顺序,没有需要并排
|
|
22
|
+
* 对照的第二个信息源(PR 才有 diff 双栏)。
|
|
23
|
+
* 日志刻意渲染成 <pre> 原文而不是 markdown-lite:原始日志里的 `#`、`|`、`---`
|
|
24
|
+
* 是 shell 注释、表格与 diff 分隔符,交给 Markdown 解析会把堆栈与 diff 撕碎。
|
|
25
|
+
*/
|
|
26
|
+
export declare function RunDetailModal(props: RunDetailModalProps): any;
|