zen-gitsync 2.17.16 → 2.17.18
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.md +2 -0
- package/package.json +1 -1
- package/src/paths.js +13 -0
- package/src/ui/public/assets/AgentView-AMFKSDeH.css +1 -0
- package/src/ui/public/assets/AgentView-DQAB0ypS.js +13 -0
- package/src/ui/public/assets/AppVersionBadge-B5VG1_Lu.js +2 -0
- package/src/ui/public/assets/AppVersionBadge-DDYQqoy7.css +1 -0
- package/src/ui/public/assets/BranchSelector-Cj6athRO.js +1 -0
- package/src/ui/public/assets/BranchSelector-F4ibldMN.css +1 -0
- package/src/ui/public/assets/CommitForm-4kI2csgs.css +1 -0
- package/src/ui/public/assets/CommitForm-a2tMsfF9.js +6 -0
- package/src/ui/public/assets/CommonDialog-I-GCqRMC.js +1 -0
- package/src/ui/public/assets/CommonDialog-w6EmBEXv.css +1 -0
- package/src/ui/public/assets/EditorView-DBqhjm84.css +1 -0
- package/src/ui/public/assets/EditorView-wTi53Nxc.js +16 -0
- package/src/ui/public/assets/FileDiffViewer-B7i4BmbH.js +15 -0
- package/src/ui/public/assets/FileDiffViewer-n5ea9qMe.css +1 -0
- package/src/ui/public/assets/{FlowExecutionViewer-Drw4hlJG.js → FlowExecutionViewer-xZjrf8e1.js} +1 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-DwuSSxJ1.css +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-R0QKmUpG.js +21 -0
- package/src/ui/public/assets/IconButton-B6eqvssJ.js +1 -0
- package/src/ui/public/assets/IconButton-RlXqKOeu.css +1 -0
- package/src/ui/public/assets/LogList-BPqurE9g.js +7 -0
- package/src/ui/public/assets/LogList-XspUhx-C.css +1 -0
- package/src/ui/public/assets/MindmapView-Diq2Cu4O.css +1 -0
- package/src/ui/public/assets/MindmapView-NdFNlkTF.js +2 -0
- package/src/ui/public/assets/MonitorView-DuSZa8a3.js +1 -0
- package/src/ui/public/assets/MonitorView-DwUwJXnY.css +1 -0
- package/src/ui/public/assets/ProjectStartupButton-BrfKVP-w.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-CB8IOg1E.css +1 -0
- package/src/ui/public/assets/RemoteManagerDialog-BI7fRsOQ.js +4 -0
- package/src/ui/public/assets/RemoteManagerDialog-D4WphMtO.css +1 -0
- package/src/ui/public/assets/RemoteRepoCard-B4E8Ud3F.js +7 -0
- package/src/ui/public/assets/RemoteRepoCard-o01IrbVz.css +1 -0
- package/src/ui/public/assets/SourceMapView-B0OzHjxP.js +3 -0
- package/src/ui/public/assets/SourceMapView-CfDydtsu.css +1 -0
- package/src/ui/public/assets/SvgIcon-B2h7B2yF.js +1 -0
- package/src/ui/public/assets/TemplateManager-DO7EPfj8.css +1 -0
- package/src/ui/public/assets/TemplateManager-mIWE3Qf8.js +1 -0
- package/src/ui/public/assets/UserInputNode-BafC-B2U.js +1 -0
- package/src/ui/public/assets/UserInputNode-DDNSarag.css +1 -0
- package/src/ui/public/assets/WorkbenchView-BU2kJCAA.css +1 -0
- package/src/ui/public/assets/WorkbenchView-BseGz8hJ.js +56 -0
- package/src/ui/public/assets/{_plugin-vue_export-helper-dg2VUnSQ.js → _plugin-vue_export-helper-Dvuzpv2Y.js} +7 -7
- package/src/ui/public/assets/agentConversations-BEKqkGcm.js +5 -0
- package/src/ui/public/assets/{configStore-DtjAc3w9.js → configStore-CX0FkQ94.js} +1 -1
- package/src/ui/public/assets/element-plus-C1xzgNSR.css +1 -0
- package/src/ui/public/assets/{element-plus-DxBPQwL7.js → element-plus-D7LDHcAn.js} +3 -3
- package/src/ui/public/assets/flow-mindmap-zXXF3qDh.js +139 -0
- package/src/ui/public/assets/gitStore-CMuWWr2d.js +6 -0
- package/src/ui/public/assets/index-CB2cIiHl.js +115 -0
- package/src/ui/public/assets/index-DIU5200G.css +1 -0
- package/src/ui/public/assets/{office-docx-CZXLkCf5.js → office-docx-C8sYW8O1.js} +5 -5
- package/src/ui/public/assets/{office-excel-CBKszjVA.js → office-excel-DpZn5My2.js} +1 -1
- package/src/ui/public/assets/{office-pptx-NeUSnN3k.js → office-pptx-BnYBTwT7.js} +1 -1
- package/src/ui/public/assets/previewSandbox-CxLaNg4X.css +1 -0
- package/src/ui/public/assets/previewSandbox-DxR4iN9P.js +0 -0
- package/src/ui/public/assets/toolsStore-DMCSd287.js +1 -0
- package/src/ui/public/assets/{useThemeObserver-E7Hk50l3.js → useThemeObserver-QpveGpnn.js} +1 -1
- package/src/ui/public/assets/{vendor-DnB9EJa3.css → vendor-C-GCxMMu.css} +1 -1
- package/src/ui/public/assets/{vendor-bdHhLiED.js → vendor-CLmPUj3K.js} +8 -8
- package/src/ui/public/assets/vue-flow-CxnoC7Uq.js +39 -0
- package/src/ui/public/fonts/jetbrains-mono-latin.woff2 +0 -0
- package/src/ui/public/fonts/plus-jakarta-sans-latin-ext.woff2 +0 -0
- package/src/ui/public/fonts/plus-jakarta-sans-latin-italic.woff2 +0 -0
- package/src/ui/public/fonts/plus-jakarta-sans-latin.woff2 +0 -0
- package/src/ui/public/index.html +28 -27
- package/src/ui/server/index.js +28 -2
- package/src/ui/server/routes/aiContext/collectors.js +360 -0
- package/src/ui/server/routes/aiContext/collectors.test.js +297 -0
- package/src/ui/server/routes/aiContext/index.js +444 -0
- package/src/ui/server/routes/aiContext/index.test.js +433 -0
- package/src/ui/server/routes/aiContext/jobRefresh.js +110 -0
- package/src/ui/server/routes/aiContext/jobRefresh.test.js +191 -0
- package/src/ui/server/routes/aiContext/render.js +366 -0
- package/src/ui/server/routes/aiContext/render.test.js +255 -0
- package/src/ui/server/routes/aiContext/wiring.js +80 -0
- package/src/ui/server/routes/fs.js +43 -0
- package/src/ui/server/routes/mindmap.js +36 -24
- package/src/ui/server/routes/monitor.js +44 -35
- package/src/ui/server/routes/remoteRepos.js +57 -48
- package/src/ui/server/routes/workbench/agentChat.js +94 -10
- package/src/ui/server/routes/workbench/agentChatContext.test.js +171 -0
- package/src/ui/server/routes/workbench/agentChatShared.test.js +33 -3
- package/src/ui/server/routes/workbench/agentEngines.js +77 -0
- package/src/ui/server/routes/workbench/agentEngines.test.js +65 -0
- package/src/ui/server/routes/workbench/agentRoutes.js +208 -48
- package/src/ui/server/routes/workbench/agentRoutes.test.js +160 -4
- package/src/ui/server/routes/workbench/agentSessionStore.js +3 -0
- package/src/ui/server/routes/workbench/envContext.js +61 -64
- package/src/ui/server/routes/workbench/envContext.test.js +133 -63
- package/src/ui/server/routes/workbench/index.js +78 -32
- package/src/ui/server/routes/workbench/runExternalTurn.js +279 -0
- package/src/ui/server/routes/workbench/runExternalTurn.test.js +275 -0
- package/src/ui/server/routes/workbench/shared.js +17 -0
- package/src/ui/server/utils/agentAttachments.js +169 -0
- package/src/ui/server/utils/agentAttachments.test.js +140 -0
- package/src/ui/public/assets/AgentView-DOyN97Ve.js +0 -5
- package/src/ui/public/assets/AgentView-DkM2KXuc.css +0 -1
- package/src/ui/public/assets/AppVersionBadge-CJrDr7GV.css +0 -1
- package/src/ui/public/assets/AppVersionBadge-oON3sAj4.js +0 -2
- package/src/ui/public/assets/BranchSelector-B0eLkAdv.css +0 -1
- package/src/ui/public/assets/BranchSelector-Cpj7eGqY.js +0 -1
- package/src/ui/public/assets/CommitForm-CQ0K8Yy2.css +0 -1
- package/src/ui/public/assets/CommitForm-D_ovYhVw.js +0 -6
- package/src/ui/public/assets/CommonDialog-ChZbqfRF.js +0 -1
- package/src/ui/public/assets/CommonDialog-jP0E9mco.css +0 -1
- package/src/ui/public/assets/EditorView-BPzpI0GV.js +0 -1
- package/src/ui/public/assets/EditorView-DZMLvQds.css +0 -1
- package/src/ui/public/assets/FileDiffViewer-B9EkuKdF.css +0 -1
- package/src/ui/public/assets/FileDiffViewer-B_qEKA9L.js +0 -15
- package/src/ui/public/assets/FlowOrchestrationWorkspace-C7yTD1gj.js +0 -21
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CYGp-PqS.css +0 -1
- package/src/ui/public/assets/IconButton-DEARmMkZ.js +0 -1
- package/src/ui/public/assets/IconButton-tjkOjzkC.css +0 -1
- package/src/ui/public/assets/LogList-BA_gowxE.js +0 -7
- package/src/ui/public/assets/LogList-BlJplIi1.css +0 -1
- package/src/ui/public/assets/MindmapView-8X6z245q.css +0 -1
- package/src/ui/public/assets/MindmapView-K-XcLLXr.js +0 -2
- package/src/ui/public/assets/MonitorView-C0RL5K9W.js +0 -1
- package/src/ui/public/assets/MonitorView-DetlFFXq.css +0 -1
- package/src/ui/public/assets/ProjectStartupButton-DC36_vvU.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-QLPltCK5.css +0 -1
- package/src/ui/public/assets/RemoteManagerDialog-BauAeH2w.css +0 -1
- package/src/ui/public/assets/RemoteManagerDialog-Bkqja660.js +0 -4
- package/src/ui/public/assets/RemoteRepoCard-Bsm7cj_H.js +0 -7
- package/src/ui/public/assets/RemoteRepoCard-CG-jARTe.css +0 -1
- package/src/ui/public/assets/SourceMapView-CN4Hkyp4.css +0 -1
- package/src/ui/public/assets/SourceMapView-Y5NKdIk6.js +0 -3
- package/src/ui/public/assets/SvgIcon-D_mFHXP4.js +0 -1
- package/src/ui/public/assets/TemplateManager-BzKVXZhy.js +0 -1
- package/src/ui/public/assets/TemplateManager-DMuG4i3G.css +0 -1
- package/src/ui/public/assets/UserInputNode-BPLK4aYx.js +0 -1
- package/src/ui/public/assets/UserInputNode-BVgQkB2l.css +0 -1
- package/src/ui/public/assets/WorkbenchView-B1BJhWHi.css +0 -1
- package/src/ui/public/assets/WorkbenchView-xbGdiVMQ.js +0 -56
- package/src/ui/public/assets/element-plus-Cwwq-Gje.css +0 -1
- package/src/ui/public/assets/flow-mindmap-BqFb1WO5.js +0 -139
- package/src/ui/public/assets/gitStore-BZo9_zyy.js +0 -6
- package/src/ui/public/assets/index-CHuCYLWm.js +0 -111
- package/src/ui/public/assets/index-CmbatbSR.css +0 -1
- package/src/ui/public/assets/previewSandbox-BTTTXooN.js +0 -0
- package/src/ui/public/assets/previewSandbox-CgdwPcuW.css +0 -1
- package/src/ui/public/assets/vue-flow-fLUQ0_kE.js +0 -39
|
@@ -0,0 +1,297 @@
|
|
|
1
|
+
// aiContext/collectors.js 单元测试。
|
|
2
|
+
//
|
|
3
|
+
// IO 全部走注入的假实现,不联网、不 spawn、不碰真实用户数据。
|
|
4
|
+
// 重点覆盖的是**误读路径**:CLI 没装 / 没登录 / 拉取报错时,
|
|
5
|
+
// 快照文件必须把"取不到"写清楚,绝不能留一片空白 —— 空白会被模型读成"用户没有仓库"。
|
|
6
|
+
import { test } from 'node:test';
|
|
7
|
+
import assert from 'node:assert/strict';
|
|
8
|
+
import {
|
|
9
|
+
TASKS_FILE_LIMIT,
|
|
10
|
+
collectCustomCommands,
|
|
11
|
+
collectGit,
|
|
12
|
+
collectMindmap,
|
|
13
|
+
collectRemote,
|
|
14
|
+
collectSystem,
|
|
15
|
+
collectWorkbenchTasks,
|
|
16
|
+
} from './collectors.js';
|
|
17
|
+
import { TASK_COLUMNS } from '../workbench/projectRegistry.js';
|
|
18
|
+
|
|
19
|
+
// ── ① Git ────────────────────────────────────────────────────
|
|
20
|
+
test('collectGit:干净仓库 → 摘要点明"工作区干净"', async () => {
|
|
21
|
+
const r = await collectGit({
|
|
22
|
+
repoPath: 'D:/ws/x',
|
|
23
|
+
probe: async () => ({ exists: true, isGitRepo: true, branch: 'main', upstream: 'origin/main', hasUpstream: true, ahead: 0, behind: 0, changed: 0, staged: 0, unstaged: 0, untracked: 0 }),
|
|
24
|
+
});
|
|
25
|
+
assert.equal(r.ok, true);
|
|
26
|
+
assert.match(r.summary, /分支 main/);
|
|
27
|
+
assert.match(r.summary, /工作区干净/);
|
|
28
|
+
assert.match(r.lines.join('\n'), /未提交改动: 0/);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
test('collectGit:有改动 / 非仓库 / 探测抛错 三条降级路径', async () => {
|
|
32
|
+
const dirty = await collectGit({
|
|
33
|
+
repoPath: 'D:/ws/x',
|
|
34
|
+
probe: async () => ({ exists: true, isGitRepo: true, branch: 'dev', hasUpstream: false, changed: 3, staged: 1, unstaged: 1, untracked: 1 }),
|
|
35
|
+
});
|
|
36
|
+
assert.equal(dirty.ok, true);
|
|
37
|
+
assert.match(dirty.summary, /未提交 3 个文件/);
|
|
38
|
+
assert.match(dirty.summary, /无上游分支/);
|
|
39
|
+
|
|
40
|
+
const notRepo = await collectGit({ repoPath: 'D:/ws/x', probe: async () => ({ exists: true, isGitRepo: false }) });
|
|
41
|
+
assert.equal(notRepo.ok, true);
|
|
42
|
+
assert.match(notRepo.summary, /不是 git 仓库/);
|
|
43
|
+
|
|
44
|
+
const boom = await collectGit({ repoPath: 'D:/ws/x', probe: async () => { throw new Error('git 不存在'); } });
|
|
45
|
+
assert.equal(boom.ok, false);
|
|
46
|
+
assert.match(boom.error, /git 不存在/);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('collectGit:没有项目路径时不硬编造,直接标失败', async () => {
|
|
50
|
+
const r = await collectGit({ repoPath: '', probe: async () => ({}) });
|
|
51
|
+
assert.equal(r.ok, false);
|
|
52
|
+
assert.match(r.error, /未拿到当前项目路径/);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// ── ②③ 远程仓库 ──────────────────────────────────────────────
|
|
56
|
+
test('collectRemote:CLI 没装 → 必须显式写明"取不到",且点明不是"没有仓库"', () => {
|
|
57
|
+
const r = collectRemote({ provider: 'github', state: { installed: false } });
|
|
58
|
+
assert.equal(r.ok, true);
|
|
59
|
+
assert.match(r.summary, /未检测到 gh CLI/);
|
|
60
|
+
const body = r.lines.join('\n');
|
|
61
|
+
assert.match(body, /未安装/);
|
|
62
|
+
assert.match(body, /不是"用户没有仓库"/);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test('collectRemote:装了没登录 → 给出登录命令', () => {
|
|
66
|
+
const r = collectRemote({ provider: 'gitee', state: { installed: true, version: '0.3.1', authenticated: false } });
|
|
67
|
+
assert.match(r.summary, /未登录/);
|
|
68
|
+
const body = r.lines.join('\n');
|
|
69
|
+
assert.match(body, /gitee auth login/);
|
|
70
|
+
assert.match(body, /不要索要 token/);
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
test('collectRemote:已登录 → 列仓库,并把拉取报错带出来', () => {
|
|
74
|
+
const r = collectRemote({
|
|
75
|
+
provider: 'github',
|
|
76
|
+
state: {
|
|
77
|
+
installed: true, version: '2.101.0', authenticated: true, user: 'xz333221', truncated: false,
|
|
78
|
+
repos: [
|
|
79
|
+
{ name: 'zen-gitsync', visibility: 'public', language: 'JavaScript', updatedAt: '2026-09-27T10:00:00Z', description: 'Git GUI' },
|
|
80
|
+
{ name: 'flow-mindmap', visibility: 'private' },
|
|
81
|
+
],
|
|
82
|
+
},
|
|
83
|
+
});
|
|
84
|
+
assert.match(r.summary, /2 个仓库/);
|
|
85
|
+
assert.match(r.summary, /账号 xz333221/);
|
|
86
|
+
assert.match(r.summary, /如 zen-gitsync、flow-mindmap/, '摘要要带前几个仓库名 —— 用户问的就是"有哪些项目"');
|
|
87
|
+
const body = r.lines.join('\n');
|
|
88
|
+
assert.match(body, /- zen-gitsync \| public \| JavaScript \| 更新于 2026-09-27/);
|
|
89
|
+
assert.match(body, /- flow-mindmap \| private/);
|
|
90
|
+
|
|
91
|
+
const withError = collectRemote({
|
|
92
|
+
provider: 'github',
|
|
93
|
+
state: { installed: true, authenticated: true, user: 'u', repos: [], error: '网络超时' },
|
|
94
|
+
});
|
|
95
|
+
assert.match(withError.lines.join('\n'), /上次拉取报错: 网络超时(清单可能不完整)/);
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
test('collectRemote:空清单要写明"该账号下没有可见仓库",而不是留空', () => {
|
|
99
|
+
const r = collectRemote({ provider: 'github', state: { installed: true, authenticated: true, user: 'u', repos: [] } });
|
|
100
|
+
assert.match(r.lines.join('\n'), /该账号下没有可见仓库/);
|
|
101
|
+
// 没有仓库时不许留下"(如 等)"这种半截预览 —— 那会被读成"有仓库但名字是空的"
|
|
102
|
+
assert.ok(!r.summary.includes('如 '), `空清单的摘要不该有预览: ${r.summary}`);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test('collectRemote:摘要预览最多带 REPOS_SUMMARY_PREVIEW 个,超出用"等"收尾', () => {
|
|
106
|
+
const repos = Array.from({ length: 12 }, (_, i) => ({ name: `repo-${String(i).padStart(2, '0')}` }));
|
|
107
|
+
const r = collectRemote({ provider: 'gitee', state: { installed: true, authenticated: true, user: 'xuze333221', repos } });
|
|
108
|
+
assert.match(r.summary, /12 个仓库/);
|
|
109
|
+
assert.match(r.summary, /如 repo-00、repo-01、repo-02、repo-03、repo-04 等/);
|
|
110
|
+
assert.ok(!r.summary.includes('repo-05'), '第 6 个开始就不该出现在摘要里(全量在文件里)');
|
|
111
|
+
// 摘要天生要短:注入块每次对话都带着它,不能变成仓库清单的副本
|
|
112
|
+
assert.ok(r.summary.length <= 200, `摘要 ${r.summary.length} 字符,太长了`);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
// ── ④ 自定义命令 ─────────────────────────────────────────────
|
|
116
|
+
test('collectCustomCommands:列出名称与命令,空配置给出明确说明', () => {
|
|
117
|
+
const r = collectCustomCommands({
|
|
118
|
+
config: { customCommands: [{ id: '1', name: 'dev', command: 'npm run dev', description: '起开发服务' }] },
|
|
119
|
+
});
|
|
120
|
+
assert.equal(r.ok, true);
|
|
121
|
+
assert.equal(r.summary, '共 1 条');
|
|
122
|
+
assert.match(r.lines[0], /- dev → `npm run dev` — 起开发服务/);
|
|
123
|
+
|
|
124
|
+
const empty = collectCustomCommands({ config: {} });
|
|
125
|
+
assert.equal(empty.summary, '共 0 条');
|
|
126
|
+
assert.match(empty.lines[0], /没有配置自定义命令/);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
// ── ⑤ 工作台任务 ─────────────────────────────────────────────
|
|
130
|
+
test('collectWorkbenchTasks:统计口径与看板一致(复用的就是同一份实现)', () => {
|
|
131
|
+
const tasks = [
|
|
132
|
+
{ id: 't1', title: '待办任务', projectPath: 'D:/ws/a', updatedAt: '2026-09-28T10:00:00Z' },
|
|
133
|
+
{ id: 't2', title: '跑着的任务', description: '描述内容', projectPath: 'D:/ws/a', updatedAt: '2026-09-28T11:00:00Z' },
|
|
134
|
+
{ id: 't3', title: '做完了', projectPath: 'D:/ws/b', updatedAt: '2026-09-27T10:00:00Z' },
|
|
135
|
+
];
|
|
136
|
+
const jobs = [
|
|
137
|
+
{ id: 'j1', taskId: 't2', status: 'running', startedAt: '2026-09-28T11:00:00Z' },
|
|
138
|
+
{ id: 'j2', taskId: 't3', status: 'done', endedAt: '2026-09-27T11:00:00Z' },
|
|
139
|
+
];
|
|
140
|
+
const r = collectWorkbenchTasks({ tasks, jobs, recentDirs: ['D:/ws/a'] });
|
|
141
|
+
assert.equal(r.ok, true);
|
|
142
|
+
assert.match(r.summary, /全部项目合计 3 条 —— 待处理 1 \/ 进行中 1 \/ 已完成 1/);
|
|
143
|
+
assert.match(r.summary, /正在执行 1 个/);
|
|
144
|
+
|
|
145
|
+
const body = r.lines.join('\n');
|
|
146
|
+
assert.match(body, /## 项目清单(共 2 个)/);
|
|
147
|
+
assert.match(body, /## 任务明细(共 3 条)/);
|
|
148
|
+
assert.match(body, /\[跑着的任务\]/);
|
|
149
|
+
assert.match(body, /最近执行 running/);
|
|
150
|
+
assert.match(body, /描述内容/);
|
|
151
|
+
});
|
|
152
|
+
|
|
153
|
+
test('collectWorkbenchTasks:无任务时也给出完整骨架(不返回 undefined 字段)', () => {
|
|
154
|
+
const r = collectWorkbenchTasks({});
|
|
155
|
+
assert.equal(r.ok, true);
|
|
156
|
+
assert.match(r.summary, /全部项目合计 0 条/);
|
|
157
|
+
assert.match(r.summary, /当前无执行中的任务/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('collectWorkbenchTasks:任务过多时截断并写明还有多少条', () => {
|
|
161
|
+
const tasks = Array.from({ length: TASKS_FILE_LIMIT + 5 }, (_, i) => ({
|
|
162
|
+
id: `t${i}`, title: `任务${i}`, projectPath: 'D:/ws/a', updatedAt: `2026-09-28T${String(i % 24).padStart(2, '0')}:00:00Z`,
|
|
163
|
+
}));
|
|
164
|
+
const r = collectWorkbenchTasks({ tasks });
|
|
165
|
+
assert.match(r.lines.join('\n'), /…还有 5 条未列出/);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
// ── tasks 板块的结构化 data:envContext(编排台视角)唯一的数据来源 ──────
|
|
169
|
+
//
|
|
170
|
+
// 这一条守的是"别再长出第二份实现":项目清单 / 各列计数 / 合计只算一遍,
|
|
171
|
+
// 结构化结果随板块一起交出去,envContext 拿它渲染,而不再自己读一遍 tasks.json。
|
|
172
|
+
test('collectWorkbenchTasks:带上结构化 data(项目/计数/合计只在算一遍)', () => {
|
|
173
|
+
const tasks = [
|
|
174
|
+
{ id: 't1', projectPath: 'D:/ws/a' },
|
|
175
|
+
{ id: 't2', projectPath: 'D:/ws/a' },
|
|
176
|
+
{ id: 't3', projectPath: 'D:/ws/b' },
|
|
177
|
+
{ id: 't4', projectPath: '' }, // 无归属 → 只进合计,不造项目行
|
|
178
|
+
];
|
|
179
|
+
const jobs = [
|
|
180
|
+
{ id: 'j1', taskId: 't3', status: 'done', endedAt: '2026-09-27T11:00:00Z' },
|
|
181
|
+
{ id: 'j2', taskId: 't1', status: 'running', startedAt: '2026-09-28T11:00:00Z' },
|
|
182
|
+
];
|
|
183
|
+
const r = collectWorkbenchTasks({ tasks, jobs, recentDirs: ['D:/ws/a'] });
|
|
184
|
+
|
|
185
|
+
assert.ok(r.data, '必须带 data');
|
|
186
|
+
// 必须是**纯对象**:它会跟着快照缓存一路传下去,Map 一旦被 JSON.stringify 就静默变 {}
|
|
187
|
+
assert.equal(r.data.statsByKey instanceof Map, false);
|
|
188
|
+
assert.equal(typeof r.data.statsByKey, 'object');
|
|
189
|
+
|
|
190
|
+
// 项目:a 来自任务 + 最近目录,b 只来自任务
|
|
191
|
+
assert.equal(r.data.projects.length, 2);
|
|
192
|
+
// 合计把无归属的 t4 也算上
|
|
193
|
+
assert.equal(r.data.total.total, 4);
|
|
194
|
+
assert.equal(r.data.runningJobs, 1);
|
|
195
|
+
assert.equal(r.data.taskCount, 4);
|
|
196
|
+
|
|
197
|
+
// data 与摘要同源:合计那一段文字里的数字必须能对上
|
|
198
|
+
const [, todo, doing, done] = (r.summary.match(/待处理 (\d+) \/ 进行中 (\d+) \/ 已完成 (\d+)/) || []).map(Number);
|
|
199
|
+
assert.equal(r.data.total.todo, todo);
|
|
200
|
+
assert.equal(r.data.total.doing, doing);
|
|
201
|
+
assert.equal(r.data.total.done, done);
|
|
202
|
+
|
|
203
|
+
// statsByKey 求和 == total(结构性自洽)
|
|
204
|
+
const sum = { total: 0 };
|
|
205
|
+
for (const k of TASK_COLUMNS) sum[k] = 0;
|
|
206
|
+
for (const s of Object.values(r.data.statsByKey)) {
|
|
207
|
+
sum.total += s.total || 0;
|
|
208
|
+
for (const k of TASK_COLUMNS) sum[k] += s[k] || 0;
|
|
209
|
+
}
|
|
210
|
+
assert.equal(sum.total, r.data.total.total);
|
|
211
|
+
for (const k of TASK_COLUMNS) assert.equal(sum[k], r.data.total[k]);
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test('collectWorkbenchTasks:失败/无数据时 data 缺席(envContext 据此降级)', () => {
|
|
215
|
+
// 空入参也要给出 data(不是 undefined),否则 envContext 会误判成"取数失败"
|
|
216
|
+
const r = collectWorkbenchTasks({});
|
|
217
|
+
assert.ok(r.data);
|
|
218
|
+
assert.deepEqual(r.data.projects, []);
|
|
219
|
+
assert.equal(r.data.total.total, 0);
|
|
220
|
+
assert.equal(r.data.runningJobs, 0);
|
|
221
|
+
|
|
222
|
+
// 其余板块不该被塞 data —— 只有 tasks 需要
|
|
223
|
+
assert.equal(collectCustomCommands({ config: {} }).data, undefined);
|
|
224
|
+
assert.equal(collectSystem({}).data, undefined);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// ── ⑥ 系统状态 ───────────────────────────────────────────────
|
|
228
|
+
test('collectSystem:CPU / 内存 / 磁盘 / 端口都要落到文件里', () => {
|
|
229
|
+
const r = collectSystem({
|
|
230
|
+
overview: {
|
|
231
|
+
cpu: { usage: 7.72, cores: 16, model: 'i9' },
|
|
232
|
+
memory: { total: 32 * 1024 ** 3, used: 20 * 1024 ** 3, usagePercent: 62.3 },
|
|
233
|
+
disks: { total: 1, free: 1, used: 1, usagePercent: 1, drives: [{ mount: 'C:', total: 952 * 1024 ** 3, used: 173.9 * 1024 ** 3, usagePercent: 18.2 }] },
|
|
234
|
+
system: { hostname: 'XUZE-QICAHONG', platform: 'win32', arch: 'x64', nodeVersion: 'v24.20.0' },
|
|
235
|
+
},
|
|
236
|
+
ports: [{ protocol: 'TCP', localAddress: '127.0.0.1', port: 4502, pid: 27088, process: 'node.exe' }],
|
|
237
|
+
});
|
|
238
|
+
assert.equal(r.ok, true);
|
|
239
|
+
assert.match(r.summary, /CPU 7\.7%/);
|
|
240
|
+
assert.match(r.summary, /内存 62\.3%/);
|
|
241
|
+
assert.match(r.summary, /1 个监听端口/);
|
|
242
|
+
const body = r.lines.join('\n');
|
|
243
|
+
assert.match(body, /- CPU 占用: 7\.7%(16 核 · i9)/);
|
|
244
|
+
assert.match(body, /- C: 已用 173\.9GB/);
|
|
245
|
+
assert.match(body, /TCP 127\.0\.0\.1:4502 PID 27088 node\.exe/);
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
test('collectSystem:磁盘为 null(查询失败降级)时不炸', () => {
|
|
249
|
+
const r = collectSystem({ overview: { cpu: {}, memory: {}, disks: null, system: {} }, ports: [] });
|
|
250
|
+
assert.equal(r.ok, true);
|
|
251
|
+
assert.match(r.lines.join('\n'), /没有查到监听端口/);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
test('collectSystem:拿不到概览 → 标失败', () => {
|
|
255
|
+
const r = collectSystem({});
|
|
256
|
+
assert.equal(r.ok, false);
|
|
257
|
+
assert.match(r.error, /未拿到系统概览/);
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
// ── ⑦ 思维导图 ───────────────────────────────────────────────
|
|
261
|
+
test('collectMindmap:按目录分组列出导图,单目录失败不影响其它目录', async () => {
|
|
262
|
+
const r = await collectMindmap({
|
|
263
|
+
dirs: ['D:/ws/mm', 'D:/gone'],
|
|
264
|
+
listDir: async (dir) => {
|
|
265
|
+
if (dir === 'D:/gone') throw new Error('目录不存在');
|
|
266
|
+
return [{ title: '智能体', name: '智能体.mindmap.json', size: 2048, mtime: Date.parse('2026-09-08T00:41:00Z') }];
|
|
267
|
+
},
|
|
268
|
+
});
|
|
269
|
+
assert.equal(r.ok, true);
|
|
270
|
+
assert.match(r.summary, /2 个目录 · 1 个导图(1 个目录读取失败)/);
|
|
271
|
+
const body = r.lines.join('\n');
|
|
272
|
+
assert.match(body, /## D:\/ws\/mm(1 个)/);
|
|
273
|
+
assert.match(body, /- 智能体 \| 2026-09-08/);
|
|
274
|
+
assert.match(body, /- D:\/gone: 目录不存在/);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
test('collectMindmap:目录全部读不到 → 整块判失败,不能报成"0 个导图"', async () => {
|
|
278
|
+
const r = await collectMindmap({
|
|
279
|
+
dirs: ['D:/gone', 'D:/gone2'],
|
|
280
|
+
listDir: async () => { throw new Error('目录不存在'); },
|
|
281
|
+
});
|
|
282
|
+
assert.equal(r.ok, false, '全失败必须是失败态,否则会被读成"没有思维导图"');
|
|
283
|
+
assert.match(r.error, /全部 2 个目录都读取失败/);
|
|
284
|
+
assert.match(r.lines.join('\n'), /- D:\/gone: 目录不存在/, '细节仍要留在文件里');
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
test('collectMindmap:没配目录时给明确说明,不是空白', async () => {
|
|
288
|
+
const r = await collectMindmap({ dirs: [] });
|
|
289
|
+
assert.equal(r.ok, true);
|
|
290
|
+
assert.match(r.summary, /未配置思维导图目录/);
|
|
291
|
+
});
|
|
292
|
+
|
|
293
|
+
test('collectMindmap:目录为空时也说明"这个目录下没有"', async () => {
|
|
294
|
+
const r = await collectMindmap({ dirs: ['D:/ws/mm'], listDir: async () => [] });
|
|
295
|
+
assert.match(r.summary, /1 个目录 · 0 个导图/);
|
|
296
|
+
assert.match(r.lines.join('\n'), /这个目录下没有 \.mindmap\.json/);
|
|
297
|
+
});
|