@xcanwin/manyoyo 5.6.8 → 5.6.10
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/lib/agent-resume.js +10 -1
- package/lib/web/frontend/app.css +158 -0
- package/lib/web/frontend/app.html +4 -1
- package/lib/web/frontend/app.js +518 -27
- package/lib/web/server.js +617 -52
- package/package.json +1 -1
package/lib/web/frontend/app.js
CHANGED
|
@@ -64,6 +64,13 @@
|
|
|
64
64
|
sessionNodeMap: new Map(),
|
|
65
65
|
sessionRenderMode: 'empty',
|
|
66
66
|
messageRequestId: 0,
|
|
67
|
+
agentRun: {
|
|
68
|
+
active: false,
|
|
69
|
+
sessionName: '',
|
|
70
|
+
stopping: false,
|
|
71
|
+
controller: null,
|
|
72
|
+
traceMessageId: ''
|
|
73
|
+
},
|
|
67
74
|
terminal: {
|
|
68
75
|
term: null,
|
|
69
76
|
fitAddon: null,
|
|
@@ -142,6 +149,7 @@
|
|
|
142
149
|
const composerHint = document.getElementById('composerHint');
|
|
143
150
|
const sendState = document.getElementById('sendState');
|
|
144
151
|
const sendBtn = document.getElementById('sendBtn');
|
|
152
|
+
const stopBtn = document.getElementById('stopBtn');
|
|
145
153
|
const refreshBtn = document.getElementById('refreshBtn');
|
|
146
154
|
const removeBtn = document.getElementById('removeBtn');
|
|
147
155
|
const removeAllBtn = document.getElementById('removeAllBtn');
|
|
@@ -182,9 +190,204 @@
|
|
|
182
190
|
bubble.appendChild(pre);
|
|
183
191
|
}
|
|
184
192
|
|
|
193
|
+
function stringifyPrettyJson(value) {
|
|
194
|
+
if (value === undefined || value === null) {
|
|
195
|
+
return '';
|
|
196
|
+
}
|
|
197
|
+
if (typeof value === 'string') {
|
|
198
|
+
return value;
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
return JSON.stringify(value, null, 2);
|
|
202
|
+
} catch (e) {
|
|
203
|
+
return String(value);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function humanizeTraceKind(traceEvent) {
|
|
208
|
+
const kind = traceEvent && traceEvent.kind ? String(traceEvent.kind) : '';
|
|
209
|
+
if (kind === 'thread') return '会话';
|
|
210
|
+
if (kind === 'turn') return '回合';
|
|
211
|
+
if (kind === 'status') return '状态';
|
|
212
|
+
if (kind === 'agent_message') return '说明';
|
|
213
|
+
if (kind === 'command') return '命令';
|
|
214
|
+
if (kind === 'mcp') return 'MCP';
|
|
215
|
+
if (kind === 'tool') return '工具';
|
|
216
|
+
if (kind === 'error') return '错误';
|
|
217
|
+
return '事件';
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function humanizeTracePhase(traceEvent) {
|
|
221
|
+
const phase = traceEvent && traceEvent.phase ? String(traceEvent.phase) : '';
|
|
222
|
+
if (phase === 'started') return '开始';
|
|
223
|
+
if (phase === 'completed') return '完成';
|
|
224
|
+
const status = traceEvent && traceEvent.status ? String(traceEvent.status).trim() : '';
|
|
225
|
+
return status;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function buildStructuredTraceResidualLines(message) {
|
|
229
|
+
const lines = String(message && message.content ? message.content : '')
|
|
230
|
+
.split('\n')
|
|
231
|
+
.map(function (line) {
|
|
232
|
+
return String(line || '').trim();
|
|
233
|
+
})
|
|
234
|
+
.filter(Boolean);
|
|
235
|
+
const traceEvents = Array.isArray(message && message.traceEvents) ? message.traceEvents : [];
|
|
236
|
+
const consumed = new Map();
|
|
237
|
+
traceEvents.forEach(function (traceEvent) {
|
|
238
|
+
const key = traceEvent && traceEvent.text ? String(traceEvent.text).trim() : '';
|
|
239
|
+
if (!key) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
consumed.set(key, (consumed.get(key) || 0) + 1);
|
|
243
|
+
});
|
|
244
|
+
return lines.filter(function (line) {
|
|
245
|
+
if (!line || line === '[执行过程]') {
|
|
246
|
+
return false;
|
|
247
|
+
}
|
|
248
|
+
const remaining = consumed.get(line) || 0;
|
|
249
|
+
if (remaining > 0) {
|
|
250
|
+
consumed.set(line, remaining - 1);
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
return true;
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function resolveTraceTone(traceEvent) {
|
|
258
|
+
const kind = traceEvent && traceEvent.kind ? String(traceEvent.kind) : '';
|
|
259
|
+
if (kind === 'command') return 'command';
|
|
260
|
+
if (kind === 'mcp') return 'mcp';
|
|
261
|
+
if (kind === 'error') return 'error';
|
|
262
|
+
if (kind === 'agent_message') return 'note';
|
|
263
|
+
if (kind === 'status') return 'status';
|
|
264
|
+
return 'neutral';
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function appendTraceCardBody(cardBody, label, value) {
|
|
268
|
+
const text = stringifyPrettyJson(value).trim();
|
|
269
|
+
if (!text) {
|
|
270
|
+
return;
|
|
271
|
+
}
|
|
272
|
+
const section = document.createElement('div');
|
|
273
|
+
section.className = 'trace-card-body-section';
|
|
274
|
+
|
|
275
|
+
const title = document.createElement('div');
|
|
276
|
+
title.className = 'trace-card-body-label';
|
|
277
|
+
title.textContent = label;
|
|
278
|
+
section.appendChild(title);
|
|
279
|
+
|
|
280
|
+
const pre = document.createElement('pre');
|
|
281
|
+
pre.className = 'trace-card-body-pre';
|
|
282
|
+
pre.textContent = text;
|
|
283
|
+
section.appendChild(pre);
|
|
284
|
+
|
|
285
|
+
cardBody.appendChild(section);
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
function createTraceEventCard(traceEvent) {
|
|
289
|
+
const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
|
|
290
|
+
const bodyParts = [];
|
|
291
|
+
if (event.kind === 'command' && event.command) {
|
|
292
|
+
bodyParts.push({ label: '命令', value: event.command });
|
|
293
|
+
}
|
|
294
|
+
if (event.kind === 'mcp') {
|
|
295
|
+
if (event.argumentSummary) {
|
|
296
|
+
bodyParts.push({ label: '参数摘要', value: event.argumentSummary });
|
|
297
|
+
}
|
|
298
|
+
if (event.arguments) {
|
|
299
|
+
bodyParts.push({ label: '参数', value: event.arguments });
|
|
300
|
+
}
|
|
301
|
+
if (event.result) {
|
|
302
|
+
bodyParts.push({ label: '结果', value: event.result });
|
|
303
|
+
}
|
|
304
|
+
if (event.error) {
|
|
305
|
+
bodyParts.push({ label: '错误', value: event.error });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
if (event.kind === 'tool' && event.toolName) {
|
|
309
|
+
bodyParts.push({ label: '工具', value: event.toolName });
|
|
310
|
+
}
|
|
311
|
+
if ((event.kind === 'agent_message' || event.kind === 'status' || event.kind === 'error') && event.detail) {
|
|
312
|
+
bodyParts.push({ label: '详情', value: event.detail });
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const hasBody = bodyParts.length > 0;
|
|
316
|
+
const card = document.createElement(hasBody ? 'details' : 'div');
|
|
317
|
+
card.className = 'trace-card trace-tone-' + resolveTraceTone(event);
|
|
318
|
+
if (hasBody && event.kind === 'error') {
|
|
319
|
+
card.open = true;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const header = document.createElement(hasBody ? 'summary' : 'div');
|
|
323
|
+
header.className = 'trace-card-summary';
|
|
324
|
+
|
|
325
|
+
const badge = document.createElement('span');
|
|
326
|
+
badge.className = 'trace-card-badge';
|
|
327
|
+
badge.textContent = humanizeTraceKind(event);
|
|
328
|
+
header.appendChild(badge);
|
|
329
|
+
|
|
330
|
+
const title = document.createElement('span');
|
|
331
|
+
title.className = 'trace-card-title';
|
|
332
|
+
title.textContent = event && event.text ? String(event.text) : '事件';
|
|
333
|
+
header.appendChild(title);
|
|
334
|
+
|
|
335
|
+
const phaseText = humanizeTracePhase(event);
|
|
336
|
+
if (phaseText) {
|
|
337
|
+
const phase = document.createElement('span');
|
|
338
|
+
phase.className = 'trace-card-phase';
|
|
339
|
+
phase.textContent = phaseText;
|
|
340
|
+
header.appendChild(phase);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
card.appendChild(header);
|
|
344
|
+
|
|
345
|
+
if (hasBody) {
|
|
346
|
+
const body = document.createElement('div');
|
|
347
|
+
body.className = 'trace-card-body';
|
|
348
|
+
bodyParts.forEach(function (part) {
|
|
349
|
+
appendTraceCardBody(body, part.label, part.value);
|
|
350
|
+
});
|
|
351
|
+
card.appendChild(body);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
return card;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function appendStructuredTraceContent(bubble, message) {
|
|
358
|
+
bubble.classList.add('trace-bubble');
|
|
359
|
+
const container = document.createElement('div');
|
|
360
|
+
container.className = 'trace-structured';
|
|
361
|
+
|
|
362
|
+
const residualLines = buildStructuredTraceResidualLines(message);
|
|
363
|
+
if (residualLines.length) {
|
|
364
|
+
const summary = document.createElement('div');
|
|
365
|
+
summary.className = 'trace-summary';
|
|
366
|
+
residualLines.forEach(function (line) {
|
|
367
|
+
const item = document.createElement('div');
|
|
368
|
+
item.className = 'trace-summary-line';
|
|
369
|
+
item.textContent = line;
|
|
370
|
+
summary.appendChild(item);
|
|
371
|
+
});
|
|
372
|
+
container.appendChild(summary);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
const flow = document.createElement('div');
|
|
376
|
+
flow.className = 'trace-flow';
|
|
377
|
+
(Array.isArray(message && message.traceEvents) ? message.traceEvents : []).forEach(function (traceEvent) {
|
|
378
|
+
flow.appendChild(createTraceEventCard(traceEvent));
|
|
379
|
+
});
|
|
380
|
+
container.appendChild(flow);
|
|
381
|
+
|
|
382
|
+
bubble.appendChild(container);
|
|
383
|
+
}
|
|
384
|
+
|
|
185
385
|
function roleName(role, message) {
|
|
186
386
|
if (role === 'user') return '我';
|
|
187
387
|
if (role === 'assistant') {
|
|
388
|
+
if (message && message.streamTrace) {
|
|
389
|
+
return 'AGENT 过程';
|
|
390
|
+
}
|
|
188
391
|
if (message && message.mode === 'agent') {
|
|
189
392
|
return 'AGENT 回复';
|
|
190
393
|
}
|
|
@@ -357,7 +560,11 @@
|
|
|
357
560
|
|
|
358
561
|
function resolveAgentPromptTemplate(commandText) {
|
|
359
562
|
const program = resolveAgentProgram(commandText);
|
|
360
|
-
|
|
563
|
+
const template = AGENT_PROMPT_TEMPLATE_MAP[program] || '';
|
|
564
|
+
if (program === 'codex' && String(commandText || '').includes('--dangerously-bypass-approvals-and-sandbox')) {
|
|
565
|
+
return 'codex exec --dangerously-bypass-approvals-and-sandbox --skip-git-repo-check {prompt}';
|
|
566
|
+
}
|
|
567
|
+
return template;
|
|
361
568
|
}
|
|
362
569
|
|
|
363
570
|
function inferCreateAgentPromptCommand() {
|
|
@@ -541,6 +748,15 @@
|
|
|
541
748
|
}) || null;
|
|
542
749
|
}
|
|
543
750
|
|
|
751
|
+
function isAgentRunActiveForSession(sessionName) {
|
|
752
|
+
return Boolean(
|
|
753
|
+
state.agentRun
|
|
754
|
+
&& state.agentRun.active
|
|
755
|
+
&& state.agentRun.sessionName
|
|
756
|
+
&& state.agentRun.sessionName === String(sessionName || '').trim()
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
544
760
|
function isActiveSessionHistoryOnly() {
|
|
545
761
|
const session = getActiveSession();
|
|
546
762
|
return sessionStatusInfo(session && session.status).tone === 'history';
|
|
@@ -1187,11 +1403,15 @@
|
|
|
1187
1403
|
scheduleTerminalFit(false);
|
|
1188
1404
|
}
|
|
1189
1405
|
|
|
1406
|
+
const activeAgentRunning = isAgentRunActiveForSession(state.active);
|
|
1190
1407
|
const busy = state.loadingSessions || state.loadingMessages || state.sending;
|
|
1191
1408
|
refreshBtn.disabled = busy;
|
|
1192
1409
|
removeBtn.disabled = !state.active || busy;
|
|
1193
1410
|
removeAllBtn.disabled = !state.active || busy;
|
|
1194
1411
|
sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
|
|
1412
|
+
if (stopBtn) {
|
|
1413
|
+
stopBtn.disabled = !activityTab || !agentMode || !activeAgentRunning || state.agentRun.stopping;
|
|
1414
|
+
}
|
|
1195
1415
|
commandInput.disabled = !activityTab || !state.active || (agentMode && !agentEnabled);
|
|
1196
1416
|
if (commandInput) {
|
|
1197
1417
|
commandInput.placeholder = agentMode
|
|
@@ -1200,7 +1420,7 @@
|
|
|
1200
1420
|
}
|
|
1201
1421
|
if (composerHint) {
|
|
1202
1422
|
composerHint.textContent = agentMode
|
|
1203
|
-
? 'Enter 发送提示词 · Shift/Alt + Enter 换行'
|
|
1423
|
+
? 'Enter 发送提示词 · Shift/Alt + Enter 换行 · 执行中可停止'
|
|
1204
1424
|
: 'Enter 发送 · Shift/Alt + Enter 换行';
|
|
1205
1425
|
}
|
|
1206
1426
|
if (openCreateBtn) {
|
|
@@ -1227,6 +1447,21 @@
|
|
|
1227
1447
|
if (createCancelBtn) {
|
|
1228
1448
|
createCancelBtn.disabled = state.createSubmitting;
|
|
1229
1449
|
}
|
|
1450
|
+
if (sendState) {
|
|
1451
|
+
if (!state.active) {
|
|
1452
|
+
sendState.textContent = '未选择会话';
|
|
1453
|
+
sendState.classList.remove('is-active');
|
|
1454
|
+
} else if (activeAgentRunning && agentMode) {
|
|
1455
|
+
sendState.textContent = state.agentRun.stopping ? '正在停止 Agent…' : 'Agent 执行中';
|
|
1456
|
+
sendState.classList.add('is-active');
|
|
1457
|
+
} else if (busy) {
|
|
1458
|
+
sendState.textContent = '处理中';
|
|
1459
|
+
sendState.classList.add('is-active');
|
|
1460
|
+
} else {
|
|
1461
|
+
sendState.textContent = agentMode ? 'Agent 就绪' : '命令就绪';
|
|
1462
|
+
sendState.classList.remove('is-active');
|
|
1463
|
+
}
|
|
1464
|
+
}
|
|
1230
1465
|
if (configModal) {
|
|
1231
1466
|
configModal.hidden = !state.configModalOpen;
|
|
1232
1467
|
}
|
|
@@ -1276,6 +1511,80 @@
|
|
|
1276
1511
|
return data;
|
|
1277
1512
|
}
|
|
1278
1513
|
|
|
1514
|
+
async function apiStream(url, options, handlers) {
|
|
1515
|
+
const requestOptions = Object.assign(
|
|
1516
|
+
{ headers: { 'Content-Type': 'application/json', 'X-Requested-With': 'XMLHttpRequest' } },
|
|
1517
|
+
options || {}
|
|
1518
|
+
);
|
|
1519
|
+
const streamHandlers = handlers && typeof handlers === 'object' ? handlers : {};
|
|
1520
|
+
const response = await fetch(url, requestOptions);
|
|
1521
|
+
if (response.status === 401) {
|
|
1522
|
+
window.location.href = '/';
|
|
1523
|
+
throw new Error('未登录或登录已过期');
|
|
1524
|
+
}
|
|
1525
|
+
if (!response.ok) {
|
|
1526
|
+
let errorText = '请求失败';
|
|
1527
|
+
try {
|
|
1528
|
+
const data = await response.json();
|
|
1529
|
+
errorText = data && data.detail ? `${data.error || '请求失败'}: ${data.detail}` : (data.error || '请求失败');
|
|
1530
|
+
} catch (e) {
|
|
1531
|
+
errorText = '请求失败';
|
|
1532
|
+
}
|
|
1533
|
+
throw new Error(errorText);
|
|
1534
|
+
}
|
|
1535
|
+
if (!response.body || typeof response.body.getReader !== 'function') {
|
|
1536
|
+
throw new Error('当前浏览器不支持流式读取');
|
|
1537
|
+
}
|
|
1538
|
+
|
|
1539
|
+
const decoder = new window.TextDecoder();
|
|
1540
|
+
const reader = response.body.getReader();
|
|
1541
|
+
let pending = '';
|
|
1542
|
+
|
|
1543
|
+
while (true) {
|
|
1544
|
+
const result = await reader.read();
|
|
1545
|
+
if (result.done) {
|
|
1546
|
+
break;
|
|
1547
|
+
}
|
|
1548
|
+
pending += decoder.decode(result.value, { stream: true });
|
|
1549
|
+
const lines = pending.split('\n');
|
|
1550
|
+
pending = lines.pop() || '';
|
|
1551
|
+
lines.forEach(function (line) {
|
|
1552
|
+
const text = String(line || '').trim();
|
|
1553
|
+
if (!text) {
|
|
1554
|
+
return;
|
|
1555
|
+
}
|
|
1556
|
+
let payload = null;
|
|
1557
|
+
try {
|
|
1558
|
+
payload = JSON.parse(text);
|
|
1559
|
+
} catch (e) {
|
|
1560
|
+
payload = null;
|
|
1561
|
+
}
|
|
1562
|
+
if (!payload) {
|
|
1563
|
+
return;
|
|
1564
|
+
}
|
|
1565
|
+
if (typeof streamHandlers.onEvent === 'function') {
|
|
1566
|
+
streamHandlers.onEvent(payload);
|
|
1567
|
+
}
|
|
1568
|
+
});
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
const rest = decoder.decode();
|
|
1572
|
+
if (rest) {
|
|
1573
|
+
pending += rest;
|
|
1574
|
+
}
|
|
1575
|
+
const finalText = String(pending || '').trim();
|
|
1576
|
+
if (finalText) {
|
|
1577
|
+
try {
|
|
1578
|
+
const payload = JSON.parse(finalText);
|
|
1579
|
+
if (typeof streamHandlers.onEvent === 'function') {
|
|
1580
|
+
streamHandlers.onEvent(payload);
|
|
1581
|
+
}
|
|
1582
|
+
} catch (e) {
|
|
1583
|
+
// ignore trailing non-json fragments
|
|
1584
|
+
}
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
|
|
1279
1588
|
async function fetchConfigSnapshot() {
|
|
1280
1589
|
const snapshot = await api('/api/config');
|
|
1281
1590
|
state.configSnapshot = snapshot;
|
|
@@ -1595,6 +1904,11 @@
|
|
|
1595
1904
|
}
|
|
1596
1905
|
|
|
1597
1906
|
function getMessageRenderKey(msg, index) {
|
|
1907
|
+
if (msg && msg.id && msg.streamTrace) {
|
|
1908
|
+
const content = msg.content ? String(msg.content) : '';
|
|
1909
|
+
const timestamp = msg.timestamp ? String(msg.timestamp) : '';
|
|
1910
|
+
return `id:${msg.id}|trace|${timestamp}|${content}`;
|
|
1911
|
+
}
|
|
1598
1912
|
if (msg && msg.id) {
|
|
1599
1913
|
return `id:${msg.id}`;
|
|
1600
1914
|
}
|
|
@@ -1641,8 +1955,16 @@
|
|
|
1641
1955
|
const bubble = document.createElement('div');
|
|
1642
1956
|
bubble.className = 'bubble';
|
|
1643
1957
|
|
|
1644
|
-
const
|
|
1645
|
-
|
|
1958
|
+
const shouldRenderStructuredTrace = Boolean(
|
|
1959
|
+
msg
|
|
1960
|
+
&& msg.streamTrace
|
|
1961
|
+
&& Array.isArray(msg.traceEvents)
|
|
1962
|
+
&& msg.traceEvents.length
|
|
1963
|
+
);
|
|
1964
|
+
const shouldRenderMarkdown = Boolean(!msg.streamTrace && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
|
|
1965
|
+
if (shouldRenderStructuredTrace) {
|
|
1966
|
+
appendStructuredTraceContent(bubble, msg);
|
|
1967
|
+
} else if (shouldRenderMarkdown) {
|
|
1646
1968
|
const markdownNode = document.createElement('div');
|
|
1647
1969
|
markdownNode.className = 'md-content';
|
|
1648
1970
|
let renderedMarkdown = '';
|
|
@@ -1941,6 +2263,53 @@
|
|
|
1941
2263
|
return -1;
|
|
1942
2264
|
}
|
|
1943
2265
|
|
|
2266
|
+
function appendAgentTraceMessageLocal(sessionName) {
|
|
2267
|
+
const traceMessage = {
|
|
2268
|
+
id: createLocalMessageId('local-agent-trace'),
|
|
2269
|
+
role: 'assistant',
|
|
2270
|
+
content: '[执行过程]\n等待 Agent 启动…',
|
|
2271
|
+
timestamp: new Date().toISOString(),
|
|
2272
|
+
mode: 'agent',
|
|
2273
|
+
streamTrace: true,
|
|
2274
|
+
traceEvents: []
|
|
2275
|
+
};
|
|
2276
|
+
if (state.active === sessionName) {
|
|
2277
|
+
state.messages.push(traceMessage);
|
|
2278
|
+
}
|
|
2279
|
+
return traceMessage.id;
|
|
2280
|
+
}
|
|
2281
|
+
|
|
2282
|
+
function updateAgentTraceMessageLocal(sessionName, traceMessageId, content, traceEvent) {
|
|
2283
|
+
if (state.active !== sessionName) {
|
|
2284
|
+
return;
|
|
2285
|
+
}
|
|
2286
|
+
for (let i = state.messages.length - 1; i >= 0; i -= 1) {
|
|
2287
|
+
const message = state.messages[i];
|
|
2288
|
+
if (!message || String(message.id || '') !== String(traceMessageId || '')) {
|
|
2289
|
+
continue;
|
|
2290
|
+
}
|
|
2291
|
+
message.content = String(content || '');
|
|
2292
|
+
message.timestamp = new Date().toISOString();
|
|
2293
|
+
if (traceEvent && typeof traceEvent === 'object') {
|
|
2294
|
+
if (!Array.isArray(message.traceEvents)) {
|
|
2295
|
+
message.traceEvents = [];
|
|
2296
|
+
}
|
|
2297
|
+
message.traceEvents.push(traceEvent);
|
|
2298
|
+
}
|
|
2299
|
+
return;
|
|
2300
|
+
}
|
|
2301
|
+
}
|
|
2302
|
+
|
|
2303
|
+
function finalizeAgentRunState() {
|
|
2304
|
+
if (state.agentRun && state.agentRun.controller) {
|
|
2305
|
+
state.agentRun.controller = null;
|
|
2306
|
+
}
|
|
2307
|
+
state.agentRun.active = false;
|
|
2308
|
+
state.agentRun.stopping = false;
|
|
2309
|
+
state.agentRun.sessionName = '';
|
|
2310
|
+
state.agentRun.traceMessageId = '';
|
|
2311
|
+
}
|
|
2312
|
+
|
|
1944
2313
|
function appendAssistantMessageLocal(sessionName, result, mode) {
|
|
1945
2314
|
if (state.active !== sessionName) {
|
|
1946
2315
|
return;
|
|
@@ -1957,6 +2326,109 @@
|
|
|
1957
2326
|
});
|
|
1958
2327
|
}
|
|
1959
2328
|
|
|
2329
|
+
async function sendAgentPromptStream(sessionName, inputText, pendingMessage) {
|
|
2330
|
+
const traceMessageId = appendAgentTraceMessageLocal(sessionName);
|
|
2331
|
+
const traceLines = ['[执行过程]', '等待 Agent 启动…'];
|
|
2332
|
+
let finalResult = null;
|
|
2333
|
+
let streamError = null;
|
|
2334
|
+
|
|
2335
|
+
state.agentRun.active = true;
|
|
2336
|
+
state.agentRun.sessionName = sessionName;
|
|
2337
|
+
state.agentRun.stopping = false;
|
|
2338
|
+
state.agentRun.controller = new window.AbortController();
|
|
2339
|
+
state.agentRun.traceMessageId = traceMessageId;
|
|
2340
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
2341
|
+
syncUi();
|
|
2342
|
+
|
|
2343
|
+
function pushTraceLine(text, traceEvent) {
|
|
2344
|
+
const line = String(text || '').trim();
|
|
2345
|
+
if (!line) {
|
|
2346
|
+
return;
|
|
2347
|
+
}
|
|
2348
|
+
if (traceLines[traceLines.length - 1] === line) {
|
|
2349
|
+
return;
|
|
2350
|
+
}
|
|
2351
|
+
traceLines.push(line);
|
|
2352
|
+
updateAgentTraceMessageLocal(sessionName, traceMessageId, traceLines.join('\n'), traceEvent);
|
|
2353
|
+
if (state.active === sessionName) {
|
|
2354
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
2355
|
+
}
|
|
2356
|
+
}
|
|
2357
|
+
|
|
2358
|
+
try {
|
|
2359
|
+
await apiStream('/api/sessions/' + encodeURIComponent(sessionName) + '/agent/stream', {
|
|
2360
|
+
method: 'POST',
|
|
2361
|
+
body: JSON.stringify({ prompt: inputText }),
|
|
2362
|
+
signal: state.agentRun.controller.signal
|
|
2363
|
+
}, {
|
|
2364
|
+
onEvent: function (event) {
|
|
2365
|
+
if (!event || typeof event !== 'object') {
|
|
2366
|
+
return;
|
|
2367
|
+
}
|
|
2368
|
+
if (event.type === 'meta') {
|
|
2369
|
+
const contextMode = String(event.contextMode || '').trim();
|
|
2370
|
+
const modeLabel = contextMode ? '上下文模式: ' + contextMode : '';
|
|
2371
|
+
if (modeLabel) {
|
|
2372
|
+
pushTraceLine(modeLabel);
|
|
2373
|
+
}
|
|
2374
|
+
if (event.resumeAttempted) {
|
|
2375
|
+
pushTraceLine(event.resumeSucceeded ? '会话恢复成功' : '会话恢复失败,已回退到历史注入');
|
|
2376
|
+
}
|
|
2377
|
+
return;
|
|
2378
|
+
}
|
|
2379
|
+
if (event.type === 'trace') {
|
|
2380
|
+
pushTraceLine(event.text || '', event.traceEvent || null);
|
|
2381
|
+
return;
|
|
2382
|
+
}
|
|
2383
|
+
if (event.type === 'result') {
|
|
2384
|
+
finalResult = event;
|
|
2385
|
+
if (event.interrupted) {
|
|
2386
|
+
pushTraceLine('[任务] 已停止');
|
|
2387
|
+
} else {
|
|
2388
|
+
pushTraceLine('[任务] 已完成');
|
|
2389
|
+
}
|
|
2390
|
+
return;
|
|
2391
|
+
}
|
|
2392
|
+
if (event.type === 'error') {
|
|
2393
|
+
streamError = new Error(event.error || 'Agent 执行失败');
|
|
2394
|
+
}
|
|
2395
|
+
}
|
|
2396
|
+
});
|
|
2397
|
+
} catch (e) {
|
|
2398
|
+
if (!(e && e.name === 'AbortError')) {
|
|
2399
|
+
streamError = e;
|
|
2400
|
+
}
|
|
2401
|
+
} finally {
|
|
2402
|
+
const pendingIndex = confirmPendingUserMessage(sessionName, pendingMessage.id);
|
|
2403
|
+
if (pendingIndex >= 0 && pendingIndex < state.messageRenderKeys.length) {
|
|
2404
|
+
if (pendingIndex < messagesNode.children.length) {
|
|
2405
|
+
const pendingRow = messagesNode.children[pendingIndex];
|
|
2406
|
+
if (pendingRow && pendingRow.classList.contains('pending')) {
|
|
2407
|
+
pendingRow.classList.remove('pending');
|
|
2408
|
+
}
|
|
2409
|
+
}
|
|
2410
|
+
}
|
|
2411
|
+
finalizeAgentRunState();
|
|
2412
|
+
syncUi();
|
|
2413
|
+
}
|
|
2414
|
+
|
|
2415
|
+
if (streamError) {
|
|
2416
|
+
throw streamError;
|
|
2417
|
+
}
|
|
2418
|
+
if (!finalResult) {
|
|
2419
|
+
throw new Error('Agent 流式响应未返回结果');
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
appendAssistantMessageLocal(sessionName, finalResult, 'agent');
|
|
2423
|
+
if (state.active === sessionName) {
|
|
2424
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
2425
|
+
}
|
|
2426
|
+
bumpSessionMetaAfterSend(sessionName);
|
|
2427
|
+
refreshSessionsSilent({ preferredName: sessionName }).catch(function () {
|
|
2428
|
+
// 静默同步失败不打断当前交互
|
|
2429
|
+
});
|
|
2430
|
+
}
|
|
2431
|
+
|
|
1960
2432
|
if (openConfigBtn) {
|
|
1961
2433
|
openConfigBtn.addEventListener('click', function () {
|
|
1962
2434
|
openConfigModal();
|
|
@@ -2092,32 +2564,31 @@
|
|
|
2092
2564
|
try {
|
|
2093
2565
|
commandInput.value = '';
|
|
2094
2566
|
commandInput.focus();
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
)
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
pendingRow.classList.remove('pending');
|
|
2567
|
+
if (mode === 'agent') {
|
|
2568
|
+
await sendAgentPromptStream(submitSession, inputText, pendingMessage);
|
|
2569
|
+
} else {
|
|
2570
|
+
const runResult = await api('/api/sessions/' + encodeURIComponent(submitSession) + '/run', {
|
|
2571
|
+
method: 'POST',
|
|
2572
|
+
body: JSON.stringify({ command: inputText })
|
|
2573
|
+
});
|
|
2574
|
+
const pendingIndex = confirmPendingUserMessage(submitSession, pendingMessage.id);
|
|
2575
|
+
if (pendingIndex >= 0 && pendingIndex < state.messageRenderKeys.length) {
|
|
2576
|
+
if (pendingIndex < messagesNode.children.length) {
|
|
2577
|
+
const pendingRow = messagesNode.children[pendingIndex];
|
|
2578
|
+
if (pendingRow && pendingRow.classList.contains('pending')) {
|
|
2579
|
+
pendingRow.classList.remove('pending');
|
|
2580
|
+
}
|
|
2110
2581
|
}
|
|
2111
2582
|
}
|
|
2583
|
+
appendAssistantMessageLocal(submitSession, runResult, mode);
|
|
2584
|
+
if (state.active === submitSession) {
|
|
2585
|
+
renderMessages(state.messages, { stickToBottom: true });
|
|
2586
|
+
}
|
|
2587
|
+
bumpSessionMetaAfterSend(submitSession);
|
|
2588
|
+
refreshSessionsSilent({ preferredName: submitSession }).catch(function () {
|
|
2589
|
+
// 静默同步失败不打断当前交互
|
|
2590
|
+
});
|
|
2112
2591
|
}
|
|
2113
|
-
appendAssistantMessageLocal(submitSession, runResult, mode);
|
|
2114
|
-
if (state.active === submitSession) {
|
|
2115
|
-
renderMessages(state.messages, { stickToBottom: true });
|
|
2116
|
-
}
|
|
2117
|
-
bumpSessionMetaAfterSend(submitSession);
|
|
2118
|
-
refreshSessionsSilent({ preferredName: submitSession }).catch(function () {
|
|
2119
|
-
// 静默同步失败不打断当前交互
|
|
2120
|
-
});
|
|
2121
2592
|
} catch (e) {
|
|
2122
2593
|
if (state.active === submitSession) {
|
|
2123
2594
|
state.messages = state.messages.filter(function (message) {
|
|
@@ -2133,6 +2604,26 @@
|
|
|
2133
2604
|
}
|
|
2134
2605
|
});
|
|
2135
2606
|
|
|
2607
|
+
if (stopBtn) {
|
|
2608
|
+
stopBtn.addEventListener('click', async function () {
|
|
2609
|
+
if (!state.active || !isAgentRunActiveForSession(state.active) || state.agentRun.stopping) {
|
|
2610
|
+
return;
|
|
2611
|
+
}
|
|
2612
|
+
state.agentRun.stopping = true;
|
|
2613
|
+
syncUi();
|
|
2614
|
+
try {
|
|
2615
|
+
await api('/api/sessions/' + encodeURIComponent(state.active) + '/agent/stop', {
|
|
2616
|
+
method: 'POST',
|
|
2617
|
+
body: JSON.stringify({})
|
|
2618
|
+
});
|
|
2619
|
+
} catch (e) {
|
|
2620
|
+
alert(e.message);
|
|
2621
|
+
state.agentRun.stopping = false;
|
|
2622
|
+
syncUi();
|
|
2623
|
+
}
|
|
2624
|
+
});
|
|
2625
|
+
}
|
|
2626
|
+
|
|
2136
2627
|
commandInput.addEventListener('keydown', function (event) {
|
|
2137
2628
|
if (event.key !== 'Enter' || event.isComposing) {
|
|
2138
2629
|
return;
|