zen-gitsync 2.16.51 → 2.17.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/package.json +12 -2
- package/src/cli/ai/agent.js +2 -2
- package/src/cli/ai/images.js +5 -3
- package/src/cli/ai/modelSetup.js +1 -1
- package/src/cli/ai/sessionStore.js +4 -6
- package/src/config.js +178 -39
- package/src/gitCommit.js +8 -0
- package/src/ui/public/assets/AgentView-BsN9y4Kg.css +1 -0
- package/src/ui/public/assets/AgentView-YDATCFcA.js +4 -0
- package/src/ui/public/assets/AppVersionBadge-Bw4-tHkL.js +2 -0
- package/src/ui/public/assets/{AppVersionBadge-CJrDr7GV.css → AppVersionBadge-D5tJzJfa.css} +1 -1
- package/src/ui/public/assets/BranchSelector-BZTHIIaF.js +1 -0
- package/src/ui/public/assets/BranchSelector-T_rJm-JY.css +1 -0
- package/src/ui/public/assets/CommitForm-DKedPnu-.js +6 -0
- package/src/ui/public/assets/CommitForm-Oij8R6P-.css +1 -0
- package/src/ui/public/assets/CommonDialog-C_RjF9Mp.css +1 -0
- package/src/ui/public/assets/CommonDialog-OCekbVQM.js +1 -0
- package/src/ui/public/assets/EditorView-BgA-_8WV.css +1 -0
- package/src/ui/public/assets/EditorView-C5O3ihKq.js +1 -0
- package/src/ui/public/assets/FlowExecutionViewer-B5kh7VxS.css +1 -0
- package/src/ui/public/assets/FlowExecutionViewer-vIvDP419.js +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-BAQDU9Jd.css +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-SlVXZt60.js +21 -0
- package/src/ui/public/assets/LogList-DlPJsPy_.js +7 -0
- package/src/ui/public/assets/LogList-DlWwSZet.css +1 -0
- package/src/ui/public/assets/MindmapView-B1Go82pJ.js +1 -0
- package/src/ui/public/assets/MindmapView-CS2t30Ko.css +1 -0
- package/src/ui/public/assets/MonitorView-CphDOlQu.css +1 -0
- package/src/ui/public/assets/MonitorView-GQ34uXPs.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-0UNF9CeP.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-B7zvqRj_.css +1 -0
- package/src/ui/public/assets/RemoteManagerDialog-D51q3x7T.js +1 -0
- package/src/ui/public/assets/RemoteManagerDialog-bpwaVAvX.css +1 -0
- package/src/ui/public/assets/RemoteRepoCard-Bin9d3GZ.js +1 -0
- package/src/ui/public/assets/RemoteRepoCard-CDykPlPj.css +1 -0
- package/src/ui/public/assets/SourceMapView-DG_a_076.css +1 -0
- package/src/ui/public/assets/SourceMapView-OxlKvnEc.js +3 -0
- package/src/ui/public/assets/{SvgIcon-CDNsk4-Q.css → SvgIcon-BQ6bR7l0.css} +1 -1
- package/src/ui/public/assets/SvgIcon-BpKq_phu.js +1 -0
- package/src/ui/public/assets/UserInputNode-D2EDkZ_H.js +1 -0
- package/src/ui/public/assets/UserInputNode-DDtvv9Iq.css +1 -0
- package/src/ui/public/assets/WorkbenchView-DWMdGe8g.css +1 -0
- package/src/ui/public/assets/WorkbenchView-DyhIOjGD.js +13 -0
- package/src/ui/public/assets/{_plugin-vue_export-helper-N7dqbp6x.js → _plugin-vue_export-helper-BaSe6lmR.js} +3 -3
- package/src/ui/public/assets/configStore-CyAQ0a-U.js +1 -0
- package/src/ui/public/assets/element-plus-H7kW-grr.js +19 -0
- package/src/ui/public/assets/{flow-mindmap-wqtwJcDW.js → flow-mindmap-DEwSVurC.js} +12 -12
- package/src/ui/public/assets/index-BXdqTR9A.css +1 -0
- package/src/ui/public/assets/index-SjRTI8Of.js +46 -0
- package/src/ui/public/assets/office-docx-C67al1tG.js +31 -0
- package/src/ui/public/assets/{office-excel-5lGzd5Yy.js → office-excel-ffLec5k6.js} +1 -1
- package/src/ui/public/assets/office-pptx-vRGbnHIO.js +60 -0
- package/src/ui/public/assets/toolsStore-BcJP9HmK.js +1 -0
- package/src/ui/public/assets/{vendor-C6ntdXkR.js → vendor-Dwln8rZH.js} +39 -46
- package/src/ui/public/assets/vue-flow-BfBE1dKN.js +39 -0
- package/src/ui/public/index.html +13 -13
- package/src/ui/server/index.js +9 -0
- package/src/ui/server/routes/config.js +9 -6
- package/src/ui/server/routes/git/aiDiffSummary.js +2 -2
- package/src/ui/server/routes/gitOps.js +63 -7
- package/src/ui/server/routes/workbench/agentSessionStore.js +6 -9
- package/src/ui/server/routes/workbench/attachmentUtils.js +93 -0
- package/src/ui/server/routes/workbench/attachmentUtils.test.js +100 -0
- package/src/ui/server/routes/workbench/envContext.js +137 -0
- package/src/ui/server/routes/workbench/envContext.test.js +178 -0
- package/src/ui/server/routes/workbench/index.js +210 -8
- package/src/ui/server/routes/workbench/orchestratorStore.js +6 -0
- package/src/ui/server/routes/workbench/pathParity.test.js +73 -0
- package/src/ui/server/routes/workbench/projectRegistry.js +37 -16
- package/src/ui/server/routes/workbench/projectRegistry.test.js +62 -17
- package/src/ui/server/routes/workbench/shared.js +7 -2
- package/src/ui/server/routes/workbench/targetResolver.js +260 -0
- package/src/ui/server/routes/workbench/targetResolver.test.js +224 -0
- package/src/ui/server/routes/workbench/taskRunner.js +42 -0
- package/src/ui/server/utils/gitExitCode.js +116 -0
- package/src/ui/server/utils/gitExitCode.test.js +108 -0
- package/src/ui/server/utils/instanceRegistry.js +17 -6
- package/src/ui/public/assets/AgentView-8-xsOmE6.css +0 -1
- package/src/ui/public/assets/AgentView-DcTGw8-j.js +0 -4
- package/src/ui/public/assets/AppVersionBadge-CA5-POGA.js +0 -2
- package/src/ui/public/assets/BranchSelector-B0eLkAdv.css +0 -1
- package/src/ui/public/assets/BranchSelector-qn-iGHOI.js +0 -1
- package/src/ui/public/assets/CommitForm-CQ0K8Yy2.css +0 -1
- package/src/ui/public/assets/CommitForm-DHfyJ331.js +0 -6
- package/src/ui/public/assets/CommonDialog-DJBixJcI.js +0 -1
- package/src/ui/public/assets/CommonDialog-jP0E9mco.css +0 -1
- package/src/ui/public/assets/EditorView-BRWoa7--.js +0 -1
- package/src/ui/public/assets/EditorView-DZMLvQds.css +0 -1
- package/src/ui/public/assets/FlowExecutionViewer-YoPgld-R.js +0 -1
- package/src/ui/public/assets/FlowExecutionViewer-jNHI5zdm.css +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CYGp-PqS.css +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-DyEjlCBW.js +0 -21
- package/src/ui/public/assets/LogList-BlJplIi1.css +0 -1
- package/src/ui/public/assets/LogList-_z4dIgvS.js +0 -7
- package/src/ui/public/assets/MindmapView-BQTqx842.css +0 -1
- package/src/ui/public/assets/MindmapView-BrkcAcDh.js +0 -2
- package/src/ui/public/assets/MonitorView-8TG_K4m3.js +0 -1
- package/src/ui/public/assets/MonitorView-DetlFFXq.css +0 -1
- package/src/ui/public/assets/ProjectStartupButton-BqLyrik7.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-LOVV6KrB.js +0 -4
- package/src/ui/public/assets/RemoteRepoCard-CG-jARTe.css +0 -1
- package/src/ui/public/assets/RemoteRepoCard-SQyKcXDG.js +0 -7
- package/src/ui/public/assets/SourceMapView-BNRjvgzH.css +0 -1
- package/src/ui/public/assets/SourceMapView-DckLynaL.js +0 -3
- package/src/ui/public/assets/SvgIcon-CvIX1KL7.js +0 -1
- package/src/ui/public/assets/UserInputNode-BVgQkB2l.css +0 -1
- package/src/ui/public/assets/UserInputNode-Bc124-BK.js +0 -1
- package/src/ui/public/assets/WorkbenchView-BtQkd9Oy.js +0 -37
- package/src/ui/public/assets/WorkbenchView-FdhmMI-b.css +0 -1
- package/src/ui/public/assets/configStore-NaVpBxPo.js +0 -1
- package/src/ui/public/assets/element-plus-Csu3CJKj.js +0 -39
- package/src/ui/public/assets/index-Ci3lbjk2.css +0 -1
- package/src/ui/public/assets/index-D9u0mCSi.js +0 -98
- package/src/ui/public/assets/office-docx-BKrAsewH.js +0 -32
- package/src/ui/public/assets/office-pptx-DKtkU_7r.js +0 -74
- package/src/ui/public/assets/toolsStore-CNDLLgUG.js +0 -1
- package/src/ui/public/assets/vue-flow-D7IxiTyP.js +0 -39
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
// 「指令该落到哪个项目」的纯函数单测。
|
|
16
|
+
//
|
|
17
|
+
// 这一层最贵的一次错误不是"没匹配上",而是"匹配错了" —— 猜错的代价是把改动
|
|
18
|
+
// 做到错误的仓库里。所以断言重心在**不该匹配的时候坚决不匹配**:
|
|
19
|
+
// 同族名(claw-management / claw-management-api)、模型编造的路径、模型挂掉,
|
|
20
|
+
// 每一条都必须退到默认项目,而不是随手挑一个。
|
|
21
|
+
//
|
|
22
|
+
// 全程不碰网络:第 3 级靠 pickByAgent / callJson 注入点替换。
|
|
23
|
+
|
|
24
|
+
import { test } from 'node:test';
|
|
25
|
+
import assert from 'node:assert/strict';
|
|
26
|
+
|
|
27
|
+
import {
|
|
28
|
+
indexOfToken,
|
|
29
|
+
matchProjectMention,
|
|
30
|
+
pickProjectByAgent,
|
|
31
|
+
resolveInstructionTarget,
|
|
32
|
+
TARGET_SOURCE,
|
|
33
|
+
TARGET_SOURCES,
|
|
34
|
+
} from './targetResolver.js';
|
|
35
|
+
|
|
36
|
+
/** 清单里的 key 就是 canonicalProjectPath 的结果:Windows 形式小写 + 斜杠归一 */
|
|
37
|
+
const PROJECTS = [
|
|
38
|
+
{ name: 'zen-gitsync', path: 'D:\\ws\\zen-gitsync', key: 'd:\\ws\\zen-gitsync' },
|
|
39
|
+
{ name: 'article-generator', path: 'D:\\ws\\article-generator', key: 'd:\\ws\\article-generator' },
|
|
40
|
+
{ name: 'claw-management', path: 'D:\\ws\\claw-management', key: 'd:\\ws\\claw-management' },
|
|
41
|
+
{ name: 'claw-management-api', path: 'D:\\ws\\claw-management-api', key: 'd:\\ws\\claw-management-api' },
|
|
42
|
+
{ name: 'claw-sdd', path: 'D:\\ws\\claw-sdd', key: 'd:\\ws\\claw-sdd' },
|
|
43
|
+
{ name: 'claw-sdd-project', path: 'D:\\ws\\claw-sdd-project', key: 'd:\\ws\\claw-sdd-project' },
|
|
44
|
+
];
|
|
45
|
+
|
|
46
|
+
const nameOf = (r) => r && r.project && r.project.name;
|
|
47
|
+
|
|
48
|
+
// ── indexOfToken:词边界 ────────────────────────────────────────────────
|
|
49
|
+
|
|
50
|
+
test('indexOfToken 只在"成词"处命中', () => {
|
|
51
|
+
// claw-management 在 claw-management-api 里只是前半截,不算点名
|
|
52
|
+
assert.equal(indexOfToken('改动 claw-management-api', 'claw-management'), -1);
|
|
53
|
+
assert.equal(indexOfToken('改动 claw-management 的路由', 'claw-management'), 3);
|
|
54
|
+
assert.equal(indexOfToken('改动 claw-management', 'claw-management'), 3);
|
|
55
|
+
assert.equal(indexOfToken('Claw-Management', 'claw-management'), 0, '大小写不敏感');
|
|
56
|
+
assert.equal(indexOfToken('', 'claw-management'), -1);
|
|
57
|
+
assert.equal(indexOfToken('有内容', ''), -1);
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// ── matchProjectMention:规则匹配 ───────────────────────────────────────
|
|
61
|
+
|
|
62
|
+
test('指名道姓的项目能被识别', () => {
|
|
63
|
+
const m = matchProjectMention('给 article-generator 的导出加个进度条', PROJECTS);
|
|
64
|
+
assert.equal(nameOf(m), 'article-generator');
|
|
65
|
+
assert.equal(m.matched, 'article-generator');
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('同族名按词边界区分,绝不误伤邻居', () => {
|
|
69
|
+
assert.equal(nameOf(matchProjectMention('修一下 claw-management-api', PROJECTS)), 'claw-management-api');
|
|
70
|
+
assert.equal(nameOf(matchProjectMention('修一下 claw-management', PROJECTS)), 'claw-management');
|
|
71
|
+
assert.equal(nameOf(matchProjectMention('看看 claw-sdd-project', PROJECTS)), 'claw-sdd-project');
|
|
72
|
+
assert.equal(nameOf(matchProjectMention('看看 claw-sdd', PROJECTS)), 'claw-sdd');
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('完整路径优先于项目名', () => {
|
|
76
|
+
const m = matchProjectMention(
|
|
77
|
+
'把 D:\\ws\\article-generator 下的日志清一清,参考 zen-gitsync 的做法',
|
|
78
|
+
PROJECTS,
|
|
79
|
+
);
|
|
80
|
+
assert.equal(nameOf(m), 'article-generator');
|
|
81
|
+
assert.equal(m.matched, 'D:\\ws\\article-generator');
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test('大小写不同的项目名也能认出来', () => {
|
|
85
|
+
assert.equal(nameOf(matchProjectMention('重构 Article-Generator 的导出', PROJECTS)), 'article-generator');
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
test('没点名任何项目 -> null(不硬猜)', () => {
|
|
89
|
+
assert.equal(matchProjectMention('把登录模块的错误处理重构一遍', PROJECTS), null);
|
|
90
|
+
assert.equal(matchProjectMention('', PROJECTS), null);
|
|
91
|
+
assert.equal(matchProjectMention('随便写点什么', []), null);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// ── pickProjectByAgent:模型返回值的校验 ─────────────────────────────────
|
|
95
|
+
|
|
96
|
+
test('pickProjectByAgent 只认清单里真实存在的路径', async () => {
|
|
97
|
+
const model = { model: 'fake' };
|
|
98
|
+
const ask = (reply) => pickProjectByAgent({
|
|
99
|
+
text: '随便写点什么', projects: PROJECTS, model, callJson: async () => reply,
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
assert.equal(nameOf(await ask({ projectPath: 'D:\\ws\\article-generator' })), 'article-generator');
|
|
103
|
+
// 盘符小写也要认(走 canonicalProjectPath 归一)
|
|
104
|
+
assert.equal(nameOf(await ask({ projectPath: 'd:\\ws\\article-generator' })), 'article-generator');
|
|
105
|
+
// 目录段大小写也一样:模型可能把路径"顺手改写"成另一种大小写。
|
|
106
|
+
// key 会在别处被精确比较,归一必须做进 key 本身,否则这里会当成"编造的路径"退默认项目
|
|
107
|
+
assert.equal(nameOf(await ask({ projectPath: 'D:\\WS\\Article-Generator' })), 'article-generator');
|
|
108
|
+
// 模型编出来的路径 -> 当作没判断出来,宁可退默认项目也不凭空造工作目录
|
|
109
|
+
assert.equal(await ask({ projectPath: 'D:\\ws\\压根不存在的项目' }), null);
|
|
110
|
+
assert.equal(await ask({ projectPath: '' }), null);
|
|
111
|
+
assert.equal(await ask({}), null);
|
|
112
|
+
assert.equal(await ask(null), null);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test('pickProjectByAgent 在没模型 / 没清单时直接放弃', async () => {
|
|
116
|
+
assert.equal(await pickProjectByAgent({ text: 'x', projects: PROJECTS, model: null }), null);
|
|
117
|
+
assert.equal(await pickProjectByAgent({ text: 'x', projects: [], model: { model: 'f' } }), null);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
// ── resolveInstructionTarget:四级优先级 ────────────────────────────────
|
|
121
|
+
|
|
122
|
+
test('1. 显式指定永远压过指令内容', async () => {
|
|
123
|
+
const r = await resolveInstructionTarget({
|
|
124
|
+
text: '给 article-generator 加个功能',
|
|
125
|
+
projects: PROJECTS,
|
|
126
|
+
explicitPath: 'D:\\ws\\zen-gitsync',
|
|
127
|
+
defaultPath: 'D:\\ws\\zen-gitsync',
|
|
128
|
+
});
|
|
129
|
+
assert.equal(r.source, TARGET_SOURCE.EXPLICIT);
|
|
130
|
+
assert.equal(r.name, 'zen-gitsync');
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
test('2. 指令里点名 -> mention,且不为它多花一次模型调用', async () => {
|
|
134
|
+
let asked = false;
|
|
135
|
+
const r = await resolveInstructionTarget({
|
|
136
|
+
text: '给 article-generator 加个进度条',
|
|
137
|
+
projects: PROJECTS,
|
|
138
|
+
defaultPath: 'D:\\ws\\zen-gitsync',
|
|
139
|
+
getModel: async () => { asked = true; return { model: 'fake' }; },
|
|
140
|
+
});
|
|
141
|
+
assert.equal(r.source, TARGET_SOURCE.MENTION);
|
|
142
|
+
assert.equal(r.name, 'article-generator');
|
|
143
|
+
assert.equal(r.matched, 'article-generator');
|
|
144
|
+
assert.equal(asked, false, '规则命中就不该再去读 config 问模型');
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('3. 规则没命中 -> 交给主 Agent 判断', async () => {
|
|
148
|
+
let asked = false;
|
|
149
|
+
const r = await resolveInstructionTarget({
|
|
150
|
+
text: '把文章生成器的导出加个进度条',
|
|
151
|
+
projects: PROJECTS,
|
|
152
|
+
defaultPath: 'D:\\ws\\zen-gitsync',
|
|
153
|
+
getModel: async () => { asked = true; return { model: 'fake' }; },
|
|
154
|
+
pickByAgent: async ({ projects }) => ({
|
|
155
|
+
project: projects.find(p => p.name === 'article-generator'),
|
|
156
|
+
reason: '语义上指向文章生成器',
|
|
157
|
+
}),
|
|
158
|
+
});
|
|
159
|
+
assert.equal(asked, true);
|
|
160
|
+
assert.equal(r.source, TARGET_SOURCE.AGENT);
|
|
161
|
+
assert.equal(r.name, 'article-generator');
|
|
162
|
+
assert.equal(r.reason, '语义上指向文章生成器');
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
test('3b. 模型判断不出来 -> 退默认项目', async () => {
|
|
166
|
+
const r = await resolveInstructionTarget({
|
|
167
|
+
text: '随便改点什么',
|
|
168
|
+
projects: PROJECTS,
|
|
169
|
+
defaultPath: 'D:\\ws\\zen-gitsync',
|
|
170
|
+
getModel: async () => ({ model: 'fake' }),
|
|
171
|
+
pickByAgent: async () => null,
|
|
172
|
+
});
|
|
173
|
+
assert.equal(r.source, TARGET_SOURCE.DEFAULT);
|
|
174
|
+
assert.equal(r.name, 'zen-gitsync');
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test('3c. 模型抛错 -> 退默认项目,并把原因交给调用方(自己不抛)', async () => {
|
|
178
|
+
let caught = null;
|
|
179
|
+
const r = await resolveInstructionTarget({
|
|
180
|
+
text: '随便改点什么',
|
|
181
|
+
projects: PROJECTS,
|
|
182
|
+
defaultPath: 'D:\\ws\\zen-gitsync',
|
|
183
|
+
getModel: async () => ({ model: 'fake' }),
|
|
184
|
+
pickByAgent: async () => { throw new Error('模型超时'); },
|
|
185
|
+
onAgentError: (e) => { caught = e; },
|
|
186
|
+
});
|
|
187
|
+
assert.equal(r.source, TARGET_SOURCE.DEFAULT);
|
|
188
|
+
assert.equal(r.name, 'zen-gitsync');
|
|
189
|
+
assert.match(caught.message, /模型超时/);
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test('3d. 没配模型 -> 直接退默认项目(不报错、不问模型)', async () => {
|
|
193
|
+
let called = false;
|
|
194
|
+
const r = await resolveInstructionTarget({
|
|
195
|
+
text: '随便改点什么',
|
|
196
|
+
projects: PROJECTS,
|
|
197
|
+
defaultPath: 'D:\\ws\\zen-gitsync',
|
|
198
|
+
getModel: async () => null,
|
|
199
|
+
pickByAgent: async () => { called = true; return null; },
|
|
200
|
+
});
|
|
201
|
+
assert.equal(r.source, TARGET_SOURCE.DEFAULT);
|
|
202
|
+
assert.equal(called, false);
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
test('4. 默认项目不在清单里也照用(原样返回,交给调用方去 stat)', async () => {
|
|
206
|
+
const r = await resolveInstructionTarget({
|
|
207
|
+
text: '',
|
|
208
|
+
projects: PROJECTS,
|
|
209
|
+
defaultPath: 'E:\\未注册的项目',
|
|
210
|
+
});
|
|
211
|
+
assert.equal(r.source, TARGET_SOURCE.DEFAULT);
|
|
212
|
+
assert.equal(r.path, 'E:\\未注册的项目');
|
|
213
|
+
assert.equal(r.name, '未注册的项目');
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
test('什么都没给 -> path 为空,由调用方决定怎么报错', async () => {
|
|
217
|
+
const r = await resolveInstructionTarget({ text: 'x', projects: PROJECTS });
|
|
218
|
+
assert.equal(r.path, '');
|
|
219
|
+
assert.equal(r.source, TARGET_SOURCE.DEFAULT);
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
test('TARGET_SOURCES 是 UI 认得的四种来源(改这里要同步 lang/{zh,en})', () => {
|
|
223
|
+
assert.deepEqual([...TARGET_SOURCES].sort(), ['agent', 'default', 'explicit', 'mention']);
|
|
224
|
+
});
|
|
@@ -47,6 +47,39 @@ import {
|
|
|
47
47
|
flushJobsSaveNow,
|
|
48
48
|
} from './jobStore.js';
|
|
49
49
|
|
|
50
|
+
// ── 运行环境上下文的提供者 ────────────────────────────────────────────────
|
|
51
|
+
// 为什么用注入而不是在这里直接读:拼这份上下文要「最近目录」(在 configManager 上),
|
|
52
|
+
// 而 taskRunner 拿不到 configManager —— 它是路由层的东西。与其在这里再造一条
|
|
53
|
+
// 读取最近目录的路径(口径分叉的老套路),不如让路由层注册时把函数递进来。
|
|
54
|
+
//
|
|
55
|
+
// 未注入时静默跳过:上下文是锦上添花,不能因为没接线就让整个执行引擎跑不起来。
|
|
56
|
+
let envContextProvider = null;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* 注册运行环境上下文的提供者。由 routes/workbench/index.js 在注册路由时调用。
|
|
60
|
+
* @param {(ctx: {repoPath: string}) => Promise<string|null>|string|null} fn
|
|
61
|
+
*/
|
|
62
|
+
export function setEnvContextProvider(fn) {
|
|
63
|
+
envContextProvider = typeof fn === 'function' ? fn : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 取本次执行的运行环境上下文块。
|
|
68
|
+
*
|
|
69
|
+
* 提供者抛错一律降级成"没有上下文":读 tasks.json 失败、配置竞态都是可能发生的,
|
|
70
|
+
* 但它们都不该让用户的指令执行不了 —— 与「读不到最近目录不该让整个看板挂掉」同一条原则。
|
|
71
|
+
*/
|
|
72
|
+
async function resolveEnvContext(repoPath) {
|
|
73
|
+
if (!envContextProvider) return '';
|
|
74
|
+
try {
|
|
75
|
+
const block = await envContextProvider({ repoPath: repoPath || '' });
|
|
76
|
+
return typeof block === 'string' ? block : '';
|
|
77
|
+
} catch (err) {
|
|
78
|
+
logger.warn(`[workbench] 运行环境上下文注入失败,本次跳过: ${err.message}`);
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
50
83
|
// 用 detached 进程跑 claude;进程退出时回填状态。
|
|
51
84
|
// 返回 { pid, child }:调用方可以监听 child.stdout/stderr 实时收集输出。
|
|
52
85
|
// 不再走 cmd /k 弹窗——claude -p 是非交互模式,输出通过 stdout pipe 实时回传
|
|
@@ -252,6 +285,15 @@ ${prompt}`;
|
|
|
252
285
|
}
|
|
253
286
|
}
|
|
254
287
|
|
|
288
|
+
// ── 运行环境上下文:项目清单 + 看板概览 + 真相源文件路径 ──
|
|
289
|
+
// 拼在**最前面**、任务正文压尾,与上面 priorOutputs 的位置口径一致:
|
|
290
|
+
// 越靠后离模型的注意力中心越近,用户真正要办的那句话必须在最后一屏。
|
|
291
|
+
// task.envContext === false 可以单任务关掉(默认开 —— 老任务不迁移也一并受益)。
|
|
292
|
+
if (task.envContext !== false) {
|
|
293
|
+
const envBlock = await resolveEnvContext(repoPath);
|
|
294
|
+
if (envBlock) prompt = `${envBlock}\n\n---\n\n${prompt}`;
|
|
295
|
+
}
|
|
296
|
+
|
|
255
297
|
const jobId = genId();
|
|
256
298
|
const job = {
|
|
257
299
|
id: jobId,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
// git 进程退出码 → 用户看得懂的失败原因。
|
|
16
|
+
//
|
|
17
|
+
// 背景:git 自己报错时 stderr 里有 `fatal: ...` 这类可读信息,直接透传就够。
|
|
18
|
+
// 但进程**根本没跑起来**的时候(被 Windows 加载器拒绝初始化、被信号杀掉),
|
|
19
|
+
// stdout/stderr 是空的,界面只能显示 `Push failed with code 3221225794` ——
|
|
20
|
+
// 用户既看不懂码,也无从判断该不该重试。
|
|
21
|
+
//
|
|
22
|
+
// 实测报障(zen-gitsync):git push 一段进度都没打出来就挂掉,stdout/stderr 全空,
|
|
23
|
+
// 退出码 0xC0000142。同一个请求里几毫秒前的 `git rev-parse` 还是好的,事后复现不了 ——
|
|
24
|
+
// 属于 Windows 上已知的瞬时进程初始化失败(DLL 加载失败),重试即可恢复。
|
|
25
|
+
// 所以文案的重点是「这不是仓库的问题,直接重试」,而不是甩一个十六进制码。
|
|
26
|
+
|
|
27
|
+
// Windows NTSTATUS 码表。键是**无符号 DWORD** —— Node 把退出码原样返回,
|
|
28
|
+
// 3221225794 就是 0xC0000142,不会转成负数,所以这里不能用有符号比较。
|
|
29
|
+
const WINDOWS_EXIT_CODES = new Map([
|
|
30
|
+
// STATUS_DLL_INIT_FAILED —— 加载器初始化 DLL 失败,进程连 main() 都没进
|
|
31
|
+
[0xC0000142, {
|
|
32
|
+
errorCode: 'PROCESS_INIT_FAILED',
|
|
33
|
+
error: '未能启动 git 进程(Windows 错误 0xC0000142:系统加载 DLL 失败)。这通常是系统的一次性瞬时故障,不是仓库的问题——请直接重试推送。'
|
|
34
|
+
}],
|
|
35
|
+
// STATUS_DLL_NOT_FOUND —— Git 安装缺文件 / 依赖 DLL 丢失
|
|
36
|
+
[0xC0000135, {
|
|
37
|
+
errorCode: 'PROCESS_DLL_NOT_FOUND',
|
|
38
|
+
error: '未能启动 git 进程(Windows 错误 0xC0000135:缺少依赖 DLL)。请检查 Git 安装是否完整,必要时重装 Git for Windows。'
|
|
39
|
+
}],
|
|
40
|
+
// STATUS_NO_MEMORY —— 系统提交内存不足,进程起不来
|
|
41
|
+
[0xC0000017, {
|
|
42
|
+
errorCode: 'PROCESS_NO_MEMORY',
|
|
43
|
+
error: '系统内存不足,git 进程未能启动(Windows 错误 0xC0000017)。请关闭一些程序后重试。'
|
|
44
|
+
}],
|
|
45
|
+
// STATUS_ACCESS_VIOLATION —— 除 0xC0000142 外最常见的「进程秒退」
|
|
46
|
+
[0xC0000005, {
|
|
47
|
+
errorCode: 'PROCESS_CRASHED',
|
|
48
|
+
error: 'git 进程崩溃(Windows 错误 0xC0000005:访问冲突),没有产生任何输出。请重试;若反复出现,检查安全软件是否拦截了 git。'
|
|
49
|
+
}],
|
|
50
|
+
// STATUS_CONTROL_C_EXIT —— 被 Ctrl+C / 被 kill 中断,不是故障
|
|
51
|
+
[0xC000013A, {
|
|
52
|
+
errorCode: 'PROCESS_INTERRUPTED',
|
|
53
|
+
error: '推送已被中断(Windows 错误 0xC000013A)。'
|
|
54
|
+
}]
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
// spawn 直接失败(PATH 里没有 git.exe)。与退出码无关,是另一条路径 ——
|
|
58
|
+
// 走 child_process 的 'error' 事件而不是 'close',所以单独给一份。
|
|
59
|
+
export const GIT_NOT_FOUND = {
|
|
60
|
+
errorCode: 'GIT_NOT_FOUND',
|
|
61
|
+
error: '找不到 git 命令,请先安装 Git 并确保它在 PATH 中。'
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 退出码 → 展示形式。
|
|
66
|
+
*
|
|
67
|
+
* Windows 的 NTSTATUS 在 Node 里是 3221225794 这种无符号 DWORD,十进制对用户没有意义;
|
|
68
|
+
* 转成 0xC0000142 才能和系统错误码、搜索结果对上。正常退出码(0/128/...)保持十进制。
|
|
69
|
+
*
|
|
70
|
+
* @param {number|null|undefined} code
|
|
71
|
+
* @returns {string} 例如 '0xC0000142'
|
|
72
|
+
*/
|
|
73
|
+
export function formatExitCode(code) {
|
|
74
|
+
if (typeof code !== 'number' || !Number.isFinite(code)) return String(code)
|
|
75
|
+
const unsigned = code >>> 0
|
|
76
|
+
return unsigned >= 0x80000000 ? `0x${unsigned.toString(16).toUpperCase()}` : String(unsigned)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 已知的进程级退出码 → 用户提示。未知返回 null(交给调用方兜底)。
|
|
81
|
+
*
|
|
82
|
+
* @param {number|null|undefined} code
|
|
83
|
+
* @returns {{errorCode: string, error: string}|null}
|
|
84
|
+
*/
|
|
85
|
+
export function describeGitExitCode(code) {
|
|
86
|
+
if (typeof code !== 'number') return null
|
|
87
|
+
const hit = WINDOWS_EXIT_CODES.get(code >>> 0)
|
|
88
|
+
return hit ? { ...hit } : null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 生成「git 进程失败」的展示文案 + 机器可读错误码。
|
|
93
|
+
*
|
|
94
|
+
* 优先级:git 自己的输出 > 退出码映射 > 兜底文案。
|
|
95
|
+
* git 有输出时一律透传 —— stderr 里的 `fatal: ...` 永远比任何翻译都准确,
|
|
96
|
+
* 映射只负责「零输出」这种 git 连话都没说出来的情况。
|
|
97
|
+
*
|
|
98
|
+
* @param {{ code?: number|null, stdout?: string, stderr?: string }} params
|
|
99
|
+
* @returns {{ error: string, errorCode: string|null, exitCode: number|null }}
|
|
100
|
+
*/
|
|
101
|
+
export function describeGitProcessFailure({ code = null, stdout = '', stderr = '' } = {}) {
|
|
102
|
+
const gitOutput = String(stderr || '').trim() || String(stdout || '').trim()
|
|
103
|
+
if (gitOutput) {
|
|
104
|
+
// git 自己说了话,errorCode 留空 —— 前端据此走「原样展示」,不会覆盖成通用文案
|
|
105
|
+
return { error: gitOutput, errorCode: null, exitCode: code }
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const known = describeGitExitCode(code)
|
|
109
|
+
if (known) return { ...known, exitCode: code }
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
errorCode: 'PROCESS_EXITED',
|
|
113
|
+
exitCode: code,
|
|
114
|
+
error: `git 进程异常退出(退出码 ${formatExitCode(code)}),且没有产生任何输出——通常是进程未能成功启动,请重试。`
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
// Copyright 2026 xz333221
|
|
2
|
+
//
|
|
3
|
+
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
// you may not use this file except in compliance with the License.
|
|
5
|
+
// You may obtain a copy of the License at
|
|
6
|
+
//
|
|
7
|
+
// http://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
//
|
|
9
|
+
// Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
// See the License for the specific language governing permissions and
|
|
13
|
+
// limitations under the License.
|
|
14
|
+
//
|
|
15
|
+
// gitExitCode.js 单元测试。
|
|
16
|
+
//
|
|
17
|
+
// 重点不是「文案写得好不好」,而是三条判定边界不能串:
|
|
18
|
+
// 1. 有 git 输出 → 必须原样透传(errorCode 留空),不能被通用文案覆盖
|
|
19
|
+
// 2. 零输出 + 已知退出码 → 必须给出可读文案,且带上 errorCode 供前端本地化
|
|
20
|
+
// 3. 零输出 + 未知退出码 → 兜底文案里必须出现十六进制码,不能只说 "failed"
|
|
21
|
+
import { test } from 'node:test'
|
|
22
|
+
import assert from 'node:assert/strict'
|
|
23
|
+
import {
|
|
24
|
+
formatExitCode,
|
|
25
|
+
describeGitExitCode,
|
|
26
|
+
describeGitProcessFailure,
|
|
27
|
+
GIT_NOT_FOUND
|
|
28
|
+
} from './gitExitCode.js'
|
|
29
|
+
|
|
30
|
+
test('formatExitCode: NTSTATUS 转十六进制,正常退出码保持十进制', () => {
|
|
31
|
+
// 用户报障里的原始数字
|
|
32
|
+
assert.equal(formatExitCode(3221225794), '0xC0000142')
|
|
33
|
+
assert.equal(formatExitCode(0xC0000005), '0xC0000005')
|
|
34
|
+
assert.equal(formatExitCode(128), '128')
|
|
35
|
+
assert.equal(formatExitCode(0), '0')
|
|
36
|
+
// 非数字不该抛
|
|
37
|
+
assert.equal(formatExitCode(null), 'null')
|
|
38
|
+
assert.equal(formatExitCode(undefined), 'undefined')
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
test('formatExitCode: 负数退出码按无符号解释', () => {
|
|
42
|
+
// POSIX 下被信号杀时 Node 给的是 null,但保险起见负数也要能格式化
|
|
43
|
+
assert.equal(formatExitCode(-1073741819), '0xC0000005')
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
test('describeGitExitCode: 已知码返回文案 + errorCode', () => {
|
|
47
|
+
const initFailed = describeGitExitCode(3221225794)
|
|
48
|
+
assert.equal(initFailed.errorCode, 'PROCESS_INIT_FAILED')
|
|
49
|
+
// 文案里必须出现真正的十六进制码(用户要靠它去搜 / 报障)
|
|
50
|
+
assert.match(initFailed.error, /0xC0000142/)
|
|
51
|
+
// 必须明确告诉用户"可以重试",这是这类瞬时故障唯一有效的处置方式
|
|
52
|
+
assert.match(initFailed.error, /重试/)
|
|
53
|
+
|
|
54
|
+
assert.equal(describeGitExitCode(0xC0000135).errorCode, 'PROCESS_DLL_NOT_FOUND')
|
|
55
|
+
assert.equal(describeGitExitCode(0xC0000005).errorCode, 'PROCESS_CRASHED')
|
|
56
|
+
assert.equal(describeGitExitCode(0xC0000017).errorCode, 'PROCESS_NO_MEMORY')
|
|
57
|
+
assert.equal(describeGitExitCode(0xC000013A).errorCode, 'PROCESS_INTERRUPTED')
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
test('describeGitExitCode: 未知码 / 正常退出码返回 null', () => {
|
|
61
|
+
assert.equal(describeGitExitCode(1), null)
|
|
62
|
+
assert.equal(describeGitExitCode(128), null)
|
|
63
|
+
assert.equal(describeGitExitCode(0), null)
|
|
64
|
+
assert.equal(describeGitExitCode(null), null)
|
|
65
|
+
assert.equal(describeGitExitCode(undefined), null)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
test('describeGitProcessFailure: git 有输出时原样透传,不覆盖成通用文案', () => {
|
|
69
|
+
const gitFatal = 'fatal: failed to push some refs to https://example.com/x.git'
|
|
70
|
+
const res = describeGitProcessFailure({ code: 128, stderr: gitFatal })
|
|
71
|
+
assert.equal(res.error, gitFatal)
|
|
72
|
+
assert.equal(res.errorCode, null)
|
|
73
|
+
assert.equal(res.exitCode, 128)
|
|
74
|
+
|
|
75
|
+
// stderr 只有空白时,应当退到 stdout,而不是判定成"零输出"
|
|
76
|
+
const res2 = describeGitProcessFailure({ code: 1, stdout: 'remote: Permission denied\n', stderr: '\n' })
|
|
77
|
+
assert.equal(res2.error, 'remote: Permission denied')
|
|
78
|
+
assert.equal(res2.errorCode, null)
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
test('describeGitProcessFailure: 零输出 + 0xC0000142 → 可读文案(用户报障场景)', () => {
|
|
82
|
+
const res = describeGitProcessFailure({ code: 3221225794, stdout: '', stderr: '' })
|
|
83
|
+
assert.equal(res.errorCode, 'PROCESS_INIT_FAILED')
|
|
84
|
+
assert.equal(res.exitCode, 3221225794)
|
|
85
|
+
assert.match(res.error, /0xC0000142/)
|
|
86
|
+
// 绝不能退化成当初那句对用户毫无信息量的兜底
|
|
87
|
+
assert.doesNotMatch(res.error, /Push failed with code/)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
test('describeGitProcessFailure: 零输出 + 未知码 → 兜底文案带十六进制码', () => {
|
|
91
|
+
const res = describeGitProcessFailure({ code: 3221225725 }) // 0xC00000FD 栈溢出,不在码表里
|
|
92
|
+
assert.equal(res.errorCode, 'PROCESS_EXITED')
|
|
93
|
+
assert.equal(res.exitCode, 3221225725)
|
|
94
|
+
assert.match(res.error, /0xC00000FD/)
|
|
95
|
+
assert.match(res.error, /重试/)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
test('describeGitProcessFailure: 无参调用不抛异常', () => {
|
|
99
|
+
const res = describeGitProcessFailure()
|
|
100
|
+
assert.equal(res.errorCode, 'PROCESS_EXITED')
|
|
101
|
+
assert.equal(res.exitCode, null)
|
|
102
|
+
assert.equal(res.error, 'git 进程异常退出(退出码 null),且没有产生任何输出——通常是进程未能成功启动,请重试。')
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('GIT_NOT_FOUND: 常量形状与前端约定一致', () => {
|
|
106
|
+
assert.equal(GIT_NOT_FOUND.errorCode, 'GIT_NOT_FOUND')
|
|
107
|
+
assert.match(GIT_NOT_FOUND.error, /PATH/)
|
|
108
|
+
})
|
|
@@ -25,29 +25,33 @@
|
|
|
25
25
|
// stale 判定:PID 不存在 或 lastHeartbeat 超过 STALE_MS。
|
|
26
26
|
// 启动时会自动从旧主文件(~/.zen-gitsync-instances.json)迁移到目录。
|
|
27
27
|
|
|
28
|
-
import nodePath from 'node:path';
|
|
29
28
|
import logger from './logger.js';
|
|
30
|
-
import
|
|
29
|
+
import { INSTANCES_DIR, LEGACY_INSTANCES_FILE } from '../../../paths.js';
|
|
31
30
|
|
|
32
31
|
const STALE_MS = 30_000; // 心跳超时阈值(毫秒)
|
|
33
32
|
const WATCH_DEBOUNCE_MS = 100; // fs.watch 防抖时间
|
|
34
33
|
const REGISTRY_VERSION = 1;
|
|
34
|
+
// 这两个字符串常量保留:createInstanceRegistry() 支持注入 pathMod/osMod(测试要跨平台
|
|
35
|
+
// mock),内部 legacyPath() 用注入的 os 拼路径。生产默认值走下面的 getter → src/paths.js。
|
|
35
36
|
const REGISTRY_DIR_NAME = '.zen-gitsync-instances';
|
|
36
37
|
const REGISTRY_LEGACY_FILE_NAME = '.zen-gitsync-instances.json';
|
|
37
38
|
const MIGRATION_MARKER = '.migrated';
|
|
38
39
|
|
|
39
40
|
/**
|
|
40
|
-
*
|
|
41
|
+
* 新注册表目录路径(每进程一个文件)。
|
|
42
|
+
*
|
|
43
|
+
* 位置由 src/paths.js 统一裁定 = ~/.zen-gitsync/instances(2026-09-18 从
|
|
44
|
+
* ~/.zen-gitsync-instances 收敛进数据目录,历史目录由 dataDirMigration 搬迁)。
|
|
41
45
|
*/
|
|
42
46
|
export function getRegistryPath() {
|
|
43
|
-
return
|
|
47
|
+
return INSTANCES_DIR;
|
|
44
48
|
}
|
|
45
49
|
|
|
46
50
|
/**
|
|
47
51
|
* 旧版单文件注册表路径(仅用于一次性迁移)
|
|
48
52
|
*/
|
|
49
53
|
export function getLegacyRegistryPath() {
|
|
50
|
-
return
|
|
54
|
+
return LEGACY_INSTANCES_FILE;
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
function defaultIsProcessAlive(pid) {
|
|
@@ -249,7 +253,14 @@ export function createInstanceRegistry({
|
|
|
249
253
|
const raw = await fsMod.readFile(legacyPath_, 'utf-8');
|
|
250
254
|
legacy = JSON.parse(raw);
|
|
251
255
|
} catch (err) {
|
|
252
|
-
|
|
256
|
+
// 历史脏状态:~/.zen-gitsync-instances.json 被误建成了一个**目录**,readFile 会
|
|
257
|
+
// 抛 EISDIR(Windows 上 ENOTDIR/EISDIR 视情况)。这不是"读旧文件失败",而是
|
|
258
|
+
// "旧文件根本不是文件",当不存在处理即可 —— 否则每次启动都打一条误导性警告,
|
|
259
|
+
// 且随后的 unlink 也必然 EPERM,垃圾永远清不掉(目录本身由 dataDirMigration 收走)。
|
|
260
|
+
const kind = err?.code;
|
|
261
|
+
if (kind === 'EISDIR' || kind === 'ENOTDIR') {
|
|
262
|
+
logger.info('[instanceRegistry] 旧主文件位置是个目录(历史脏状态),按无旧数据处理');
|
|
263
|
+
} else if (kind !== 'ENOENT') {
|
|
253
264
|
logger.warn(`[instanceRegistry] 读旧主文件失败: ${err?.message || err}`);
|
|
254
265
|
}
|
|
255
266
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.agent-view[data-v-bd681ddd]{background:var(--bg-container);height:100%;min-height:0;display:flex}.agent-sidebar[data-v-bd681ddd]{background:var(--bg-panel);border-right:1px solid var(--border-color);flex-direction:column;flex-shrink:0;display:flex;overflow:hidden}.sidebar-header[data-v-bd681ddd]{flex-shrink:0;padding:10px 12px}.new-session-btn[data-v-bd681ddd]{border:1px dashed var(--border-color);border-radius:var(--radius-md);width:100%;color:var(--text-secondary);cursor:pointer;background:0 0;justify-content:center;align-items:center;gap:6px;padding:8px 12px;font-family:inherit;font-size:13px;transition:all .15s;display:flex}.new-session-btn[data-v-bd681ddd]:hover{border-color:var(--color-primary);color:var(--color-primary);background:color-mix(in srgb, var(--color-primary) 6%, transparent)}.sidebar-search[data-v-bd681ddd]{flex-shrink:0;align-items:center;gap:6px;padding:0 12px 8px;display:flex}.sidebar-search .search-icon[data-v-bd681ddd]{color:var(--text-tertiary);flex-shrink:0}.sidebar-search .search-input[data-v-bd681ddd]{color:var(--text-primary);background:0 0;border:none;outline:none;flex:1;padding:4px 0;font-family:inherit;font-size:13px}.sidebar-search .search-input[data-v-bd681ddd]::placeholder{color:var(--text-tertiary)}.session-list[data-v-bd681ddd]{flex:1;min-height:0;padding:0 6px 8px;overflow-y:auto}.empty-state[data-v-bd681ddd]{text-align:center;color:var(--text-tertiary);flex-direction:column;justify-content:center;align-items:center;padding:48px 16px;display:flex}.empty-state .empty-text[data-v-bd681ddd]{color:var(--text-secondary);margin-top:10px;font-size:14px}.empty-state .empty-hint[data-v-bd681ddd]{margin-top:4px;font-size:12px}.session-item[data-v-bd681ddd]{border-radius:var(--radius-sm);cursor:pointer;align-items:flex-start;gap:4px;padding:8px 10px;transition:background .12s;display:flex;position:relative}.session-item[data-v-bd681ddd]:hover{background:var(--bg-hover)}.session-item:hover .session-item-actions[data-v-bd681ddd]{opacity:1}.session-item.active[data-v-bd681ddd]{background:color-mix(in srgb, var(--color-primary) 10%, transparent)}.session-item.active .session-item-title[data-v-bd681ddd]{color:var(--color-primary)}.session-item.active[data-v-bd681ddd]:before{content:"";background:var(--color-primary);border-radius:0 2px 2px 0;width:3px;height:20px;position:absolute;top:50%;left:0;transform:translateY(-50%)}.session-item-main[data-v-bd681ddd]{flex:1;min-width:0}.session-item-title[data-v-bd681ddd]{color:var(--text-primary);white-space:nowrap;text-overflow:ellipsis;margin-bottom:2px;font-size:13px;font-weight:500;overflow:hidden}.session-item-meta[data-v-bd681ddd]{color:var(--text-tertiary);align-items:center;gap:4px;font-size:11px;display:flex}.session-item-meta .meta-dot[data-v-bd681ddd]{opacity:.5}.session-item-meta .meta-source[data-v-bd681ddd]{border-radius:3px;padding:0 4px;font-size:10px;font-weight:600}.session-item-meta .meta-source.cli[data-v-bd681ddd]{background:color-mix(in srgb, var(--color-warning) 20%, transparent);color:var(--color-warning)}.session-item-actions[data-v-bd681ddd]{opacity:0;flex-shrink:0;gap:2px;transition:opacity .12s;display:flex}.item-action-btn[data-v-bd681ddd]{width:24px;height:24px;color:var(--text-tertiary);cursor:pointer;border-radius:var(--radius-xs);background:0 0;border:none;justify-content:center;align-items:center;padding:0;transition:all .12s;display:flex}.item-action-btn[data-v-bd681ddd]:hover{background:var(--bg-hover);color:var(--text-secondary)}.item-action-btn.danger[data-v-bd681ddd]:hover{color:var(--color-danger);background:color-mix(in srgb, var(--color-danger) 10%, transparent)}.sidebar-resizer[data-v-bd681ddd]{cursor:col-resize;background:0 0;flex-shrink:0;width:4px;transition:background .15s}.sidebar-resizer[data-v-bd681ddd]:hover,.sidebar-resizer.active[data-v-bd681ddd]{background:var(--color-primary);opacity:.3}.agent-chat-area[data-v-bd681ddd]{flex-direction:column;flex:1;min-width:0;display:flex;position:relative;overflow:hidden}.chat-loading[data-v-bd681ddd]{color:var(--text-tertiary);flex-direction:column;flex:1;justify-content:center;align-items:center;gap:12px;font-size:14px;display:flex}.stop-button-bar[data-v-bd681ddd]{z-index:10;position:absolute;bottom:80px;left:50%;transform:translate(-50%)}.stop-button[data-v-bd681ddd]{border:1px solid var(--color-danger);background:var(--bg-container);color:var(--color-danger);cursor:pointer;border-radius:20px;align-items:center;gap:6px;padding:6px 16px;font-family:inherit;font-size:13px;transition:all .15s;display:flex;box-shadow:0 2px 8px #0000001f}.stop-button[data-v-bd681ddd]:hover{background:var(--color-danger);color:#fff}.fade-enter-active[data-v-bd681ddd],.fade-leave-active[data-v-bd681ddd]{transition:opacity .2s}.fade-enter-from[data-v-bd681ddd],.fade-leave-to[data-v-bd681ddd]{opacity:0}[data-theme=dark]{background:var(--bg-panel)}
|
|
@@ -1,4 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-BM3Ffeng.js";import{An as e,Bt as t,Dt as n,G as r,Hr as i,Jn as a,Jr as o,Kn as s,Mn as c,Pt as l,Sn as u,Tn as d,Wr as f,_n as p,a as m,et as h,fr as g,gn as _,ht as v,i as y,in as b,ir as x,lt as S,mr as C,pn as w,r as T,rn as E,rr as D,tr as O,wn as k,xn as A,zn as j}from"./element-plus-Csu3CJKj.js";import{a as M,r as N,t as P}from"./vendor-C6ntdXkR.js";import{n as F,t as I}from"./_plugin-vue_export-helper-N7dqbp6x.js";import{t as L}from"./configStore-NaVpBxPo.js";var R=64*1024;function z(e){if(!e||!Array.isArray(e.messages))return[];let t=[],n=e.messages.length;for(let r=0;r<n;r++){let i=e.messages[r];if(i.role!==`system`){if(i.role===`user`){let e=``,n;if(typeof i.content==`string`)e=i.content;else if(Array.isArray(i.content)){e=i.content.filter(e=>e?.type===`text`).map(e=>e.text||``).join(` `);let t=i.content.filter(e=>e?.type===`image_url`&&typeof e.image_url?.url==`string`).map((e,t)=>({id:`att-${r}-${t}`,name:`image`,size:0,type:H(e.image_url?.url||``)||`image/*`,preview:e.image_url?.url}));t.length>0&&(n=t)}(e||n)&&t.push({id:`msg-${r}`,role:`user`,content:e,attachments:n,status:`done`,createdAt:Date.now()})}else if(i.role===`assistant`){let a=``;typeof i.content==`string`&&(a=i.content),a.length>R&&(a=`…(前文已截断)\n${a.slice(-R)}`);let o=[];if(i.tool_calls&&i.tool_calls.length>0)for(let e of i.tool_calls)o.push({id:e.id||e.function?.name||N(),name:e.function?.name||``,arguments:e.function?.arguments||``,argsPreview:B(e.function?.name||``,e.function?.arguments||``),status:`done`,result:``});let s=r+1;for(;s<n&&e.messages[s].role===`tool`;){let t=e.messages[s],n=t.tool_call_id||t.name||``,i=o.find(e=>e.id===n);if(i){let e=t.content;typeof e!=`string`&&(e=`(non-text result)`),e.length>R&&(e=`…(前文已截断)\n${e.slice(-R)}`),i.result=e}r=s,s++}let c=!!a,l=o.length>0;(c||l)&&t.push({id:`msg-${r}`,role:`assistant`,content:a||``,toolCalls:l?o:void 0,status:`done`,createdAt:Date.now()})}}}return t}function B(e,t){try{let n=JSON.parse(t);switch(e){case`run_command`:return String(n.command||``).slice(0,200);case`read_file`:case`write_file`:case`edit_file`:return String(n.path||``);case`list_files`:return String(n.path||`.`);case`search_text`:return String(n.pattern||``);default:return JSON.stringify(n).slice(0,200)}}catch{return t.slice(0,200)}}function V(e){return new Promise((t,n)=>{let r=new FileReader;r.onload=()=>t(String(r.result||``)),r.onerror=()=>n(r.error||Error(`FileReader failed`)),r.readAsDataURL(e)})}function H(e){let t=/^data:([^;,]+)/.exec(e);return t?t[1]:``}function U(){let e=L(),t=C([]),n=C(!1),r=C(null),i=C([]),a=C(!1),o=C(!1),s=null,c=0,l=0;async function u(){let r=++l,i=e.currentDirectory||``;if(!i){t.value=[],n.value=!1;return}n.value=!0;try{let e=`/api/agent/sessions?cwd=${encodeURIComponent(i)}`,n=await fetch(e).then(e=>e.json());if(r!==l)return;if(!n.success){y.error(n.error||F(`@AGENT:加载会话列表失败`));return}t.value=Array.isArray(n.sessions)?n.sessions:[]}catch(e){if(r!==l)return;y.error(F(`@AGENT:加载会话列表失败`)+`: `+(e?.message||e))}finally{r===l&&(n.value=!1)}}async function d(e){o.value=!0,r.value=e;try{let t=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`).then(e=>e.json());if(!t.success){y.error(t.error||F(`@AGENT:加载会话失败`));return}i.value=z(t.session)}catch(e){y.error(F(`@AGENT:加载会话失败`)+`: `+(e?.message||e))}finally{o.value=!1}}async function f(e){try{await T.confirm(F(`@AGENT:确认删除该会话`),F(`@AGENT:删除后无法恢复`),{type:`warning`})}catch{return}try{let n=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`,{method:`DELETE`}).then(e=>e.json());if(!n.success){y.error(n.error||F(`@AGENT:删除失败`));return}t.value=t.value.filter(t=>t.sessionId!==e),r.value===e&&(r.value=null,i.value=[]),y.success(F(`@AGENT:已删除`))}catch(e){y.error(F(`@AGENT:删除失败`)+`: `+(e?.message||e))}}async function p(e,n){try{let r=await fetch(`/api/agent/sessions/${encodeURIComponent(e)}`,{method:`PUT`,headers:{"Content-Type":`application/json`},body:JSON.stringify({title:n})}).then(e=>e.json());if(!r.success){y.error(r.error||F(`@AGENT:重命名失败`));return}let i=t.value.find(t=>t.sessionId===e);i&&(i.title=n),y.success(F(`@AGENT:已重命名`))}catch(e){y.error(F(`@AGENT:重命名失败`)+`: `+(e?.message||e))}}function m(){r.value=null,i.value=[]}async function h(t,n=[]){let o=n.filter(e=>e?.file?.type?.startsWith(`image/`));if(o.length<n.length&&y.warning(F(`@AGENT:仅支持发送图片,非图片文件已忽略`)),!t.trim()&&o.length===0||a.value)return;let l=(await Promise.allSettled(o.map(e=>V(e.file)))).map(e=>e.status===`fulfilled`?e.value:``).filter(e=>e.startsWith(`data:image/`)),d=++c;s=new AbortController;let f=s,p={id:N(),role:`user`,content:t,status:`done`,createdAt:Date.now()};o.length>0&&(p.attachments=o.map(e=>({id:e.id,name:e.file.name||`image`,size:e.file.size||0,type:e.file.type||``,preview:e.preview}))),i.value.push(p);let m=g({id:N(),role:`assistant`,content:``,status:`pending`,createdAt:Date.now()});i.value.push(m),a.value=!0;let h=[];try{let n=await fetch(`/api/agent/chat`,{method:`POST`,headers:{"Content-Type":`application/json`,Accept:`text/event-stream`},body:JSON.stringify({sessionId:r.value||``,userMessage:t,cwd:e.currentDirectory||``,...l.length>0?{images:l}:{}}),signal:f.signal});if(d!==c)return;if(!n.ok||!n.body){let e=await n.text().catch(()=>``);throw Error(e||`HTTP ${n.status}`)}let a=n.body.getReader(),o=new TextDecoder(`utf-8`),s=``,p=r.value;for(;;){let{value:e,done:t}=await a.read();if(d!==c)return;if(t)break;s+=o.decode(e,{stream:!0});let n=s.split(`
|
|
2
|
-
`);s=n.pop()??``;for(let e of n){let t=e.trim();if(!t.startsWith(`data:`))continue;let n=t.slice(5).trim();if(!n)continue;let i;try{i=JSON.parse(n)}catch{continue}switch(i.type){case`meta`:i.sessionId&&(p=i.sessionId,r.value=i.sessionId);break;case`thinking`:m.reasoning||(m.reasoning=``,m.reasoningStatus=`streaming`),m.reasoning+=String(i.delta||``),m.status=`streaming`;break;case`content`:m.status===`pending`&&(m.status=`streaming`);{let e=String(i.delta||``);m.content+=e;let t=P(m.content);t.reasoning&&(m.reasoning||(m.reasoning=``,m.reasoningStatus=`streaming`),m.reasoning=t.reasoning,m.content=t.content)}break;case`tool_call_start`:{let e=g({id:i.toolCallId||N(),name:i.name||``,argsPreview:i.argsPreview||``,arguments:i.argsPreview||``,status:`running`,result:``});h.push(e),m.toolCalls||=[],m.toolCalls.push(e),m.status===`pending`&&(m.status=`streaming`);break}case`tool_result`:{let e=h.find(e=>e.id===i.toolCallId);e&&(e.result=String(i.result||``),e.status=`done`);break}case`done`:{let e=P(i.content||m.content);e.reasoning&&!m.reasoning&&(m.reasoning=e.reasoning),m.content=e.content,m.status=`done`,m.reasoningStatus===`streaming`&&(m.reasoningStatus=`done`);break}case`error`:m.status=`error`,m.error=String(i.error||F(`@AGENT:未知错误`)),m.reasoningStatus===`streaming`&&(m.reasoningStatus=`done`);break}}i.value=[...i.value]}if(d!==c)return;m.status===`pending`&&(m.status=`done`),p&&p!==r.value&&(r.value=p),u().catch(()=>{})}catch(e){if(d!==c)return;e?.name===`AbortError`||f.signal.aborted?(m.content=(m.content||``)+`
|
|
3
|
-
|
|
4
|
-
[`+F(`@AGENT:已停止`)+`]`,m.status=`done`,m.reasoningStatus===`streaming`&&(m.reasoningStatus=`done`)):(m.status=`error`,m.error=e?.message||String(e),y.error(m.error||F(`@AGENT:对话失败`)))}finally{d===c&&(a.value=!1,s=null)}}function _(){s&&=(s.abort(),null)}return{sessions:t,sessionsLoading:n,currentSessionId:r,messages:i,isStreaming:a,sessionLoading:o,loadSessions:u,loadSession:d,deleteSession:f,renameSession:p,newSession:m,sendMessage:h,stop:_}}var W=c({__name:`AgentView`,setup(e,{expose:i}){i();let a=L(),{sessions:o,sessionsLoading:s,currentSessionId:c,messages:u,isStreaming:d,sessionLoading:f,loadSessions:p,loadSession:m,deleteSession:g,renameSession:_,newSession:b,sendMessage:x,stop:w}=U(),D=A(()=>{let e=a.theme;return e===`dark`||e===`auto`&&window.matchMedia(`(prefers-color-scheme: dark)`).matches?`dark`:`light`}),k=C(``),N=A(()=>{if(!k.value.trim())return o.value;let e=k.value.toLowerCase();return o.value.filter(t=>(t.title||``).toLowerCase().includes(e)||(t.model||``).toLowerCase().includes(e))}),P=A(()=>[{id:`p1`,label:F(`@AGENT:查看项目结构`),prompt:F(`@AGENT:prompt_p1`)},{id:`p2`,label:F(`@AGENT:分析代码质量`),prompt:F(`@AGENT:prompt_p2`)},{id:`p3`,label:F(`@AGENT:帮我写测试`),prompt:F(`@AGENT:prompt_p3`)},{id:`p4`,label:F(`@AGENT:Git 状态检查`),prompt:F(`@AGENT:prompt_p4`)}]);async function I(e){await x(e.text,e.files),await j(),B()}async function R(e){await x(e.prompt),await j(),B()}let z=C(null);function B(e=!0){z.value?.scrollToBottom(e)}O(()=>u.value.length,()=>{j(()=>B(!1))});function V(e){if(d.value){y.warning(F(`@AGENT:请先停止当前生成`));return}m(e)}function H(){if(d.value){y.warning(F(`@AGENT:请先停止当前生成`));return}b()}function W(e,t){if(t.stopPropagation(),d.value&&c.value===e){y.warning(F(`@AGENT:请先停止当前生成`));return}g(e)}async function G(e,t,n){n.stopPropagation();try{let{value:n}=await T.prompt(F(`@AGENT:请输入新的会话标题`),F(`@AGENT:重命名会话`),{inputValue:t,inputPattern:/.+/,inputErrorMessage:F(`@AGENT:标题不能为空`)});n&&n!==t&&await _(e,n)}catch{}}function K(e){if(!e)return``;try{let t=new Date(e);if(Number.isNaN(t.getTime()))return e;let n=new Date,r=n.getTime()-t.getTime(),i=e=>String(e).padStart(2,`0`);if(t.toDateString()===n.toDateString())return`${i(t.getHours())}:${i(t.getMinutes())}`;if(r<10080*60*1e3){let e=Math.floor(r/(1440*60*1e3));return e===0?F(`@AGENT:今天`):`${e}${F(`@AGENT:天前`)}`}return`${t.getFullYear()}-${i(t.getMonth()+1)}-${i(t.getDate())}`}catch{return e}}let q=C(280),J=C(!1);function Y(e){J.value=!0;let t=e.clientX,n=q.value,r=e=>{let r=e.clientX-t;q.value=Math.max(200,Math.min(500,n+r))},i=()=>{J.value=!1,document.removeEventListener(`mousemove`,r),document.removeEventListener(`mouseup`,i)};document.addEventListener(`mousemove`,r),document.addEventListener(`mouseup`,i)}O(()=>[a.currentDirectory,d.value],async([e,t],n)=>{let r=!n||e!==n[0],i=!!n&&n[1]===!0&&!t;(r||i)&&await p(),c.value&&!d.value&&!o.value.some(e=>e.sessionId===c.value)&&b()},{immediate:!0});let X={configStore:a,sessions:o,sessionsLoading:s,currentSessionId:c,messages:u,isStreaming:d,sessionLoading:f,loadSessions:p,loadSession:m,deleteSession:g,renameSession:_,newSession:b,sendMessage:x,stop:w,chatTheme:D,searchQuery:k,filteredSessions:N,presetQuestions:P,onSend:I,onSelectPreset:R,chatContainerRef:z,scrollToBottom:B,selectSession:V,handleNewSession:H,handleDelete:W,handleRename:G,formatDate:K,sidebarWidth:q,isResizing:J,startResize:Y,get $t(){return F},get ElTooltip(){return r},get ElIcon(){return E},get Plus(){return l},get Search(){return t},get Delete(){return S},get Edit(){return v},get ChatLineRound(){return h},get Loading(){return n},get ChatContainer(){return M}};return Object.defineProperty(X,`__isScriptSetup`,{enumerable:!1,value:!0}),X}}),G={class:`agent-view`},K={class:`sidebar-header`},q={class:`sidebar-search`},J=[`placeholder`],Y={class:`session-list`},X={key:0,class:`empty-state`},Z={class:`empty-text`},Q={key:0,class:`empty-hint`},$=[`onClick`],ee={class:`session-item-main`},te={class:`session-item-title`},ne={class:`session-item-meta`},re={class:`meta-time`},ie={class:`meta-count`},ae={key:0,class:`meta-source cli`},oe=[`onClick`],se=[`onClick`],ce={class:`agent-chat-area`},le={key:0,class:`chat-loading`},ue={key:0,class:`stop-button-bar`};function de(t,n,r,c,l,h){let g=m;return s(),d(`div`,G,[k(` ═══ 左侧:会话列表 ═══ `),u(`aside`,{class:`agent-sidebar`,style:f({width:c.sidebarWidth+`px`})},[k(` 顶部操作栏 `),u(`div`,K,[u(`button`,{class:`new-session-btn`,onClick:c.handleNewSession},[e(c.ElIcon,null,{default:D(()=>[e(c.Plus)]),_:1}),u(`span`,null,o(c.$t(`@AGENT:新建会话`)),1)])]),k(` 搜索框 `),u(`div`,q,[e(c.ElIcon,{class:`search-icon`},{default:D(()=>[e(c.Search)]),_:1}),x(u(`input`,{"onUpdate:modelValue":n[0]||=e=>c.searchQuery=e,placeholder:c.$t(`@AGENT:搜索会话...`),class:`search-input`,type:`text`},null,8,J),[[w,c.searchQuery]])]),k(` 会话列表 `),x((s(),d(`div`,Y,[c.filteredSessions.length===0&&!c.sessionsLoading?(s(),d(`div`,X,[e(c.ElIcon,{size:28,color:`var(--text-tertiary)`},{default:D(()=>[e(c.ChatLineRound)]),_:1}),u(`div`,Z,o(c.searchQuery?c.$t(`@AGENT:未找到匹配的会话`):c.$t(`@AGENT:暂无会话`)),1),c.searchQuery?k(`v-if`,!0):(s(),d(`div`,Q,o(c.$t(`@AGENT:点击上方按钮开始对话`)),1))])):k(`v-if`,!0),(s(!0),d(p,null,a(c.filteredSessions,t=>(s(),d(`div`,{key:t.sessionId,class:i([`session-item`,{active:c.currentSessionId===t.sessionId}]),onClick:e=>c.selectSession(t.sessionId)},[u(`div`,ee,[u(`div`,te,o(t.title||c.$t(`@AGENT:无标题`)),1),u(`div`,ne,[u(`span`,re,o(c.formatDate(t.updatedAt)),1),n[3]||=u(`span`,{class:`meta-dot`},`·`,-1),u(`span`,ie,o(t.messageCount)+` `+o(c.$t(`@AGENT:条`)),1),t.source===`cli`?(s(),d(`span`,ae,`CLI`)):k(`v-if`,!0)])]),u(`div`,{class:`session-item-actions`,onClick:n[1]||=_(()=>{},[`stop`])},[e(c.ElTooltip,{content:c.$t(`@AGENT:重命名`),placement:`top`,"show-after":500},{default:D(()=>[u(`button`,{class:`item-action-btn`,onClick:e=>c.handleRename(t.sessionId,t.title,e)},[e(c.ElIcon,null,{default:D(()=>[e(c.Edit)]),_:1})],8,oe)]),_:2},1032,[`content`]),e(c.ElTooltip,{content:c.$t(`@AGENT:删除`),placement:`top`,"show-after":500},{default:D(()=>[u(`button`,{class:`item-action-btn danger`,onClick:e=>c.handleDelete(t.sessionId,e)},[e(c.ElIcon,null,{default:D(()=>[e(c.Delete)]),_:1})],8,se)]),_:2},1032,[`content`])])],10,$))),128))])),[[g,c.sessionsLoading]])],4),k(` 拖拽分隔条 `),u(`div`,{class:i([`sidebar-resizer`,{active:c.isResizing}]),onMousedown:c.startResize},null,34),k(` ═══ 右侧:对话区域 ═══ `),u(`main`,ce,[k(` 加载中 `),c.sessionLoading?(s(),d(`div`,le,[e(c.ElIcon,{class:`is-loading`,size:32},{default:D(()=>[e(c.Loading)]),_:1}),u(`span`,null,o(c.$t(`@AGENT:加载会话中...`)),1)])):(s(),d(p,{key:1},[k(` ChatContainer `),e(c.ChatContainer,{ref:`chatContainerRef`,messages:c.messages,"preset-questions":c.presetQuestions,"welcome-title":c.$t(`@AGENT:智能体助手`),"welcome-description":c.$t(`@AGENT:我可以帮你阅读代码、执行命令、修改文件。选择下方话题或直接输入你的问题。`),"assistant-name":`g ai`,theme:c.chatTheme,disabled:c.isStreaming,"upload-config":{accept:`image/*`},placeholder:c.isStreaming?c.$t(`@AGENT:正在生成中...`):c.$t(`@AGENT:输入消息,Enter 发送`),onSend:c.onSend,onSelect:c.onSelectPreset},null,8,[`messages`,`preset-questions`,`welcome-title`,`welcome-description`,`theme`,`disabled`,`placeholder`])],2112)),k(` 停止按钮浮层 `),e(b,{name:`fade`},{default:D(()=>[c.isStreaming?(s(),d(`div`,ue,[u(`button`,{class:`stop-button`,onClick:n[2]||=(...e)=>c.stop&&c.stop(...e)},[e(c.ElIcon,null,{default:D(()=>[e(c.Loading,{class:`is-loading`})]),_:1}),u(`span`,null,o(c.$t(`@AGENT:停止生成`)),1)])])):k(`v-if`,!0)]),_:1})])])}var fe=I(W,[[`render`,de],[`__scopeId`,`data-v-bd681ddd`],[`__file`,`D:/xz_workspace/github_workspace/zen-gitsync/src/ui/client/src/views/AgentView.vue`]]);export{fe as default};
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import"./rolldown-runtime-BM3Ffeng.js";import{An as e,Cn as t,Dt as n,G as r,Hn as i,Hr as a,Jr as o,Kn as s,M as c,Mn as l,Sn as u,Tn as d,U as f,Wn as p,i as m,kn as h,mr as g,nt as _,r as v,rn as y,rr as b,rt as x,tr as S,wn as C,xn as w,zn as T}from"./element-plus-Csu3CJKj.js";import{n as E,t as D}from"./_plugin-vue_export-helper-N7dqbp6x.js";async function O(e,t){let n=e.body.getReader(),r=new TextDecoder(`utf-8`),i=``;for(;;){let{done:e,value:a}=await n.read();if(e)break;i+=r.decode(a,{stream:!0});let o=i.split(`
|
|
2
|
-
`);i=o.pop()??``;for(let e of o)e.trim()&&t(e)}i.trim()&&t(i)}async function k(){let e=await fetch(`/api/app-version`);return e.ok?e.json():{success:!1,current:``,latest:null,hasUpdate:!1,error:`HTTP ${e.status}`}}async function A(e){let t=await fetch(`/api/app-upgrade`,{method:`POST`});if(!t.ok){let n=`HTTP ${t.status}`;try{let e=await t.json();e?.error&&(n=e.error)}catch{}e({type:`error`,message:n}),e({type:`done`,code:t.status});return}if(!t.body){e({type:`error`,message:`响应无 body`}),e({type:`done`,code:-1});return}await O(t,t=>{try{e(JSON.parse(t))}catch{}})}async function j(){let e=await fetch(`/api/app-restart`,{method:`POST`});if(!e.ok){let t=`HTTP ${e.status}`;try{let n=await e.json();n?.error&&(t=n.error)}catch{}throw Error(t)}if(!e.body)throw Error(`响应无 body`);return new Promise((t,n)=>{let r=!1,i=e=>{r||(r=!0,e())};O(e,e=>{try{let r=JSON.parse(e);r.type===`ready`&&typeof r.port==`number`&&r.port>0?i(()=>t(r.port)):r.type===`timeout`?i(()=>n(Error(`restart.timeout`))):r.type===`error`&&i(()=>n(Error(r.message||`restart.failed`)))}catch{}}).then(()=>{i(()=>n(Error(`restart.stream-closed`)))}).catch(()=>{})})}var M=`npm install -g zen-gitsync --registry https://registry.npmjs.org/`,N=l({__name:`UpgradeDialog`,props:{modelValue:{type:Boolean,required:!0},logs:{type:String,required:!0},status:{type:String,required:!0},countdown:{type:Number,required:!1,default:0}},emits:[`update:modelValue`,`retry`,`restart`,`cancel`],setup(e,{expose:t,emit:r}){t();let i=e,a=r,o=g(null);S(()=>i.logs,async()=>{await T(),o.value&&(o.value.scrollTop=o.value.scrollHeight)});function s(){i.status!==`running`&&(i.status===`success`&&i.countdown>0&&a(`cancel`),a(`update:modelValue`,!1))}function c(){a(`retry`)}function l(){a(`restart`)}function u(){a(`cancel`),a(`update:modelValue`,!1)}async function d(){try{await navigator.clipboard.writeText(M),m.success(E(`@F13B4:已复制升级命令`))}catch{m.warning(E(`@F13B4:复制失败,请手动复制`))}}let f={MANUAL_COMMAND:M,props:i,emit:a,logEl:o,close:s,onRetry:c,onRestart:l,onLater:u,onCopyCommand:d,get Loading(){return n},get CircleCheck(){return _},get CircleClose(){return x},get $t(){return E}};return Object.defineProperty(f,`__isScriptSetup`,{enumerable:!1,value:!0}),f}}),P={key:3},F={key:4},I={key:5},L={key:0,class:`upgrade-hint`},R={key:0,class:`upgrade-countdown`},z={key:1,class:`upgrade-manual`},B={class:`upgrade-manual__label`};function V(n,r,i,l,p,m){let g=y,_=f,v=c;return s(),t(v,{"model-value":i.modelValue,"onUpdate:modelValue":r[0]||=e=>l.emit(`update:modelValue`,e),title:l.$t(`@F13B4:升级`),width:`680px`,"close-on-click-modal":!1,"close-on-press-escape":!1,"show-close":i.status!==`running`,"append-to-body":``,onClose:l.close},{footer:b(()=>[i.status===`failed`?(s(),t(_,{key:0,type:`primary`,onClick:l.onRetry},{default:b(()=>[h(o(l.$t(`@F13B4:重试`)),1)]),_:1})):C(`v-if`,!0),i.status===`failed`?(s(),t(_,{key:1,onClick:l.onCopyCommand},{default:b(()=>[h(o(l.$t(`@F13B4:复制升级命令`)),1)]),_:1})):C(`v-if`,!0),i.status===`success`?(s(),t(_,{key:2,type:`primary`,onClick:l.onRestart},{default:b(()=>[h(o(l.$t(`@F13B4:立即重启并刷新`)),1)]),_:1})):C(`v-if`,!0),i.status===`success`?(s(),t(_,{key:3,onClick:l.onLater},{default:b(()=>[h(o(l.$t(`@F13B4:稍后手动重启`)),1)]),_:1})):C(`v-if`,!0),i.status===`running`?(s(),t(_,{key:4,disabled:``},{default:b(()=>[h(o(l.$t(`@F13B4:升级中`))+`... `,1)]),_:1})):C(`v-if`,!0)]),default:b(()=>[u(`div`,{class:a([`upgrade-status`,`is-${i.status}`])},[i.status===`running`?(s(),t(g,{key:0,class:`is-loading`},{default:b(()=>[e(l.Loading)]),_:1})):i.status===`success`?(s(),t(g,{key:1,color:`#67c23a`},{default:b(()=>[e(l.CircleCheck)]),_:1})):(s(),t(g,{key:2,color:`#f56c6c`},{default:b(()=>[e(l.CircleClose)]),_:1})),i.status===`running`?(s(),d(`span`,P,o(l.$t(`@F13B4:升级中`)),1)):i.status===`success`?(s(),d(`span`,F,o(l.$t(`@F13B4:升级完成`)),1)):(s(),d(`span`,I,o(l.$t(`@F13B4:升级失败`)),1))],2),i.status===`success`?(s(),d(`p`,L,[h(o(l.$t(`@F13B4:新版本已全局安装,需要重启服务才能生效`))+` `,1),i.countdown>0?(s(),d(`span`,R,o(l.$t(`@F13B4:{seconds} 秒后自动刷新`,{seconds:i.countdown})),1)):C(`v-if`,!0)])):C(`v-if`,!0),i.status===`failed`?(s(),d(`p`,z,[u(`span`,B,o(l.$t(`@F13B4:手动升级命令`)),1),u(`code`,{class:`upgrade-manual__cmd`},o(l.MANUAL_COMMAND))])):C(`v-if`,!0),u(`pre`,{ref:`logEl`,class:`upgrade-log`},o(i.logs||l.$t(`@F13B4:等待日志输出`)),513)]),_:1},8,[`model-value`,`title`,`show-close`])}var H=D(N,[[`render`,V],[`__scopeId`,`data-v-bc566bba`],[`__file`,`D:/xz_workspace/github_workspace/zen-gitsync/src/ui/client/src/components/UpgradeDialog.vue`]]),U=`https://github.com/xz333221/zen-gitsync/releases`,W=5,G=`app-version-check`,K=3600*1e3,q=l({__name:`AppVersionBadge`,setup(e,{expose:t}){t();let n=`2.16.51`,r=g(n),a=w(()=>r.value||n),o=g(null),s=g(!1),c=g(``),l=g(`running`),u=g(0),d=null,f=g(!1);function h(){try{let e=sessionStorage.getItem(G);if(!e)return null;let{at:t,data:n}=JSON.parse(e);return Date.now()-t>K?null:n}catch{return null}}function _(e){try{sessionStorage.setItem(G,JSON.stringify({at:Date.now(),data:e}))}catch{}}async function y(e=!1){let t=h();t&&(o.value=t);try{let t=await k();o.value=t,_(t),t.current&&(r.value=t.current),!e&&t.success&&t.hasUpdate&&m({type:`success`,message:E(`@F13B4:发现新版本 {version}`,{version:t.latest??``}),duration:4e3})}catch(t){e||m({type:`warning`,message:E(`@F13B4:检查更新失败 {error}`,{error:t?.message||``})})}}async function b(){if(o.value?.hasUpdate){try{await v.confirm(E(`@F13B4:确定要从 {current} 升级到 {latest} 吗?`,{current:o.value.current,latest:o.value.latest}),E(`@F13B4:升级`),{confirmButtonText:E(`@F13B4:确定升级`),cancelButtonText:E(`@F13B4:取消`),type:`info`})}catch{return}c.value=``,l.value=`running`,s.value=!0,await A(e=>{if(e.type===`done`){if(l.value=e.code===0?`success`:`failed`,e.code===0){try{sessionStorage.removeItem(G)}catch{}o.value?.latest&&(r.value=o.value.latest),x()}}else e.message&&(c.value+=e.message)})}}function x(){S(),u.value=W,d=setInterval(()=>{--u.value,u.value<=0&&(d&&=(clearInterval(d),null),T())},1e3)}function S(){d&&=(clearInterval(d),null),u.value=0}function C(){S()}async function T(){if(S(),!f.value){f.value=!0;try{let e=await j(),{pathname:t,search:n,hash:r}=window.location;window.location.href=`http://localhost:${e}${t}${n}${r}`}catch(e){let t=e?.message||``,n={"restart.timeout":`@F13B4:重启超时`,"restart.stream-closed":`@F13B4:重启失败,请刷新页面重试`,"restart.failed":`@F13B4:重启失败`}[t];m({type:`warning`,message:n?E(n):E(`@F13B4:重启失败 {error}`,{error:t})}),f.value=!1}}}i(()=>{S()}),p(()=>{y(!0)});let D={buildVersion:n,releasesUrl:U,runtimeVersion:r,displayVersion:a,latestInfo:o,upgradeDialogVisible:s,upgradeLogs:c,upgradeStatus:l,AUTO_REFRESH_SECONDS:W,countdown:u,get countdownTimer(){return d},set countdownTimer(e){d=e},restartInFlight:f,CACHE_KEY:G,CACHE_TTL_MS:K,readCache:h,writeCache:_,checkUpdate:y,handleUpgrade:b,startAutoRefreshCountdown:x,cancelAutoRefreshCountdown:S,handleCancelAutoRefresh:C,handleRestart:T,get $t(){return E},UpgradeDialog:H};return Object.defineProperty(D,`__isScriptSetup`,{enumerable:!1,value:!0}),D}}),J={class:`app-version-badge`},Y=[`aria-label`],X=[`aria-label`],Z=[`title`,`aria-label`],Q={class:`upgrade-btn__label`},$={class:`upgrade-btn__version`};function ee(t,n,i,a,c,l){let f=r;return s(),d(`div`,J,[C(` GitHub 链接 `),e(f,{content:a.$t(`@F13B4:在 GitHub 上查看源码`),placement:`top`,effect:`dark`,"show-after":300},{default:b(()=>[u(`a`,{href:`https://github.com/xz333221/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link`,"aria-label":a.$t(`@F13B4:在 GitHub 上查看源码`)},n[1]||=[u(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[u(`path`,{d:`M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0 1 12 6.844a9.59 9.59 0 0 1 2.504.337c1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.02 10.02 0 0 0 22 12.017C22 6.484 17.522 2 12 2z`})],-1)],8,Y)]),_:1},8,[`content`]),C(` NPM 链接 `),e(f,{content:a.$t(`@F13B4:在 npm 上查看包`),placement:`top`,effect:`dark`,"show-after":300},{default:b(()=>[u(`a`,{href:`https://www.npmjs.com/package/zen-gitsync`,target:`_blank`,rel:`noopener noreferrer`,class:`badge-icon-link badge-icon-link--npm`,"aria-label":a.$t(`@F13B4:在 npm 上查看包`)},n[2]||=[u(`svg`,{viewBox:`0 0 24 24`,width:`15`,height:`15`,"aria-hidden":`true`,fill:`currentColor`,xmlns:`http://www.w3.org/2000/svg`},[u(`path`,{d:`M2 2v20h20V2H2zm17.167 17.167H12.5v-10h-3.333v10H4.833V4.833h14.334v14.334z`})],-1)],8,X)]),_:1},8,[`content`]),C(` 版本号 `),e(f,{content:a.$t(`@F13B4:查看更新`),placement:`top`,effect:`dark`,"show-after":300},{default:b(()=>[u(`a`,{href:a.releasesUrl,target:`_blank`,rel:`noopener noreferrer`,class:`version-link`},`v`+o(a.displayVersion),1)]),_:1},8,[`content`]),a.latestInfo?.hasUpdate?(s(),d(`button`,{key:0,type:`button`,class:`upgrade-btn`,title:a.$t(`@F13B4:发现新版本 {version},点击升级`,{version:a.latestInfo.latest??``}),"aria-label":a.$t(`@F13B4:发现新版本 {version},点击升级`,{version:a.latestInfo.latest??``}),onClick:a.handleUpgrade},[n[3]||=u(`span`,{class:`upgrade-btn__dot`,"aria-hidden":`true`},null,-1),u(`span`,Q,o(a.$t(`@F13B4:升级`)),1),u(`span`,$,`v`+o(a.latestInfo.latest),1)],8,Z)):C(`v-if`,!0),e(a.UpgradeDialog,{modelValue:a.upgradeDialogVisible,"onUpdate:modelValue":n[0]||=e=>a.upgradeDialogVisible=e,logs:a.upgradeLogs,status:a.upgradeStatus,countdown:a.countdown,onRetry:a.handleUpgrade,onRestart:a.handleRestart,onCancel:a.handleCancelAutoRefresh},null,8,[`modelValue`,`logs`,`status`,`countdown`])])}var te=D(q,[[`render`,ee],[`__scopeId`,`data-v-7e0300ec`],[`__file`,`D:/xz_workspace/github_workspace/zen-gitsync/src/ui/client/src/components/AppVersionBadge.vue`]]);export{te as default};
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.branch-wrapper[data-v-9548a76f]{align-items:center;gap:2px;display:inline-flex}.branch-btn[data-v-9548a76f]{cursor:pointer;height:22px;color:var(--color-text);font-family:var(--font-mono,monospace);white-space:nowrap;background:0 0;border:none;border-radius:3px;align-items:center;gap:4px;padding:0 6px;font-size:12px;transition:background .15s;display:inline-flex}.branch-btn[data-v-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-btn[data-v-9548a76f]:active{background:var(--bg-active,#ffffff1f)}[data-v-9548a76f] .branch-btn-icon{flex-shrink:0;width:14px;height:14px}.branch-btn-name[data-v-9548a76f]{text-overflow:ellipsis;max-width:160px;overflow:hidden}.branch-btn-caret[data-v-9548a76f]{opacity:.6;align-items:center;font-size:10px;display:flex}.branch-popover-inner[data-v-9548a76f]{flex-direction:column;gap:0;display:flex}.branch-search[data-v-9548a76f]{padding-bottom:8px}.branch-list[data-v-9548a76f]{max-height:240px;margin:0 -16px;padding:0 4px;overflow-y:auto}.branch-list[data-v-9548a76f]::-webkit-scrollbar{width:4px}.branch-list[data-v-9548a76f]::-webkit-scrollbar-thumb{background:var(--border-color,#8080804d);border-radius:2px}.branch-item[data-v-9548a76f]{cursor:pointer;height:28px;color:var(--color-text);border-radius:3px;align-items:center;gap:6px;padding:0 12px;font-size:13px;transition:background .1s;display:flex}.branch-item[data-v-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-item.is-active[data-v-9548a76f]{color:var(--color-primary)}.branch-item-check[data-v-9548a76f]{color:var(--color-primary);flex-shrink:0;font-size:12px}.branch-item-check-placeholder[data-v-9548a76f]{flex-shrink:0;width:12px;display:inline-block}[data-v-9548a76f] .branch-item-icon{opacity:.7;flex-shrink:0;width:13px;height:13px}.branch-item-name[data-v-9548a76f]{text-overflow:ellipsis;white-space:nowrap;flex:1;overflow:hidden}.branch-empty[data-v-9548a76f]{text-align:center;color:var(--color-text-secondary,#888);padding:12px;font-size:12px}.branch-popover-footer[data-v-9548a76f]{margin-top:4px;padding-top:6px}.branch-create-btn[data-v-9548a76f]{cursor:pointer;width:100%;height:28px;color:var(--color-text);text-align:left;background:0 0;border:none;border-radius:3px;align-items:center;gap:6px;padding:0 12px;font-size:13px;transition:background .1s;display:flex}.branch-create-btn[data-v-9548a76f]:hover{background:var(--bg-hover,#ffffff14)}.branch-info[data-v-9548a76f]{align-items:center;display:flex}.create-branch-content[data-v-9548a76f]{padding:var(--spacing-base) 0}.is-loading[data-v-9548a76f]{animation:2s linear infinite rotating-9548a76f}@keyframes rotating-9548a76f{0%{transform:rotate(0)}to{transform:rotate(360deg)}}.branch-popover{border-radius:4px!important;padding:12px 16px!important;box-shadow:0 4px 16px #0000002e!important}.create-branch-dialog .modern-select .el-select__wrapper{border-radius:var(--radius-lg);border:1px solid var(--border-input);box-shadow:var(--shadow-sm);padding:10px var(--spacing-md);background:var(--bg-container);transition:all .2s}.create-branch-dialog .modern-select .el-select__wrapper:hover{border-color:var(--color-gray-300);box-shadow:var(--shadow-md)}.create-branch-dialog .modern-select.is-focus .el-select__wrapper{border-color:#3498db;box-shadow:0 0 0 2px #3498db1a,0 2px 6px #00000014}
|