zen-gitsync 2.17.10 → 2.17.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/src/ui/public/assets/AgentView-DJVwX8Er.js +5 -0
- package/src/ui/public/assets/AgentView-vvVhbIQ5.css +1 -0
- package/src/ui/public/assets/{AppVersionBadge-D5tJzJfa.css → AppVersionBadge-CJrDr7GV.css} +1 -1
- package/src/ui/public/assets/AppVersionBadge-rNwRlkEK.js +2 -0
- package/src/ui/public/assets/BranchSelector-B0eLkAdv.css +1 -0
- package/src/ui/public/assets/BranchSelector-D2JJINuw.js +1 -0
- package/src/ui/public/assets/CommitForm-CQ0K8Yy2.css +1 -0
- package/src/ui/public/assets/CommitForm-CayXdf3-.js +6 -0
- package/src/ui/public/assets/CommonDialog-DF5Vzvi8.js +1 -0
- package/src/ui/public/assets/CommonDialog-jP0E9mco.css +1 -0
- package/src/ui/public/assets/EditorView-Bv-3PWwF.js +1 -0
- package/src/ui/public/assets/EditorView-DZMLvQds.css +1 -0
- package/src/ui/public/assets/FileDiffViewer-B9EkuKdF.css +1 -0
- package/src/ui/public/assets/FileDiffViewer-CUsML3pW.js +15 -0
- package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +1 -0
- package/src/ui/public/assets/FlowExecutionViewer-n07dZgnE.js +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CYGp-PqS.css +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CdG8HRux.js +21 -0
- package/src/ui/public/assets/IconButton-GxK-nCF7.js +1 -0
- package/src/ui/public/assets/IconButton-tjkOjzkC.css +1 -0
- package/src/ui/public/assets/LogList-BlJplIi1.css +1 -0
- package/src/ui/public/assets/LogList-CpcpXaIZ.js +7 -0
- package/src/ui/public/assets/MindmapView-BHVJ6OzY.js +2 -0
- package/src/ui/public/assets/MindmapView-BQTqx842.css +1 -0
- package/src/ui/public/assets/MonitorView-DetlFFXq.css +1 -0
- package/src/ui/public/assets/MonitorView-IpfJRJaK.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-DM2gYw48.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-QLPltCK5.css +1 -0
- package/src/ui/public/assets/RemoteManagerDialog-BauAeH2w.css +1 -0
- package/src/ui/public/assets/RemoteManagerDialog-DLPnI8Tr.js +4 -0
- package/src/ui/public/assets/RemoteRepoCard-CG-jARTe.css +1 -0
- package/src/ui/public/assets/RemoteRepoCard-D804CRwk.js +7 -0
- package/src/ui/public/assets/SourceMapView-CN4Hkyp4.css +1 -0
- package/src/ui/public/assets/SourceMapView-Ct8J5WA4.js +3 -0
- package/src/ui/public/assets/{SvgIcon-BQ6bR7l0.css → SvgIcon-CDNsk4-Q.css} +1 -1
- package/src/ui/public/assets/SvgIcon-DRYmw37Q.js +1 -0
- package/src/ui/public/assets/TemplateManager-BGEG9EkP.js +1 -0
- package/src/ui/public/assets/TemplateManager-DMuG4i3G.css +1 -0
- package/src/ui/public/assets/UserInputNode-BVgQkB2l.css +1 -0
- package/src/ui/public/assets/UserInputNode-DO4QsmCx.js +1 -0
- package/src/ui/public/assets/WorkbenchView-B1BJhWHi.css +1 -0
- package/src/ui/public/assets/WorkbenchView-D4herpOg.js +51 -0
- package/src/ui/public/assets/_plugin-vue_export-helper-uGHPMZWt.js +19 -0
- package/src/ui/public/assets/configStore-DAZtECRW.js +1 -0
- package/src/ui/public/assets/element-plus-BJ5_9fFh.js +48 -0
- package/src/ui/public/assets/element-plus-C1PloHQu.css +1 -0
- package/src/ui/public/assets/flow-mindmap-9rLOb4W_.js +139 -0
- package/src/ui/public/assets/{gitStore-DMEDMZMk.js → gitStore-BQhrNfOL.js} +4 -4
- package/src/ui/public/assets/{index-CW3xs-af.css → index-B-c23Usy.css} +1 -1
- package/src/ui/public/assets/index-BM9Kz0P0.js +87 -0
- package/src/ui/public/assets/office-docx-BrfnjjUo.js +32 -0
- package/src/ui/public/assets/{office-excel-D-uYLKaA.js → office-excel-C08VoWqf.js} +1 -1
- package/src/ui/public/assets/office-pptx-Cs69NVuQ.js +74 -0
- package/src/ui/public/assets/{previewSandbox-B6Ud9g6a.css → previewSandbox-CgdwPcuW.css} +1 -1
- package/src/ui/public/assets/previewSandbox-D2nklUXY.js +0 -0
- package/src/ui/public/assets/socket-io-BFqQXqAp.js +1 -0
- package/src/ui/public/assets/{toolsStore-CpImRUhI.js → toolsStore-DX4sCU-U.js} +1 -1
- package/src/ui/public/assets/{useThemeObserver-CG8k8U18.js → useThemeObserver-chduV3hr.js} +1 -1
- package/src/ui/public/assets/{vendor-BIgZEtkx.js → vendor-xs0vOPEn.js} +46 -39
- package/src/ui/public/assets/vue-flow-DvidrbDz.js +39 -0
- package/src/ui/public/index.html +25 -25
- package/src/ui/server/routes/remoteRepos.js +73 -19
- package/src/ui/server/routes/remoteRepos.test.js +111 -1
- package/src/ui/server/routes/workbench/agentSessionStore.js +2 -2
- package/src/ui/server/routes/workbench/envContext.test.js +8 -8
- package/src/ui/server/routes/workbench/index.js +32 -736
- package/src/ui/server/routes/workbench/instructionStore.js +3 -113
- package/src/ui/server/routes/workbench/jobStore.js +2 -4
- package/src/ui/server/routes/workbench/jobStore.test.js +1 -1
- package/src/ui/server/routes/workbench/jsonParse.js +6 -143
- package/src/ui/server/routes/workbench/orchestratorStore.js +1 -8
- package/src/ui/server/routes/workbench/projectRegistry.js +6 -29
- package/src/ui/server/routes/workbench/projectRegistry.test.js +33 -56
- package/src/ui/server/routes/workbench/projectTool.js +0 -1
- package/src/ui/server/routes/workbench/projectTool.test.js +2 -3
- package/src/ui/server/routes/workbench/shared.js +2 -7
- package/src/ui/server/routes/workbench/taskRunner.js +18 -180
- package/src/ui/server/routes/workbench.js +4 -5
- package/src/ui/public/assets/AgentView-BV15UtEF.css +0 -1
- package/src/ui/public/assets/AgentView-C-AJo7KS.js +0 -5
- package/src/ui/public/assets/AppVersionBadge-B6R6eN2N.js +0 -2
- package/src/ui/public/assets/BranchSelector-Cj3kMWON.js +0 -1
- package/src/ui/public/assets/BranchSelector-T_rJm-JY.css +0 -1
- package/src/ui/public/assets/CommitForm-DpXuFG-F.js +0 -6
- package/src/ui/public/assets/CommitForm-Oij8R6P-.css +0 -1
- package/src/ui/public/assets/CommonDialog-BLPHDsn0.js +0 -1
- package/src/ui/public/assets/CommonDialog-C_RjF9Mp.css +0 -1
- package/src/ui/public/assets/EditorView-B1OnoXXf.js +0 -1
- package/src/ui/public/assets/EditorView-BgA-_8WV.css +0 -1
- package/src/ui/public/assets/FileDiffViewer-B2_ghtbv.css +0 -1
- package/src/ui/public/assets/FileDiffViewer-BQl0JcWd.js +0 -14
- package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +0 -1
- package/src/ui/public/assets/FlowExecutionViewer-BgEt5_Hz.js +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-BAQDU9Jd.css +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CbLmsSF8.js +0 -21
- package/src/ui/public/assets/IconButton-CTwyZrv2.css +0 -1
- package/src/ui/public/assets/IconButton-aM3vaSIN.js +0 -1
- package/src/ui/public/assets/LogList-DlWwSZet.css +0 -1
- package/src/ui/public/assets/LogList-UjoVFZ-H.js +0 -7
- package/src/ui/public/assets/MindmapView-CS2t30Ko.css +0 -1
- package/src/ui/public/assets/MindmapView-D_04v0n4.js +0 -1
- package/src/ui/public/assets/MonitorView-BD1_uzq7.js +0 -1
- package/src/ui/public/assets/MonitorView-CphDOlQu.css +0 -1
- package/src/ui/public/assets/ProjectStartupButton-B7zvqRj_.css +0 -1
- package/src/ui/public/assets/ProjectStartupButton-BTYlVvzh.js +0 -1
- package/src/ui/public/assets/RemoteManagerDialog-20mpQMwu.js +0 -1
- package/src/ui/public/assets/RemoteManagerDialog-bpwaVAvX.css +0 -1
- package/src/ui/public/assets/RemoteRepoCard-CDykPlPj.css +0 -1
- package/src/ui/public/assets/RemoteRepoCard-DOrm17Xd.js +0 -1
- package/src/ui/public/assets/SourceMapView-DjYKJZLj.css +0 -1
- package/src/ui/public/assets/SourceMapView-nUU9bYPh.js +0 -3
- package/src/ui/public/assets/SvgIcon-LUynX3Iq.js +0 -1
- package/src/ui/public/assets/TemplateManager-Bo3Q7J9Y.js +0 -1
- package/src/ui/public/assets/TemplateManager-C0rm8xfC.css +0 -1
- package/src/ui/public/assets/UserInputNode-D5tSXiJz.js +0 -1
- package/src/ui/public/assets/UserInputNode-DDtvv9Iq.css +0 -1
- package/src/ui/public/assets/WorkbenchView-C_Gmzvj1.css +0 -1
- package/src/ui/public/assets/WorkbenchView-UGC334JL.js +0 -13
- package/src/ui/public/assets/_plugin-vue_export-helper-D-D9qkRz.js +0 -19
- package/src/ui/public/assets/configStore-BLRcYJdf.js +0 -1
- package/src/ui/public/assets/element-plus-BWbXh9mk.css +0 -1
- package/src/ui/public/assets/element-plus-DcZ-TVp6.js +0 -28
- package/src/ui/public/assets/flow-mindmap-D1VTkZh3.js +0 -139
- package/src/ui/public/assets/index-S0_gRyiO.js +0 -26
- package/src/ui/public/assets/office-docx-C0g5Oepv.js +0 -31
- package/src/ui/public/assets/office-pptx-De3fnc6M.js +0 -60
- package/src/ui/public/assets/previewSandbox-DXAxqDVu.js +0 -0
- package/src/ui/public/assets/socket-io-TBtw0msS.js +0 -1
- package/src/ui/public/assets/vue-flow-CPCFgenl.js +0 -39
- package/src/ui/server/routes/workbench/sessionStore.js +0 -126
package/src/ui/public/index.html
CHANGED
|
@@ -10,37 +10,37 @@
|
|
|
10
10
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
11
11
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
|
12
12
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;1,400&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
|
|
13
|
-
<script type="module" crossorigin src="/assets/index-
|
|
13
|
+
<script type="module" crossorigin src="/assets/index-BM9Kz0P0.js"></script>
|
|
14
14
|
<link rel="modulepreload" crossorigin href="/assets/rolldown-runtime-C0FnF6B9.js">
|
|
15
|
-
<link rel="modulepreload" crossorigin href="/assets/element-plus-
|
|
15
|
+
<link rel="modulepreload" crossorigin href="/assets/element-plus-BJ5_9fFh.js">
|
|
16
16
|
<link rel="modulepreload" crossorigin href="/assets/monaco-DkJuNcCr.js">
|
|
17
|
-
<link rel="modulepreload" crossorigin href="/assets/vendor-
|
|
18
|
-
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-
|
|
19
|
-
<link rel="modulepreload" crossorigin href="/assets/SvgIcon-
|
|
20
|
-
<link rel="modulepreload" crossorigin href="/assets/IconButton-
|
|
21
|
-
<link rel="modulepreload" crossorigin href="/assets/configStore-
|
|
22
|
-
<link rel="modulepreload" crossorigin href="/assets/socket-io-
|
|
23
|
-
<link rel="modulepreload" crossorigin href="/assets/gitStore-
|
|
24
|
-
<link rel="modulepreload" crossorigin href="/assets/useThemeObserver-
|
|
25
|
-
<link rel="modulepreload" crossorigin href="/assets/flow-mindmap-
|
|
26
|
-
<link rel="modulepreload" crossorigin href="/assets/previewSandbox-
|
|
27
|
-
<link rel="modulepreload" crossorigin href="/assets/FileDiffViewer-
|
|
28
|
-
<link rel="modulepreload" crossorigin href="/assets/toolsStore-
|
|
29
|
-
<link rel="modulepreload" crossorigin href="/assets/CommonDialog-
|
|
30
|
-
<link rel="modulepreload" crossorigin href="/assets/TemplateManager-
|
|
31
|
-
<link rel="modulepreload" crossorigin href="/assets/vue-flow-
|
|
32
|
-
<link rel="stylesheet" crossorigin href="/assets/element-plus-
|
|
17
|
+
<link rel="modulepreload" crossorigin href="/assets/vendor-xs0vOPEn.js">
|
|
18
|
+
<link rel="modulepreload" crossorigin href="/assets/_plugin-vue_export-helper-uGHPMZWt.js">
|
|
19
|
+
<link rel="modulepreload" crossorigin href="/assets/SvgIcon-DRYmw37Q.js">
|
|
20
|
+
<link rel="modulepreload" crossorigin href="/assets/IconButton-GxK-nCF7.js">
|
|
21
|
+
<link rel="modulepreload" crossorigin href="/assets/configStore-DAZtECRW.js">
|
|
22
|
+
<link rel="modulepreload" crossorigin href="/assets/socket-io-BFqQXqAp.js">
|
|
23
|
+
<link rel="modulepreload" crossorigin href="/assets/gitStore-BQhrNfOL.js">
|
|
24
|
+
<link rel="modulepreload" crossorigin href="/assets/useThemeObserver-chduV3hr.js">
|
|
25
|
+
<link rel="modulepreload" crossorigin href="/assets/flow-mindmap-9rLOb4W_.js">
|
|
26
|
+
<link rel="modulepreload" crossorigin href="/assets/previewSandbox-D2nklUXY.js">
|
|
27
|
+
<link rel="modulepreload" crossorigin href="/assets/FileDiffViewer-CUsML3pW.js">
|
|
28
|
+
<link rel="modulepreload" crossorigin href="/assets/toolsStore-DX4sCU-U.js">
|
|
29
|
+
<link rel="modulepreload" crossorigin href="/assets/CommonDialog-DF5Vzvi8.js">
|
|
30
|
+
<link rel="modulepreload" crossorigin href="/assets/TemplateManager-BGEG9EkP.js">
|
|
31
|
+
<link rel="modulepreload" crossorigin href="/assets/vue-flow-DvidrbDz.js">
|
|
32
|
+
<link rel="stylesheet" crossorigin href="/assets/element-plus-C1PloHQu.css">
|
|
33
33
|
<link rel="stylesheet" crossorigin href="/assets/monaco-B2lPvkrc.css">
|
|
34
34
|
<link rel="stylesheet" crossorigin href="/assets/vendor-4ojsjDYc.css">
|
|
35
|
-
<link rel="stylesheet" crossorigin href="/assets/SvgIcon-
|
|
36
|
-
<link rel="stylesheet" crossorigin href="/assets/IconButton-
|
|
35
|
+
<link rel="stylesheet" crossorigin href="/assets/SvgIcon-CDNsk4-Q.css">
|
|
36
|
+
<link rel="stylesheet" crossorigin href="/assets/IconButton-tjkOjzkC.css">
|
|
37
37
|
<link rel="stylesheet" crossorigin href="/assets/flow-mindmap-DnzM9SlK.css">
|
|
38
|
-
<link rel="stylesheet" crossorigin href="/assets/previewSandbox-
|
|
39
|
-
<link rel="stylesheet" crossorigin href="/assets/FileDiffViewer-
|
|
40
|
-
<link rel="stylesheet" crossorigin href="/assets/CommonDialog-
|
|
41
|
-
<link rel="stylesheet" crossorigin href="/assets/TemplateManager-
|
|
38
|
+
<link rel="stylesheet" crossorigin href="/assets/previewSandbox-CgdwPcuW.css">
|
|
39
|
+
<link rel="stylesheet" crossorigin href="/assets/FileDiffViewer-B9EkuKdF.css">
|
|
40
|
+
<link rel="stylesheet" crossorigin href="/assets/CommonDialog-jP0E9mco.css">
|
|
41
|
+
<link rel="stylesheet" crossorigin href="/assets/TemplateManager-DMuG4i3G.css">
|
|
42
42
|
<link rel="stylesheet" crossorigin href="/assets/vue-flow-CavUTEzp.css">
|
|
43
|
-
<link rel="stylesheet" crossorigin href="/assets/index-
|
|
43
|
+
<link rel="stylesheet" crossorigin href="/assets/index-B-c23Usy.css">
|
|
44
44
|
</head>
|
|
45
45
|
<body>
|
|
46
46
|
<div id="app"></div>
|
|
@@ -38,6 +38,7 @@ import { spawn, spawnSync } from 'node:child_process';
|
|
|
38
38
|
import fs from 'node:fs/promises';
|
|
39
39
|
import os from 'node:os';
|
|
40
40
|
import path from 'node:path';
|
|
41
|
+
import { stripVTControlCharacters } from 'node:util';
|
|
41
42
|
import { asyncRoute, HttpError } from '../utils/asyncRoute.js';
|
|
42
43
|
import { getToolInstallers, launchCommandInTerminal, parseVersionOutput, publicInstallerInfo } from './fileOpen.js';
|
|
43
44
|
import { readRegistryPathDirs } from '../../../utils/shellPath.js';
|
|
@@ -226,14 +227,59 @@ export async function findCliExecutable(provider) {
|
|
|
226
227
|
|
|
227
228
|
// ── CLI 调用 ────────────────────────────────────────────────────────────────
|
|
228
229
|
|
|
230
|
+
/** 剥掉 ANSI 转义序列。CLI 一旦被"强制上色",我们拿到的就是带色码的文本 ——
|
|
231
|
+
* 那东西既不能 JSON.parse,也不能直接摆在界面上给用户看。 */
|
|
232
|
+
export function stripAnsi(text) {
|
|
233
|
+
return stripVTControlCharacters(String(text ?? ''));
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* "强制上色"这一族环境变量。它们**优先级高于 NO_COLOR**:
|
|
238
|
+
* 实测(2026-09-22)只设 `CLICOLOR_FORCE=1` 时,gh 连我们显式传下去的
|
|
239
|
+
* `NO_COLOR=1` 都不理,`gh repo list --json` 会输出**高亮 + 折行**的 JSON,
|
|
240
|
+
* 首行正是 `\x1b[1;37m[`。
|
|
241
|
+
* 后果是三重的、而且每一条都不报错:
|
|
242
|
+
* ① `parseRepoJson` 解不出来 → repos 空;
|
|
243
|
+
* ② `gh auth status` 里的账号名被色码包住 → user 抠不出来(null);
|
|
244
|
+
* ③ 界面上的"错误原因"变成 `\x1b[1;37m[\x1b[m` 这种乱码(用户截图来问的那种)。
|
|
245
|
+
* 所以不能只"设 NO_COLOR",必须把整族变量从子进程环境里删掉。
|
|
246
|
+
*/
|
|
247
|
+
const COLOR_FORCING_ENV_KEYS = ['CLICOLOR_FORCE', 'FORCE_COLOR', 'GH_FORCE_TTY', 'GITEE_FORCE_TTY'];
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* 拼子进程环境:先删掉"强制上色"整族变量,再显式声明不要颜色与非交互。
|
|
251
|
+
*
|
|
252
|
+
* 为什么要按 key 遍历删除而不是 `delete env.CLICOLOR_FORCE`:
|
|
253
|
+
* Windows 上环境变量名**不区分大小写**,而 `process.env` 的 key 保留原样 ——
|
|
254
|
+
* `clicolor_force` / `gh_force_tty` 这种小写写法会被大写 delete 漏掉。
|
|
255
|
+
* (`...overrides` 放在最后是刻意的:调用方显式指定的变量应当说话算数。)
|
|
256
|
+
*/
|
|
257
|
+
export function buildCliEnv(overrides = {}) {
|
|
258
|
+
const env = { ...process.env };
|
|
259
|
+
for (const key of Object.keys(env)) {
|
|
260
|
+
if (COLOR_FORCING_ENV_KEYS.includes(key.toUpperCase())) delete env[key];
|
|
261
|
+
}
|
|
262
|
+
return {
|
|
263
|
+
...env,
|
|
264
|
+
NO_COLOR: '1',
|
|
265
|
+
CLICOLOR: '0',
|
|
266
|
+
GH_PROMPT_DISABLED: '1',
|
|
267
|
+
GH_NO_UPDATE_NOTIFIER: '1',
|
|
268
|
+
GITEE_NO_UPDATE_NOTIFIER: '1',
|
|
269
|
+
GIT_TERMINAL_PROMPT: '0',
|
|
270
|
+
...overrides,
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
|
|
229
274
|
/**
|
|
230
275
|
* 跑一条 CLI 命令并把 stdout 收全。
|
|
231
276
|
*
|
|
232
277
|
* 全程非交互:GH_PROMPT_DISABLED / GIT_TERMINAL_PROMPT=0 保证需要凭据时
|
|
233
278
|
* **快速失败而不是挂住**(挂住会连带前端轮询一起卡死,与 directoryFetch.js
|
|
234
279
|
* 里 fetch 的处理是同一个考虑)。
|
|
235
|
-
*
|
|
236
|
-
*
|
|
280
|
+
* 颜色:见 buildCliEnv(删掉强制上色变量)+ finish() 里再兜一层 stripAnsi ——
|
|
281
|
+
* 万一将来某个 CLI 不认 NO_COLOR,我们也绝不会把色码当数据用。
|
|
282
|
+
* GITEE_NO_UPDATE_NOTIFIER:不要让每次调用都去查一次更新。
|
|
237
283
|
*
|
|
238
284
|
* Windows 上 npm 装出来的是 .cmd 垫片,不能直接 spawn,必须过一层 shell
|
|
239
285
|
* (这里的 args 全部是服务端硬编码常量,不含任何用户输入)。
|
|
@@ -245,25 +291,19 @@ export function runCli(executable, args, { timeoutMs = CLI_TIMEOUT_MS, env = {}
|
|
|
245
291
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
246
292
|
windowsHide: true,
|
|
247
293
|
shell: useShell,
|
|
248
|
-
env:
|
|
249
|
-
...process.env,
|
|
250
|
-
NO_COLOR: '1',
|
|
251
|
-
GH_PROMPT_DISABLED: '1',
|
|
252
|
-
GH_NO_UPDATE_NOTIFIER: '1',
|
|
253
|
-
GITEE_NO_UPDATE_NOTIFIER: '1',
|
|
254
|
-
GIT_TERMINAL_PROMPT: '0',
|
|
255
|
-
...env,
|
|
256
|
-
},
|
|
294
|
+
env: buildCliEnv(env),
|
|
257
295
|
});
|
|
258
296
|
|
|
259
297
|
let stdout = '';
|
|
260
298
|
let stderr = '';
|
|
261
299
|
let done = false;
|
|
300
|
+
// 色码必须在**收全之后**再剥:按 chunk 剥会把跨 chunk 的转义序列切坏
|
|
301
|
+
// (前一个 chunk 停在 `\x1b[`,后一个 chunk 从 `1;37m` 开始)。
|
|
262
302
|
const finish = (code, timedOut) => {
|
|
263
303
|
if (done) return;
|
|
264
304
|
done = true;
|
|
265
305
|
try { child.kill('SIGKILL'); } catch {}
|
|
266
|
-
resolve({ code, stdout, stderr, timedOut: !!timedOut });
|
|
306
|
+
resolve({ code, stdout: stripAnsi(stdout), stderr: stripAnsi(stderr), timedOut: !!timedOut });
|
|
267
307
|
};
|
|
268
308
|
|
|
269
309
|
child.stdout.on('data', (chunk) => { stdout += chunk; });
|
|
@@ -277,12 +317,20 @@ export function runCli(executable, args, { timeoutMs = CLI_TIMEOUT_MS, env = {}
|
|
|
277
317
|
});
|
|
278
318
|
}
|
|
279
319
|
|
|
280
|
-
/**
|
|
281
|
-
|
|
320
|
+
/**
|
|
321
|
+
* 从 CLI 输出里挑一句能给用户看的话(gitee 把错误打在 stdout,gh 打在 stderr)。
|
|
322
|
+
*
|
|
323
|
+
* 跳过"剥掉色码后只剩标点/符号"的行:被高亮过的 JSON 压掉色码后可能只剩一个
|
|
324
|
+
* `[`,拿它当错误提示等于没说 —— 界面上就会出现用户完全看不懂的一行(实测踩到)。
|
|
325
|
+
*/
|
|
326
|
+
export function firstMeaningfulLine(...texts) {
|
|
282
327
|
for (const text of texts) {
|
|
283
328
|
if (!text) continue;
|
|
284
|
-
for (const
|
|
285
|
-
|
|
329
|
+
for (const raw of String(text).split(/\r?\n/)) {
|
|
330
|
+
const line = stripAnsi(raw).trim();
|
|
331
|
+
if (!line) continue;
|
|
332
|
+
if (!/[\p{L}\p{N}]/u.test(line)) continue;
|
|
333
|
+
return line;
|
|
286
334
|
}
|
|
287
335
|
}
|
|
288
336
|
return '';
|
|
@@ -290,13 +338,19 @@ function firstMeaningfulLine(...texts) {
|
|
|
290
338
|
|
|
291
339
|
/** 宽松 JSON 解析:容忍 CLI 在 JSON 前面打警告/更新提示行。
|
|
292
340
|
* 只定位第一个 `[` 或 `{` 再整体交给 JSON.parse —— 不做任何字符串清洗,
|
|
293
|
-
* 解析不出来就是 null,由调用方决定怎么降级。
|
|
341
|
+
* 解析不出来就是 null,由调用方决定怎么降级。
|
|
342
|
+
*
|
|
343
|
+
* 唯一"清洗"就是剥 ANSI:被强制上色的 `--json` 输出是**带色码的多行 JSON**,
|
|
344
|
+
* 色码不影响结构,剥掉就能正常解析(2026-09-22 实测:CLICOLOR_FORCE=1 时
|
|
345
|
+
* gh 输出首行是 `\x1b[1;37m[`)。runCli 出口已经剥过一遍,这里再兜一层,
|
|
346
|
+
* 保证任何直接调用本函数的路径都不会被色码坑到。 */
|
|
294
347
|
export function parseJsonLoose(text) {
|
|
295
348
|
if (!text) return null;
|
|
296
|
-
const
|
|
349
|
+
const plain = stripAnsi(text);
|
|
350
|
+
const start = plain.search(/[[{]/);
|
|
297
351
|
if (start < 0) return null;
|
|
298
352
|
try {
|
|
299
|
-
return JSON.parse(
|
|
353
|
+
return JSON.parse(plain.slice(start));
|
|
300
354
|
} catch {
|
|
301
355
|
return null;
|
|
302
356
|
}
|
|
@@ -14,17 +14,23 @@
|
|
|
14
14
|
//
|
|
15
15
|
// src/ui/server/routes/remoteRepos.js 单元测试。
|
|
16
16
|
//
|
|
17
|
-
//
|
|
17
|
+
// 重点覆盖三处**实测踩过的坑**,它们都不会报错、只会静默给出错误结果:
|
|
18
18
|
// 1. `gitee auth status` 未登录时退出码是 0 —— 用退出码判会永远认为已登录
|
|
19
19
|
// 2. `parseRepoJson` 对纯对象(如 auth status 的 JSON)返回 null ——
|
|
20
20
|
// 拿它去解登录态会让解析悄悄退化成文本匹配,登录后拿不到用户名
|
|
21
|
+
// 3. 环境里带了"强制上色"变量(如 CLICOLOR_FORCE=1)时,CLI 的 --json 输出
|
|
22
|
+
// 会带 ANSI 色码 —— JSON 解不出来、账号名抠不出来,界面上只剩 `\x1b[1;37m[`
|
|
21
23
|
// 另外覆盖四条路由分支(未安装 / 未登录 / 已登录 / 拉取失败)与 provider 校验。
|
|
22
24
|
//
|
|
23
25
|
// 不真起 Express:沿用 branchStatus.test.js 的 mock app + mock req/res 做法。
|
|
24
26
|
// CLI 调用通过 registerRemoteReposRoutes 的 *Impl 注入口换成假实现,不联网、不装 gh。
|
|
27
|
+
// 唯独"色码"那几条必须真起子进程 —— 用 node 自己当被测 CLI(process.execPath),
|
|
28
|
+
// 不依赖机器上有没有装 gh。
|
|
25
29
|
import { test } from 'node:test'
|
|
26
30
|
import assert from 'node:assert/strict'
|
|
27
31
|
import {
|
|
32
|
+
buildCliEnv,
|
|
33
|
+
firstMeaningfulLine,
|
|
28
34
|
interpretGiteeAuthStatus,
|
|
29
35
|
interpretGithubAuthStatus,
|
|
30
36
|
normalizeGiteeRepos,
|
|
@@ -33,8 +39,17 @@ import {
|
|
|
33
39
|
parseRepoJson,
|
|
34
40
|
pickUserName,
|
|
35
41
|
registerRemoteReposRoutes,
|
|
42
|
+
runCli,
|
|
43
|
+
stripAnsi,
|
|
36
44
|
} from './remoteRepos.js'
|
|
37
45
|
|
|
46
|
+
const ESC = '\u001b'
|
|
47
|
+
|
|
48
|
+
/** 用 node 自己当"被测 CLI":跑一段内联脚本,把 stdout 收回来。 */
|
|
49
|
+
function runNode(code, options) {
|
|
50
|
+
return runCli(process.execPath, ['-e', code], options)
|
|
51
|
+
}
|
|
52
|
+
|
|
38
53
|
/** 最小 express app mock:只实现 get + post 与 handler 调用 */
|
|
39
54
|
function makeApp() {
|
|
40
55
|
const handlers = new Map()
|
|
@@ -193,6 +208,101 @@ test('normalizeGiteeRepos: private 缺失时用 public 反推;url 缺失时用 f
|
|
|
193
208
|
assert.equal(explicit.isPrivate, true)
|
|
194
209
|
})
|
|
195
210
|
|
|
211
|
+
// ── 色码(ANSI)—— 强制上色的环境会把解析和提示一起搞坏 ───────────────────────
|
|
212
|
+
//
|
|
213
|
+
// 实测(2026-09-22):只要启动服务的那个终端里带着 CLICOLOR_FORCE=1,gh 就**不理**
|
|
214
|
+
// 我们传下去的 NO_COLOR=1,`gh repo list --json` 会吐**高亮 + 折行**的 JSON:
|
|
215
|
+
// \x1b[1;37m[\x1b[m
|
|
216
|
+
// \x1b[1;37m{\x1b[m
|
|
217
|
+
// \x1b[1;34m"name"\x1b[m\x1b[1;37m:\x1b[m \x1b[32m"zen-gitsync"\x1b[m
|
|
218
|
+
// 于是:① repos 空(JSON.parse 失败)② user 为 null(账号名被色码包住)
|
|
219
|
+
// ③ 界面上的"错误原因"变成 `\x1b[1;37m[\x1b[m` 这种乱码。
|
|
220
|
+
|
|
221
|
+
test('stripAnsi: 剥掉 SGR 与光标类转义序列', () => {
|
|
222
|
+
assert.equal(stripAnsi(`${ESC}[1;37m[${ESC}[m`), '[')
|
|
223
|
+
assert.equal(stripAnsi('plain text'), 'plain text')
|
|
224
|
+
assert.equal(stripAnsi(`${ESC}[2K${ESC}[1Aabc`), 'abc')
|
|
225
|
+
assert.equal(stripAnsi(null), '')
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
test('buildCliEnv: 继承来的"强制上色"变量一律清掉,并显式关色(Windows 上大小写不敏感)', () => {
|
|
229
|
+
process.env.CLICOLOR_FORCE = '1'
|
|
230
|
+
process.env.gh_force_tty = '100%'
|
|
231
|
+
process.env.FORCE_COLOR = '3'
|
|
232
|
+
try {
|
|
233
|
+
const env = buildCliEnv()
|
|
234
|
+
assert.equal(env.CLICOLOR_FORCE, undefined)
|
|
235
|
+
// 小写写法必须一起清 —— Windows 上环境变量名不区分大小写,
|
|
236
|
+
// 只 delete 大写那种写法会漏掉它,而漏掉就等于没修。
|
|
237
|
+
assert.equal(env.gh_force_tty, undefined)
|
|
238
|
+
assert.equal(env.GH_FORCE_TTY, undefined)
|
|
239
|
+
assert.equal(env.FORCE_COLOR, undefined)
|
|
240
|
+
assert.equal(env.NO_COLOR, '1')
|
|
241
|
+
assert.equal(env.CLICOLOR, '0')
|
|
242
|
+
// 其它环境变量照旧带下去(子进程要能拿到 PATH 等)
|
|
243
|
+
assert.equal(env.PATH, process.env.PATH)
|
|
244
|
+
} finally {
|
|
245
|
+
delete process.env.CLICOLOR_FORCE
|
|
246
|
+
delete process.env.gh_force_tty
|
|
247
|
+
delete process.env.FORCE_COLOR
|
|
248
|
+
}
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
test('runCli: 父进程带着强制上色变量时,子进程既看不到它、输出里的色码也被剥掉', async () => {
|
|
252
|
+
const code = 'process.stdout.write(String(process.env.CLICOLOR_FORCE) + "|" + String(process.env.NO_COLOR) + "|" + String(process.env.GH_FORCE_TTY))'
|
|
253
|
+
process.env.CLICOLOR_FORCE = '1'
|
|
254
|
+
process.env.GH_FORCE_TTY = '80'
|
|
255
|
+
try {
|
|
256
|
+
const res = await runNode(code)
|
|
257
|
+
assert.equal(res.code, 0)
|
|
258
|
+
assert.equal(res.stdout, 'undefined|1|undefined')
|
|
259
|
+
} finally {
|
|
260
|
+
delete process.env.CLICOLOR_FORCE
|
|
261
|
+
delete process.env.GH_FORCE_TTY
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// 色码在出口处统一剥掉(按 chunk 剥会把跨 chunk 的转义序列切坏,所以必须在收全后剥)
|
|
265
|
+
const colored = await runNode(`process.stdout.write(${JSON.stringify(`${ESC}[1;37m[${ESC}[m\nabc\n`)})`)
|
|
266
|
+
assert.equal(colored.stdout, '[\nabc\n')
|
|
267
|
+
|
|
268
|
+
// 同样适用于 stderr —— 报错信息是要直接摆到界面上的
|
|
269
|
+
const coloredErr = await runNode(`process.stderr.write(${JSON.stringify(`${ESC}[31mHTTP 401: Bad credentials${ESC}[m\n`)})`)
|
|
270
|
+
assert.equal(coloredErr.stderr, 'HTTP 401: Bad credentials\n')
|
|
271
|
+
})
|
|
272
|
+
|
|
273
|
+
test('runCli + interpretGithubAuthStatus: 彩色 auth status 也能抠出账号名', async () => {
|
|
274
|
+
// 没有这一层修复时,账号名被 \x1b[1;37m 包住 → 正则匹配不到 → user 为 null,
|
|
275
|
+
// 界面显示"已登录 "后面空一格(users 看不到自己是谁)。
|
|
276
|
+
const output = `github.com\n ${ESC}[32m✓${ESC}[m Logged in to github.com account ${ESC}[1;37mxz333221${ESC}[m (keyring)\n`
|
|
277
|
+
const res = await runNode(`process.stdout.write(${JSON.stringify(`${output}\n`)})`)
|
|
278
|
+
assert.deepEqual(interpretGithubAuthStatus(res.code, res.stdout), {
|
|
279
|
+
authenticated: true,
|
|
280
|
+
user: 'xz333221',
|
|
281
|
+
})
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
test('parseJsonLoose: 高亮过的多行 JSON 剥掉色码后照常解析(兜底,不依赖 runCli)', () => {
|
|
285
|
+
const colored = [
|
|
286
|
+
`${ESC}[1;37m[${ESC}[m`,
|
|
287
|
+
` ${ESC}[1;37m{${ESC}[m`,
|
|
288
|
+
` ${ESC}[1;34m"name"${ESC}[m${ESC}[1;37m:${ESC}[m ${ESC}[32m"zen-gitsync"${ESC}[m`,
|
|
289
|
+
` ${ESC}[1;37m}${ESC}[m`,
|
|
290
|
+
`${ESC}[1;37m]${ESC}[m`,
|
|
291
|
+
].join('\n')
|
|
292
|
+
assert.deepEqual(parseJsonLoose(colored), [{ name: 'zen-gitsync' }])
|
|
293
|
+
assert.deepEqual(parseRepoJson(colored), [{ name: 'zen-gitsync' }])
|
|
294
|
+
})
|
|
295
|
+
|
|
296
|
+
test('firstMeaningfulLine: 剥掉色码后只剩标点的行不算"能给用户看的话"', () => {
|
|
297
|
+
// 用户看到的那行乱码正是这一条:整段输出剥完色码只剩 [,拿它当错误提示等于没说
|
|
298
|
+
assert.equal(firstMeaningfulLine('', `${ESC}[1;37m[${ESC}[m`), '')
|
|
299
|
+
assert.equal(
|
|
300
|
+
firstMeaningfulLine(`${ESC}[1;37m[${ESC}[m`, 'HTTP 401: Bad credentials (https://api.github.com/graphql)'),
|
|
301
|
+
'HTTP 401: Bad credentials (https://api.github.com/graphql)',
|
|
302
|
+
)
|
|
303
|
+
assert.equal(firstMeaningfulLine(''), '')
|
|
304
|
+
})
|
|
305
|
+
|
|
196
306
|
// ── 路由分支 ────────────────────────────────────────────────────────────────
|
|
197
307
|
|
|
198
308
|
test('GET /api/remote-repos: provider 非法 → 400', async () => {
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
//
|
|
15
15
|
// 智能体对话 session 持久化层。
|
|
16
16
|
//
|
|
17
|
-
//
|
|
17
|
+
// 这里存储的是完整 agent 对话(含工具调用)—— 智能体面板专用,与任务执行链路的 job 记录互不相干。
|
|
18
18
|
// 消息格式兼容 OpenAI chat completions:
|
|
19
19
|
// - { role: 'system', content: string }
|
|
20
20
|
// - { role: 'user', content: string | content_parts[] }
|
|
@@ -29,7 +29,7 @@ import path from 'path';
|
|
|
29
29
|
import { logger } from './shared.js';
|
|
30
30
|
import { AGENT_SESSIONS_DIR } from '../../../../paths.js';
|
|
31
31
|
|
|
32
|
-
//
|
|
32
|
+
// 数据目录定义见 src/paths.js
|
|
33
33
|
export { AGENT_SESSIONS_DIR };
|
|
34
34
|
// 历史遗留别名:此前这里有个"从 import.meta.url 推路径"的错误实现(Windows 下不可靠),
|
|
35
35
|
// 后来补了个 _FIXED 版本两套并存。现在统一到 paths.js 的单一常量。
|
|
@@ -57,11 +57,11 @@ test('项目清单带名称、路径与三列计数,并标出当前项目', ()
|
|
|
57
57
|
const projects = [entry('D:\\ws\\zen-gitsync'), entry('D:\\ws\\article-generator')];
|
|
58
58
|
const tasks = [
|
|
59
59
|
// 已完成:simple 任务跑过一次且 job 是 done
|
|
60
|
-
{ id: 't1', projectPath: 'D:\\ws\\zen-gitsync', type: 'simple'
|
|
60
|
+
{ id: 't1', projectPath: 'D:\\ws\\zen-gitsync', type: 'simple' },
|
|
61
61
|
// 进行中:有 job 处于 running
|
|
62
|
-
{ id: 't2', projectPath: 'D:\\ws\\article-generator', type: 'simple'
|
|
62
|
+
{ id: 't2', projectPath: 'D:\\ws\\article-generator', type: 'simple' },
|
|
63
63
|
// 待处理:没子任务也没执行过
|
|
64
|
-
{ id: 't3', projectPath: 'D:\\ws\\article-generator', type: 'simple'
|
|
64
|
+
{ id: 't3', projectPath: 'D:\\ws\\article-generator', type: 'simple' },
|
|
65
65
|
];
|
|
66
66
|
const jobs = [job('j1', 't1', 'done'), job('j2', 't2', 'running')];
|
|
67
67
|
|
|
@@ -106,7 +106,7 @@ test('项目数超过上限时只列前 N 个,但合计仍按全量算', () =>
|
|
|
106
106
|
for (let i = 0; i < total; i += 1) {
|
|
107
107
|
const p = `D:\\ws\\p${i}`;
|
|
108
108
|
projects.push(entry(p));
|
|
109
|
-
tasks.push({ id: `t${i}`, projectPath: p, type: 'simple'
|
|
109
|
+
tasks.push({ id: `t${i}`, projectPath: p, type: 'simple' });
|
|
110
110
|
}
|
|
111
111
|
|
|
112
112
|
const block = buildEnvContextBlock({
|
|
@@ -133,8 +133,8 @@ test('maxProjects 可覆盖,且非法值回落到默认上限', () => {
|
|
|
133
133
|
test('没关联项目的任务不计入项目行,但计入合计', () => {
|
|
134
134
|
const projects = [entry('D:\\ws\\a')];
|
|
135
135
|
const tasks = [
|
|
136
|
-
{ id: 't1', projectPath: 'D:\\ws\\a', type: 'simple'
|
|
137
|
-
{ id: 't2', projectPath: '', type: 'simple'
|
|
136
|
+
{ id: 't1', projectPath: 'D:\\ws\\a', type: 'simple' },
|
|
137
|
+
{ id: 't2', projectPath: '', type: 'simple' },
|
|
138
138
|
];
|
|
139
139
|
const block = buildEnvContextBlock({ projects, tasks, jobs: [], ...PATHS });
|
|
140
140
|
// 项目行只认 key 匹配得上的那条(t1)→ 待处理 1
|
|
@@ -153,8 +153,8 @@ test('没关联项目的任务不计入项目行,但计入合计', () => {
|
|
|
153
153
|
test('项目行/合计的段数必须与 TASK_COLUMNS 一致,且每列都有中文标签', () => {
|
|
154
154
|
const projects = [entry('D:\\ws\\a')];
|
|
155
155
|
const tasks = [
|
|
156
|
-
{ id: 't1', projectPath: 'D:\\ws\\a', type: 'simple'
|
|
157
|
-
{ id: 't2', projectPath: 'D:\\ws\\a', type: 'simple'
|
|
156
|
+
{ id: 't1', projectPath: 'D:\\ws\\a', type: 'simple' },
|
|
157
|
+
{ id: 't2', projectPath: 'D:\\ws\\a', type: 'simple' },
|
|
158
158
|
];
|
|
159
159
|
const block = buildEnvContextBlock({ projects, tasks, jobs: [], ...PATHS });
|
|
160
160
|
|