@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.
@@ -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
- return AGENT_PROMPT_TEMPLATE_MAP[program] || '';
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 shouldRenderMarkdown = Boolean(markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
1645
- if (shouldRenderMarkdown) {
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
- const endpoint = mode === 'agent' ? '/agent' : '/run';
2096
- const runResult = await api('/api/sessions/' + encodeURIComponent(submitSession) + endpoint, {
2097
- method: 'POST',
2098
- body: JSON.stringify(
2099
- mode === 'agent'
2100
- ? { prompt: inputText }
2101
- : { command: inputText }
2102
- )
2103
- });
2104
- const pendingIndex = confirmPendingUserMessage(submitSession, pendingMessage.id);
2105
- if (pendingIndex >= 0 && pendingIndex < state.messageRenderKeys.length) {
2106
- if (pendingIndex < messagesNode.children.length) {
2107
- const pendingRow = messagesNode.children[pendingIndex];
2108
- if (pendingRow && pendingRow.classList.contains('pending')) {
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;