zen-gitsync 2.16.13 → 2.16.14
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 +1 -1
- package/src/cli/ai/sessionStore.js +97 -97
- package/src/config.js +52 -19
- package/src/ui/public/assets/AgentView-tIASiLaj.js +4 -0
- package/src/ui/public/assets/AppVersionBadge-BaS_yj3n.js +2 -0
- package/src/ui/public/assets/BranchSelector-B7RamkH9.js +1 -0
- package/src/ui/public/assets/CommitForm-DUjKTZ_Z.js +6 -0
- package/src/ui/public/assets/CommonDialog-ChSKsEz2.css +1 -0
- package/src/ui/public/assets/CommonDialog-FNHz2ADP.js +1 -0
- package/src/ui/public/assets/EditorView-BsLgJ2gr.js +0 -0
- package/src/ui/public/assets/FlowExecutionViewer-B6YdupsX.js +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-DBeNhoNV.js +21 -0
- package/src/ui/public/assets/LogList-UvFfOD6q.js +15 -0
- package/src/ui/public/assets/MindmapView-DaUBWuao.js +2 -0
- package/src/ui/public/assets/MonitorView-DCYcyM3i.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-BLXDeEfO.js +1 -0
- package/src/ui/public/assets/RemoteRepoCard-RHaqsUVJ.js +5 -0
- package/src/ui/public/assets/SourceMapView-DRrnpkUE.js +3 -0
- package/src/ui/public/assets/SvgIcon-BDMUZ0Pv.js +1 -0
- package/src/ui/public/assets/SvgIcon-CDNsk4-Q.css +1 -0
- package/src/ui/public/assets/UserInputNode-BsMEfkJ_.js +1 -0
- package/src/ui/public/assets/WorkbenchView-BXBpVF4k.js +28 -0
- package/src/ui/public/assets/_plugin-vue_export-helper-DvYp0OXm.js +15 -0
- package/src/ui/public/assets/configStore-BoBfkqsG.js +1 -0
- package/src/ui/public/assets/element-plus-BId3Dnij.js +25 -0
- package/src/ui/public/assets/element-plus-D3Dn7nX0.css +1 -0
- package/src/ui/public/assets/{flow-mindmap-CGn6wqsF.js → flow-mindmap-DnyDhwJv.js} +1 -1
- package/src/ui/public/assets/index-CiQmfjtm.css +1 -0
- package/src/ui/public/assets/index-DHzXn8Zw.js +68 -0
- package/src/ui/public/assets/{monaco-DPQ3OaiL.js → monaco-Dje8Rg13.js} +2 -2
- package/src/ui/public/assets/socket-io-CGezBd--.js +1 -0
- package/src/ui/public/assets/{ts.worker-METxwbDZ.js → ts.worker-Dth06zuC.js} +16 -1
- package/src/ui/public/assets/{vendor-C2ZpZPB4.css → vendor-DVvq7qtS.css} +1 -1
- package/src/ui/public/assets/{vendor-pbWglt5U.js → vendor-exjCs1jx.js} +67 -26
- package/src/ui/public/assets/vue-flow-Dj_X_Vr1.js +39 -0
- package/src/ui/public/index.html +15 -9
- package/src/ui/server/routes/git.js +36 -5
- package/src/ui/server/routes/git.test.js +147 -0
- package/src/ui/server/routes/gitOps.js +107 -3
- package/src/ui/server/routes/workbench/agentRoutes.js +218 -218
- package/src/ui/server/routes/workbench/agentSessionStore.js +173 -173
- package/src/ui/public/assets/AgentView-DpHVftKB.js +0 -4
- package/src/ui/public/assets/AppVersionBadge-DoBF_YRQ.js +0 -2
- package/src/ui/public/assets/BranchSelector-BG5-5Rji.js +0 -1
- package/src/ui/public/assets/CommitForm-CRvh9bMD.js +0 -6
- package/src/ui/public/assets/EditorView-D8Rj7g4x.js +0 -0
- package/src/ui/public/assets/FlowExecutionViewer-BblOVK2I.js +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-DYJtdPCz.js +0 -21
- package/src/ui/public/assets/LogList-BJDC-tvx.js +0 -15
- package/src/ui/public/assets/MindmapView-DVKkHt-H.js +0 -2
- package/src/ui/public/assets/MonitorView-ZHUyTSjI.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-CRpmOPMY.js +0 -1
- package/src/ui/public/assets/RemoteRepoCard-oN1X-RTu.js +0 -5
- package/src/ui/public/assets/SourceMapView-DWjOj5HG.js +0 -3
- package/src/ui/public/assets/UserInputNode-DUS9U0qM.js +0 -1
- package/src/ui/public/assets/WorkbenchView-qCRoJV-3.js +0 -28
- package/src/ui/public/assets/element-plus-C0loJL0J.js +0 -32
- package/src/ui/public/assets/element-plus-CXfbWXaJ.css +0 -1
- package/src/ui/public/assets/index-DLfjXhwQ.js +0 -82
- package/src/ui/public/assets/index-Fbjp0DKn.css +0 -1
- package/src/ui/public/assets/socket-io-B9UuzwTN.js +0 -1
- package/src/ui/public/assets/vue-flow-DRwHh4gM.js +0 -39
- /package/src/ui/public/assets/{dagre-CePNlbK4.js → dagre-BCXBo6I9.js} +0 -0
|
@@ -1,218 +1,218 @@
|
|
|
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
|
-
// GET /api/agent/sessions — 列出所有会话(仅 metadata)
|
|
19
|
-
// GET /api/agent/sessions/:id — 获取会话详情(含完整消息)
|
|
20
|
-
// DELETE /api/agent/sessions/:id — 删除会话
|
|
21
|
-
// PUT /api/agent/sessions/:id — 重命名会话
|
|
22
|
-
// POST /api/agent/chat — SSE 流式聊天(含工具调用)
|
|
23
|
-
|
|
24
|
-
import { asyncRoute, HttpError } from '../../utils/asyncRoute.js';
|
|
25
|
-
import { agentSessionStore } from './agentSessionStore.js';
|
|
26
|
-
import { runAgentTurn } from './agentChat.js';
|
|
27
|
-
import { nowIso } from './shared.js';
|
|
28
|
-
|
|
29
|
-
const { genSessionId, autoTitle, read: readSession, write: writeSession, delete: deleteSession, listMeta: listSessionsMeta, enforceRetention, rename: renameSession } = agentSessionStore;
|
|
30
|
-
|
|
31
|
-
/**
|
|
32
|
-
* 注册智能体路由。
|
|
33
|
-
* @param {Object} deps
|
|
34
|
-
* @param {import('express').Express} deps.app
|
|
35
|
-
* @param {() => string} deps.getCurrentProjectPath
|
|
36
|
-
* @param {Object} deps.configManager
|
|
37
|
-
*/
|
|
38
|
-
export function registerAgentRoutes({ app, getCurrentProjectPath, configManager }) {
|
|
39
|
-
|
|
40
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
41
|
-
// §1. 会话列表
|
|
42
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
43
|
-
app.get('/api/agent/sessions', asyncRoute(async (_req, res) => {
|
|
44
|
-
const sessions = await listSessionsMeta();
|
|
45
|
-
res.json({ success: true, sessions });
|
|
46
|
-
}));
|
|
47
|
-
|
|
48
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
49
|
-
// §2. 会话详情
|
|
50
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
51
|
-
app.get('/api/agent/sessions/:sessionId', asyncRoute(async (req, res) => {
|
|
52
|
-
const session = await readSession(req.params.sessionId);
|
|
53
|
-
res.json({ success: true, session });
|
|
54
|
-
}));
|
|
55
|
-
|
|
56
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
57
|
-
// §3. 删除会话
|
|
58
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
59
|
-
app.delete('/api/agent/sessions/:sessionId', asyncRoute(async (req, res) => {
|
|
60
|
-
await deleteSession(req.params.sessionId);
|
|
61
|
-
res.json({ success: true });
|
|
62
|
-
}));
|
|
63
|
-
|
|
64
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
65
|
-
// §4. 重命名会话
|
|
66
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
67
|
-
app.put('/api/agent/sessions/:sessionId', asyncRoute(async (req, res) => {
|
|
68
|
-
const title = String(req.body?.title || '').trim();
|
|
69
|
-
if (!title) throw new HttpError(400, '标题不能为空');
|
|
70
|
-
const session = await renameSession(req.params.sessionId, title);
|
|
71
|
-
res.json({ success: true, session });
|
|
72
|
-
}));
|
|
73
|
-
|
|
74
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
75
|
-
// §5. SSE 流式聊天(含工具调用循环)
|
|
76
|
-
// ════════════════════════════════════════════════════════════════════════
|
|
77
|
-
app.post('/api/agent/chat', async (req, res) => {
|
|
78
|
-
const userMessage = String(req.body?.userMessage || '').trim();
|
|
79
|
-
const sessionIdInput = String(req.body?.sessionId || '').trim();
|
|
80
|
-
const locale = String(req.body?.locale || req.headers['accept-language'] || 'zh').startsWith('en') ? 'en' : 'zh';
|
|
81
|
-
|
|
82
|
-
// 图片附件(base64 dataURL 数组):前端已限制只能选图片,这里再做一层白名单校验
|
|
83
|
-
const images = (Array.isArray(req.body?.images) ? req.body.images : [])
|
|
84
|
-
.filter(u => typeof u === 'string' && /^data:image\/[\w.+-]+;base64,/.test(u))
|
|
85
|
-
.slice(0, 10);
|
|
86
|
-
|
|
87
|
-
if (!userMessage && images.length === 0) {
|
|
88
|
-
return res.status(400).json({ success: false, error: '消息内容不能为空' });
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
// SSE 头
|
|
92
|
-
res.set({
|
|
93
|
-
'Content-Type': 'text/event-stream',
|
|
94
|
-
'Cache-Control': 'no-cache, no-transform',
|
|
95
|
-
'Connection': 'keep-alive',
|
|
96
|
-
'X-Accel-Buffering': 'no'
|
|
97
|
-
});
|
|
98
|
-
res.flushHeaders?.();
|
|
99
|
-
const send = (obj) => {
|
|
100
|
-
try { res.write(`data: ${JSON.stringify(obj)}\n\n`); } catch {}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const abortController = new AbortController();
|
|
104
|
-
let finished = false;
|
|
105
|
-
let activeChild = null;
|
|
106
|
-
|
|
107
|
-
// 客户端断开
|
|
108
|
-
if (req.socket) {
|
|
109
|
-
req.socket.once('close', () => {
|
|
110
|
-
if (!finished) {
|
|
111
|
-
abortController.abort();
|
|
112
|
-
if (activeChild) {
|
|
113
|
-
try { activeChild.kill('SIGTERM'); } catch {}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
try {
|
|
120
|
-
// 加载或新建 session
|
|
121
|
-
let session;
|
|
122
|
-
let isNew = false;
|
|
123
|
-
|
|
124
|
-
if (sessionIdInput) {
|
|
125
|
-
try {
|
|
126
|
-
session = await readSession(sessionIdInput);
|
|
127
|
-
} catch (err) {
|
|
128
|
-
if (err.statusCode === 404) {
|
|
129
|
-
send({ type: 'error', error: '会话不存在' });
|
|
130
|
-
finished = true;
|
|
131
|
-
return res.end();
|
|
132
|
-
}
|
|
133
|
-
throw err;
|
|
134
|
-
}
|
|
135
|
-
} else {
|
|
136
|
-
session = {
|
|
137
|
-
version: 1,
|
|
138
|
-
sessionId: genSessionId(),
|
|
139
|
-
title: '(新会话)',
|
|
140
|
-
source: 'web',
|
|
141
|
-
cwd: typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : process.cwd(),
|
|
142
|
-
model: '',
|
|
143
|
-
createdAt: nowIso(),
|
|
144
|
-
updatedAt: nowIso(),
|
|
145
|
-
messages: []
|
|
146
|
-
};
|
|
147
|
-
isNew = true;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// 获取模型配置
|
|
151
|
-
let model;
|
|
152
|
-
try {
|
|
153
|
-
if (!configManager) throw new Error('configManager 不可用');
|
|
154
|
-
const rawConfig = await configManager.readRawConfigFile();
|
|
155
|
-
const models = Array.isArray(rawConfig.models) ? rawConfig.models : [];
|
|
156
|
-
model = models.find(m => m.isDefault) || models[0];
|
|
157
|
-
} catch (err) {
|
|
158
|
-
send({ type: 'error', error: '读取 AI 配置失败: ' + err.message });
|
|
159
|
-
finished = true;
|
|
160
|
-
return res.end();
|
|
161
|
-
}
|
|
162
|
-
if (!model) {
|
|
163
|
-
send({ type: 'error', error: '未配置 AI 模型,请先在通用设置中添加模型' });
|
|
164
|
-
finished = true;
|
|
165
|
-
return res.end();
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
// 更新 session 的 model 信息
|
|
169
|
-
session.model = `${model.model || ''} (${model.name || ''})`;
|
|
170
|
-
|
|
171
|
-
// 工作目录
|
|
172
|
-
const cwd = session.cwd || (typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : process.cwd());
|
|
173
|
-
|
|
174
|
-
// 推 meta
|
|
175
|
-
send({
|
|
176
|
-
type: 'meta',
|
|
177
|
-
sessionId: session.sessionId,
|
|
178
|
-
isNew,
|
|
179
|
-
title: isNew ? autoTitle([{ role: 'user', content: userMessage }]) : session.title
|
|
180
|
-
});
|
|
181
|
-
|
|
182
|
-
// 运行 agent 循环
|
|
183
|
-
const { aborted } = await runAgentTurn({
|
|
184
|
-
session,
|
|
185
|
-
model,
|
|
186
|
-
userMessage,
|
|
187
|
-
images,
|
|
188
|
-
cwd,
|
|
189
|
-
locale,
|
|
190
|
-
signal: abortController.signal,
|
|
191
|
-
send,
|
|
192
|
-
onChild: (child) => { activeChild = child; }
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
if (aborted) {
|
|
196
|
-
finished = true;
|
|
197
|
-
return res.end();
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
// 更新标题(新会话从第一条 user 消息自动生成)
|
|
201
|
-
if (isNew) {
|
|
202
|
-
session.title = autoTitle(session.messages);
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// 持久化
|
|
206
|
-
session.updatedAt = nowIso();
|
|
207
|
-
await writeSession(session.sessionId, session);
|
|
208
|
-
enforceRetention().catch(() => {});
|
|
209
|
-
|
|
210
|
-
finished = true;
|
|
211
|
-
res.end();
|
|
212
|
-
} catch (err) {
|
|
213
|
-
send({ type: 'error', error: '智能体对话失败: ' + (err?.message || String(err)) });
|
|
214
|
-
finished = true;
|
|
215
|
-
res.end();
|
|
216
|
-
}
|
|
217
|
-
});
|
|
218
|
-
}
|
|
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
|
+
// GET /api/agent/sessions — 列出所有会话(仅 metadata)
|
|
19
|
+
// GET /api/agent/sessions/:id — 获取会话详情(含完整消息)
|
|
20
|
+
// DELETE /api/agent/sessions/:id — 删除会话
|
|
21
|
+
// PUT /api/agent/sessions/:id — 重命名会话
|
|
22
|
+
// POST /api/agent/chat — SSE 流式聊天(含工具调用)
|
|
23
|
+
|
|
24
|
+
import { asyncRoute, HttpError } from '../../utils/asyncRoute.js';
|
|
25
|
+
import { agentSessionStore } from './agentSessionStore.js';
|
|
26
|
+
import { runAgentTurn } from './agentChat.js';
|
|
27
|
+
import { nowIso } from './shared.js';
|
|
28
|
+
|
|
29
|
+
const { genSessionId, autoTitle, read: readSession, write: writeSession, delete: deleteSession, listMeta: listSessionsMeta, enforceRetention, rename: renameSession } = agentSessionStore;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 注册智能体路由。
|
|
33
|
+
* @param {Object} deps
|
|
34
|
+
* @param {import('express').Express} deps.app
|
|
35
|
+
* @param {() => string} deps.getCurrentProjectPath
|
|
36
|
+
* @param {Object} deps.configManager
|
|
37
|
+
*/
|
|
38
|
+
export function registerAgentRoutes({ app, getCurrentProjectPath, configManager }) {
|
|
39
|
+
|
|
40
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
41
|
+
// §1. 会话列表
|
|
42
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
43
|
+
app.get('/api/agent/sessions', asyncRoute(async (_req, res) => {
|
|
44
|
+
const sessions = await listSessionsMeta();
|
|
45
|
+
res.json({ success: true, sessions });
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
49
|
+
// §2. 会话详情
|
|
50
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
51
|
+
app.get('/api/agent/sessions/:sessionId', asyncRoute(async (req, res) => {
|
|
52
|
+
const session = await readSession(req.params.sessionId);
|
|
53
|
+
res.json({ success: true, session });
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
57
|
+
// §3. 删除会话
|
|
58
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
59
|
+
app.delete('/api/agent/sessions/:sessionId', asyncRoute(async (req, res) => {
|
|
60
|
+
await deleteSession(req.params.sessionId);
|
|
61
|
+
res.json({ success: true });
|
|
62
|
+
}));
|
|
63
|
+
|
|
64
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
65
|
+
// §4. 重命名会话
|
|
66
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
67
|
+
app.put('/api/agent/sessions/:sessionId', asyncRoute(async (req, res) => {
|
|
68
|
+
const title = String(req.body?.title || '').trim();
|
|
69
|
+
if (!title) throw new HttpError(400, '标题不能为空');
|
|
70
|
+
const session = await renameSession(req.params.sessionId, title);
|
|
71
|
+
res.json({ success: true, session });
|
|
72
|
+
}));
|
|
73
|
+
|
|
74
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
75
|
+
// §5. SSE 流式聊天(含工具调用循环)
|
|
76
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
77
|
+
app.post('/api/agent/chat', async (req, res) => {
|
|
78
|
+
const userMessage = String(req.body?.userMessage || '').trim();
|
|
79
|
+
const sessionIdInput = String(req.body?.sessionId || '').trim();
|
|
80
|
+
const locale = String(req.body?.locale || req.headers['accept-language'] || 'zh').startsWith('en') ? 'en' : 'zh';
|
|
81
|
+
|
|
82
|
+
// 图片附件(base64 dataURL 数组):前端已限制只能选图片,这里再做一层白名单校验
|
|
83
|
+
const images = (Array.isArray(req.body?.images) ? req.body.images : [])
|
|
84
|
+
.filter(u => typeof u === 'string' && /^data:image\/[\w.+-]+;base64,/.test(u))
|
|
85
|
+
.slice(0, 10);
|
|
86
|
+
|
|
87
|
+
if (!userMessage && images.length === 0) {
|
|
88
|
+
return res.status(400).json({ success: false, error: '消息内容不能为空' });
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// SSE 头
|
|
92
|
+
res.set({
|
|
93
|
+
'Content-Type': 'text/event-stream',
|
|
94
|
+
'Cache-Control': 'no-cache, no-transform',
|
|
95
|
+
'Connection': 'keep-alive',
|
|
96
|
+
'X-Accel-Buffering': 'no'
|
|
97
|
+
});
|
|
98
|
+
res.flushHeaders?.();
|
|
99
|
+
const send = (obj) => {
|
|
100
|
+
try { res.write(`data: ${JSON.stringify(obj)}\n\n`); } catch {}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const abortController = new AbortController();
|
|
104
|
+
let finished = false;
|
|
105
|
+
let activeChild = null;
|
|
106
|
+
|
|
107
|
+
// 客户端断开
|
|
108
|
+
if (req.socket) {
|
|
109
|
+
req.socket.once('close', () => {
|
|
110
|
+
if (!finished) {
|
|
111
|
+
abortController.abort();
|
|
112
|
+
if (activeChild) {
|
|
113
|
+
try { activeChild.kill('SIGTERM'); } catch {}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
// 加载或新建 session
|
|
121
|
+
let session;
|
|
122
|
+
let isNew = false;
|
|
123
|
+
|
|
124
|
+
if (sessionIdInput) {
|
|
125
|
+
try {
|
|
126
|
+
session = await readSession(sessionIdInput);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
if (err.statusCode === 404) {
|
|
129
|
+
send({ type: 'error', error: '会话不存在' });
|
|
130
|
+
finished = true;
|
|
131
|
+
return res.end();
|
|
132
|
+
}
|
|
133
|
+
throw err;
|
|
134
|
+
}
|
|
135
|
+
} else {
|
|
136
|
+
session = {
|
|
137
|
+
version: 1,
|
|
138
|
+
sessionId: genSessionId(),
|
|
139
|
+
title: '(新会话)',
|
|
140
|
+
source: 'web',
|
|
141
|
+
cwd: typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : process.cwd(),
|
|
142
|
+
model: '',
|
|
143
|
+
createdAt: nowIso(),
|
|
144
|
+
updatedAt: nowIso(),
|
|
145
|
+
messages: []
|
|
146
|
+
};
|
|
147
|
+
isNew = true;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// 获取模型配置
|
|
151
|
+
let model;
|
|
152
|
+
try {
|
|
153
|
+
if (!configManager) throw new Error('configManager 不可用');
|
|
154
|
+
const rawConfig = await configManager.readRawConfigFile();
|
|
155
|
+
const models = Array.isArray(rawConfig.models) ? rawConfig.models : [];
|
|
156
|
+
model = models.find(m => m.isDefault) || models[0];
|
|
157
|
+
} catch (err) {
|
|
158
|
+
send({ type: 'error', error: '读取 AI 配置失败: ' + err.message });
|
|
159
|
+
finished = true;
|
|
160
|
+
return res.end();
|
|
161
|
+
}
|
|
162
|
+
if (!model) {
|
|
163
|
+
send({ type: 'error', error: '未配置 AI 模型,请先在通用设置中添加模型' });
|
|
164
|
+
finished = true;
|
|
165
|
+
return res.end();
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// 更新 session 的 model 信息
|
|
169
|
+
session.model = `${model.model || ''} (${model.name || ''})`;
|
|
170
|
+
|
|
171
|
+
// 工作目录
|
|
172
|
+
const cwd = session.cwd || (typeof getCurrentProjectPath === 'function' ? getCurrentProjectPath() : process.cwd());
|
|
173
|
+
|
|
174
|
+
// 推 meta
|
|
175
|
+
send({
|
|
176
|
+
type: 'meta',
|
|
177
|
+
sessionId: session.sessionId,
|
|
178
|
+
isNew,
|
|
179
|
+
title: isNew ? autoTitle([{ role: 'user', content: userMessage }]) : session.title
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
// 运行 agent 循环
|
|
183
|
+
const { aborted } = await runAgentTurn({
|
|
184
|
+
session,
|
|
185
|
+
model,
|
|
186
|
+
userMessage,
|
|
187
|
+
images,
|
|
188
|
+
cwd,
|
|
189
|
+
locale,
|
|
190
|
+
signal: abortController.signal,
|
|
191
|
+
send,
|
|
192
|
+
onChild: (child) => { activeChild = child; }
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
if (aborted) {
|
|
196
|
+
finished = true;
|
|
197
|
+
return res.end();
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 更新标题(新会话从第一条 user 消息自动生成)
|
|
201
|
+
if (isNew) {
|
|
202
|
+
session.title = autoTitle(session.messages);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// 持久化
|
|
206
|
+
session.updatedAt = nowIso();
|
|
207
|
+
await writeSession(session.sessionId, session);
|
|
208
|
+
enforceRetention().catch(() => {});
|
|
209
|
+
|
|
210
|
+
finished = true;
|
|
211
|
+
res.end();
|
|
212
|
+
} catch (err) {
|
|
213
|
+
send({ type: 'error', error: '智能体对话失败: ' + (err?.message || String(err)) });
|
|
214
|
+
finished = true;
|
|
215
|
+
res.end();
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
}
|