zen-gitsync 2.16.9 → 2.16.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -0
- package/package.json +1 -1
- package/src/cli/ai/agent.js +123 -15
- package/src/cli/ai/modelSetup.js +265 -25
- package/src/cli/ai/modelSetup.test.js +36 -7
- package/src/cli/ai/sessionStore.js +97 -97
- package/src/cli/ai/termui.js +173 -6
- package/src/cli/ai/termui.test.js +261 -1
- package/src/ui/public/assets/AgentView-d83DuyQq.js +4 -0
- package/src/ui/public/assets/AppVersionBadge-UNabzjNF.js +2 -0
- package/src/ui/public/assets/BranchSelector-DUbFSWiQ.js +1 -0
- package/src/ui/public/assets/CommitForm-HiRHE42z.js +6 -0
- package/src/ui/public/assets/EditorView-BvLP-NL1.js +0 -0
- package/src/ui/public/assets/FlowExecutionViewer-6mr8M454.js +1 -0
- package/src/ui/public/assets/FlowOrchestrationWorkspace-Cw2ADY-p.js +21 -0
- package/src/ui/public/assets/LogList-DLg6RUCd.js +15 -0
- package/src/ui/public/assets/MindmapView-BKIsdNvz.js +2 -0
- package/src/ui/public/assets/MonitorView-BjjHr8a5.js +1 -0
- package/src/ui/public/assets/ProjectStartupButton-C-qI63k1.js +1 -0
- package/src/ui/public/assets/RemoteRepoCard-B0q3WipX.js +5 -0
- package/src/ui/public/assets/SourceMapView-BZgc-Ylv.js +3 -0
- package/src/ui/public/assets/UserInputNode-s3Dfg2Gm.js +1 -0
- package/src/ui/public/assets/WorkbenchView-BakrUWDH.js +28 -0
- package/src/ui/public/assets/element-plus-C0loJL0J.js +32 -0
- package/src/ui/public/assets/element-plus-CXfbWXaJ.css +1 -0
- package/src/ui/public/assets/{flow-mindmap-DnyDhwJv.js → flow-mindmap-CGn6wqsF.js} +1 -1
- package/src/ui/public/assets/index-Bumd4MSq.css +1 -0
- package/src/ui/public/assets/index-Cr5-U-r6.js +82 -0
- package/src/ui/public/assets/{monaco-Dje8Rg13.js → monaco-DPQ3OaiL.js} +2 -2
- package/src/ui/public/assets/socket-io-B9UuzwTN.js +1 -0
- package/src/ui/public/assets/{ts.worker-Dth06zuC.js → ts.worker-METxwbDZ.js} +1 -16
- package/src/ui/public/assets/{vendor-DVvq7qtS.css → vendor-C2ZpZPB4.css} +1 -1
- package/src/ui/public/assets/{vendor-exjCs1jx.js → vendor-pbWglt5U.js} +26 -67
- package/src/ui/public/assets/vue-flow-DRwHh4gM.js +39 -0
- package/src/ui/public/index.html +9 -15
- package/src/ui/server/index.js +4 -0
- package/src/ui/server/routes/workbench/agentChat.js +14 -3
- package/src/ui/server/routes/workbench/agentRoutes.js +218 -212
- package/src/ui/server/routes/workbench/agentSessionStore.js +173 -173
- package/src/ui/public/assets/AgentView-CioAzh-a.js +0 -4
- package/src/ui/public/assets/AppVersionBadge-Cwg6vOMz.js +0 -2
- package/src/ui/public/assets/BranchSelector-DgxIB9XY.js +0 -1
- package/src/ui/public/assets/CommitForm-CTa0pupJ.js +0 -6
- package/src/ui/public/assets/CommonDialog-ChSKsEz2.css +0 -1
- package/src/ui/public/assets/CommonDialog-Tlk0Fa7g.js +0 -1
- package/src/ui/public/assets/EditorView-BOZPwTxr.js +0 -0
- package/src/ui/public/assets/FlowExecutionViewer-CFpiqZ2C.js +0 -1
- package/src/ui/public/assets/FlowOrchestrationWorkspace-CVYPdN7a.js +0 -21
- package/src/ui/public/assets/LogList-B4rrapVn.js +0 -15
- package/src/ui/public/assets/MindmapView-C1pPUu8W.js +0 -2
- package/src/ui/public/assets/MonitorView-C2EyggDk.js +0 -1
- package/src/ui/public/assets/ProjectStartupButton-D7mgEsmf.js +0 -1
- package/src/ui/public/assets/RemoteRepoCard-B-5MDx8k.js +0 -5
- package/src/ui/public/assets/SourceMapView-C3gZ5s1r.js +0 -3
- package/src/ui/public/assets/SvgIcon-CDNsk4-Q.css +0 -1
- package/src/ui/public/assets/SvgIcon-Diox29ks.js +0 -1
- package/src/ui/public/assets/UserInputNode-CYxFuwuS.js +0 -1
- package/src/ui/public/assets/WorkbenchView-BE53JHac.js +0 -28
- package/src/ui/public/assets/_plugin-vue_export-helper-B9cwfrdH.js +0 -15
- package/src/ui/public/assets/configStore-B8X980y1.js +0 -1
- package/src/ui/public/assets/element-plus-BId3Dnij.js +0 -25
- package/src/ui/public/assets/element-plus-D3Dn7nX0.css +0 -1
- package/src/ui/public/assets/index--onqtBRQ.css +0 -1
- package/src/ui/public/assets/index-Ds_2iFkX.js +0 -68
- package/src/ui/public/assets/socket-io-CGezBd--.js +0 -1
- package/src/ui/public/assets/vue-flow-Dj_X_Vr1.js +0 -39
- /package/src/ui/public/assets/{dagre-BCXBo6I9.js → dagre-CePNlbK4.js} +0 -0
|
@@ -88,6 +88,7 @@ ${isWin ? `- 当前是 Windows,以下 Unix 命令**不存在**,用了必定报"
|
|
|
88
88
|
# 与用户交互
|
|
89
89
|
- 需要向用户确认、提问或汇报重要决策时,直接用普通文本输出
|
|
90
90
|
- 不要调用不存在的工具,可用工具只有上面列出的 6 个
|
|
91
|
+
- 用户可能随消息附带图片:图片以 image_url 部件出现在 user 消息里;如果当前模型不支持视觉(带图请求报错),提醒用户换用支持视觉的模型
|
|
91
92
|
- 发现高风险或状态不一致的情况时:先用文本说明发现和影响,停下来等用户指示,不要擅自继续破坏性操作
|
|
92
93
|
|
|
93
94
|
# 输出
|
|
@@ -128,6 +129,7 @@ ${isWin ? `- This is Windows. The following Unix commands do NOT exist here:
|
|
|
128
129
|
- Use offset/limit for large files
|
|
129
130
|
- git operations via run_command
|
|
130
131
|
- run_command defaults to the working directory; default timeout 120s, max 600s
|
|
132
|
+
- The user may attach images to a message; they arrive as image_url parts in the user message. If the current model rejects images (no vision support), tell the user to switch to a vision-capable model
|
|
131
133
|
|
|
132
134
|
# Output
|
|
133
135
|
- Your text output is displayed in the user's Web UI
|
|
@@ -304,6 +306,7 @@ function stripStaleImages(messages) {
|
|
|
304
306
|
// - session: 从 agentSessionStore 读取的会话对象
|
|
305
307
|
// - model: { baseURL, model, apiKey }
|
|
306
308
|
// - userMessage: 用户输入文本
|
|
309
|
+
// - images: base64 dataURL 数组(可选,多模态图片,随最新一条 user 消息发给模型)
|
|
307
310
|
// - cwd: 工作目录
|
|
308
311
|
// - locale: 'zh' | 'en'
|
|
309
312
|
// - signal: AbortSignal (客户端断开时触发)
|
|
@@ -311,7 +314,7 @@ function stripStaleImages(messages) {
|
|
|
311
314
|
// - onChild: (child) => void 子进程回调(用于取消)
|
|
312
315
|
//
|
|
313
316
|
// 返回: { aborted: boolean }
|
|
314
|
-
export async function runAgentTurn({ session, model, userMessage, cwd, locale, signal, send, onChild }) {
|
|
317
|
+
export async function runAgentTurn({ session, model, userMessage, images = [], cwd, locale, signal, send, onChild }) {
|
|
315
318
|
const ctx = { cwd, locale, onChild };
|
|
316
319
|
|
|
317
320
|
// 确保 session.messages 存在
|
|
@@ -325,8 +328,16 @@ export async function runAgentTurn({ session, model, userMessage, cwd, locale, s
|
|
|
325
328
|
});
|
|
326
329
|
}
|
|
327
330
|
|
|
328
|
-
// 追加 user
|
|
329
|
-
|
|
331
|
+
// 追加 user 消息:有图片时组装 OpenAI 多模态 content 数组(与 CLI agent.js 一致),否则保持纯字符串
|
|
332
|
+
const imageParts = (Array.isArray(images) ? images : [])
|
|
333
|
+
.filter(u => typeof u === 'string' && u.startsWith('data:image/'))
|
|
334
|
+
.map(u => ({ type: 'image_url', image_url: { url: u } }));
|
|
335
|
+
session.messages.push({
|
|
336
|
+
role: 'user',
|
|
337
|
+
content: imageParts.length > 0
|
|
338
|
+
? [{ type: 'text', text: userMessage || ' ' }, ...imageParts]
|
|
339
|
+
: userMessage
|
|
340
|
+
});
|
|
330
341
|
|
|
331
342
|
// 旧图片降级
|
|
332
343
|
stripStaleImages(session.messages);
|
|
@@ -1,212 +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
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
send({ type: 'error', error: '
|
|
159
|
-
finished = true;
|
|
160
|
-
return res.end();
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
session
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
if (
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
}
|
|
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
|
+
}
|