@xcanwin/manyoyo 5.9.0 → 5.9.2

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.
@@ -1667,22 +1667,6 @@ body.command-mode .msg.origin-agent .bubble {
1667
1667
  gap: 10px;
1668
1668
  }
1669
1669
 
1670
- .trace-summary {
1671
- display: flex;
1672
- flex-direction: column;
1673
- gap: 6px;
1674
- }
1675
-
1676
- .trace-summary-line {
1677
- padding: 7px 10px;
1678
- border-radius: 8px;
1679
- border: 1px dashed rgba(181, 146, 99, 0.45);
1680
- background: rgba(255, 250, 242, 0.82);
1681
- color: var(--muted);
1682
- font-size: 12px;
1683
- line-height: 1.45;
1684
- }
1685
-
1686
1670
  .trace-flow {
1687
1671
  display: flex;
1688
1672
  flex-direction: column;
@@ -1722,6 +1706,11 @@ body.command-mode .msg.origin-agent .bubble {
1722
1706
  border-left-color: var(--line-strong);
1723
1707
  }
1724
1708
 
1709
+ .trace-card.trace-card-residual {
1710
+ background: rgba(255, 250, 242, 0.86);
1711
+ box-shadow: none;
1712
+ }
1713
+
1725
1714
  .trace-card-summary {
1726
1715
  list-style: none;
1727
1716
  display: flex;
@@ -1735,36 +1724,6 @@ details.trace-card > .trace-card-summary {
1735
1724
  cursor: pointer;
1736
1725
  }
1737
1726
 
1738
- .trace-card.trace-card-compact {
1739
- border-left-width: 2px;
1740
- border-radius: 999px;
1741
- background: rgba(255, 251, 245, 0.78);
1742
- box-shadow: none;
1743
- }
1744
-
1745
- .trace-card.trace-card-compact .trace-card-summary {
1746
- padding: 5px 10px;
1747
- gap: 7px;
1748
- }
1749
-
1750
- .trace-card.trace-card-compact .trace-card-badge {
1751
- min-width: 0;
1752
- padding: 2px 6px;
1753
- background: rgba(31, 26, 20, 0.06);
1754
- font-size: 10px;
1755
- }
1756
-
1757
- .trace-card.trace-card-compact .trace-card-title {
1758
- font-size: 11px;
1759
- font-weight: 500;
1760
- color: #6e6256;
1761
- }
1762
-
1763
- .trace-card.trace-card-compact .trace-card-phase {
1764
- font-size: 10px;
1765
- color: #8a7d70;
1766
- }
1767
-
1768
1727
  .trace-card-summary::-webkit-details-marker {
1769
1728
  display: none;
1770
1729
  }
@@ -92,6 +92,10 @@
92
92
  controller: null,
93
93
  traceMessageId: ''
94
94
  },
95
+ agentRecovery: {
96
+ sessionName: '',
97
+ timer: null
98
+ },
95
99
  terminal: {
96
100
  term: null,
97
101
  fitAddon: null,
@@ -381,34 +385,6 @@
381
385
  return 'neutral';
382
386
  }
383
387
 
384
- function shouldCompactTraceEvent(traceEvent) {
385
- const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
386
- const phase = event.phase ? String(event.phase) : '';
387
- const kind = event.kind ? String(event.kind) : '';
388
- if (phase !== 'completed') {
389
- return false;
390
- }
391
- return kind === 'command' || kind === 'mcp' || kind === 'tool';
392
- }
393
-
394
- function buildCompactTraceText(traceEvent) {
395
- const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
396
- if (event.kind === 'command') {
397
- const suffix = typeof event.exitCode === 'number'
398
- ? `exit ${event.exitCode}`
399
- : (event.status ? String(event.status) : 'completed');
400
- return `${event.command || event.text || '命令'} · ${suffix}`;
401
- }
402
- if (event.kind === 'mcp') {
403
- const toolLabel = [event.server, event.tool].filter(Boolean).join('.') || event.text || 'MCP';
404
- return event.argumentSummary ? `${toolLabel} · ${event.argumentSummary}` : toolLabel;
405
- }
406
- if (event.kind === 'tool') {
407
- return event.toolName || event.text || '工具';
408
- }
409
- return event.text || '事件';
410
- }
411
-
412
388
  function appendTraceCardBody(cardBody, label, value) {
413
389
  const text = stringifyPrettyJson(value).trim();
414
390
  if (!text) {
@@ -432,12 +408,19 @@
432
408
 
433
409
  function createTraceEventCard(traceEvent) {
434
410
  const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
435
- const compact = shouldCompactTraceEvent(event);
436
411
  const bodyParts = [];
437
- if (!compact && event.kind === 'command' && event.command) {
412
+ if (event.kind === 'command' && event.command) {
438
413
  bodyParts.push({ label: '命令', value: event.command });
414
+ if (typeof event.exitCode === 'number') {
415
+ bodyParts.push({ label: '退出码', value: String(event.exitCode) });
416
+ } else if (event.status) {
417
+ bodyParts.push({ label: '状态', value: event.status });
418
+ }
439
419
  }
440
- if (!compact && event.kind === 'mcp') {
420
+ if (event.kind === 'mcp') {
421
+ if (event.server || event.tool) {
422
+ bodyParts.push({ label: '工具', value: [event.server, event.tool].filter(Boolean).join('.') });
423
+ }
441
424
  if (event.argumentSummary) {
442
425
  bodyParts.push({ label: '参数摘要', value: event.argumentSummary });
443
426
  }
@@ -451,7 +434,7 @@
451
434
  bodyParts.push({ label: '错误', value: event.error });
452
435
  }
453
436
  }
454
- if (!compact && event.kind === 'tool' && event.toolName) {
437
+ if (event.kind === 'tool' && event.toolName) {
455
438
  bodyParts.push({ label: '工具', value: event.toolName });
456
439
  }
457
440
  if ((event.kind === 'agent_message' || event.kind === 'status' || event.kind === 'error') && event.detail) {
@@ -460,7 +443,7 @@
460
443
 
461
444
  const hasBody = bodyParts.length > 0;
462
445
  const card = document.createElement(hasBody ? 'details' : 'div');
463
- card.className = 'trace-card trace-tone-' + resolveTraceTone(event) + (compact ? ' trace-card-compact' : '');
446
+ card.className = 'trace-card trace-tone-' + resolveTraceTone(event);
464
447
  if (hasBody && event.kind === 'error') {
465
448
  card.open = true;
466
449
  }
@@ -475,7 +458,7 @@
475
458
 
476
459
  const title = document.createElement('span');
477
460
  title.className = 'trace-card-title';
478
- title.textContent = compact ? buildCompactTraceText(event) : (event && event.text ? String(event.text) : '事件');
461
+ title.textContent = event && event.text ? String(event.text) : '事件';
479
462
  header.appendChild(title);
480
463
 
481
464
  const phaseText = humanizeTracePhase(event);
@@ -494,9 +477,44 @@
494
477
  bodyParts.forEach(function (part) {
495
478
  appendTraceCardBody(body, part.label, part.value);
496
479
  });
497
- card.appendChild(body);
480
+ card.appendChild(body);
481
+ }
482
+
483
+ return card;
484
+ }
485
+
486
+ function resolveResidualTraceTone(line) {
487
+ const text = String(line || '').trim();
488
+ if (!text) {
489
+ return 'neutral';
490
+ }
491
+ if (text.startsWith('[stderr]') || text.startsWith('[错误]')) {
492
+ return 'error';
498
493
  }
494
+ if (text.startsWith('[任务]') || text.includes('上下文模式') || text.includes('会话恢复') || text.includes('等待 Agent 启动')) {
495
+ return 'status';
496
+ }
497
+ return 'neutral';
498
+ }
499
+
500
+ function createResidualTraceCard(line) {
501
+ const card = document.createElement('div');
502
+ card.className = 'trace-card trace-tone-' + resolveResidualTraceTone(line) + ' trace-card-residual';
503
+
504
+ const header = document.createElement('div');
505
+ header.className = 'trace-card-summary';
506
+
507
+ const badge = document.createElement('span');
508
+ badge.className = 'trace-card-badge';
509
+ badge.textContent = '状态';
510
+ header.appendChild(badge);
511
+
512
+ const title = document.createElement('span');
513
+ title.className = 'trace-card-title';
514
+ title.textContent = String(line || '');
515
+ header.appendChild(title);
499
516
 
517
+ card.appendChild(header);
500
518
  return card;
501
519
  }
502
520
 
@@ -505,21 +523,11 @@
505
523
  const container = document.createElement('div');
506
524
  container.className = 'trace-structured';
507
525
 
508
- const residualLines = buildStructuredTraceResidualLines(message);
509
- if (residualLines.length) {
510
- const summary = document.createElement('div');
511
- summary.className = 'trace-summary';
512
- residualLines.forEach(function (line) {
513
- const item = document.createElement('div');
514
- item.className = 'trace-summary-line';
515
- item.textContent = line;
516
- summary.appendChild(item);
517
- });
518
- container.appendChild(summary);
519
- }
520
-
521
526
  const flow = document.createElement('div');
522
527
  flow.className = 'trace-flow';
528
+ buildStructuredTraceResidualLines(message).forEach(function (line) {
529
+ flow.appendChild(createResidualTraceCard(line));
530
+ });
523
531
  (Array.isArray(message && message.traceEvents) ? message.traceEvents : []).forEach(function (traceEvent) {
524
532
  flow.appendChild(createTraceEventCard(traceEvent));
525
533
  });
@@ -1334,6 +1342,114 @@
1334
1342
  );
1335
1343
  }
1336
1344
 
1345
+ function clearAgentRecoveryPoll() {
1346
+ if (state.agentRecovery && state.agentRecovery.timer) {
1347
+ window.clearTimeout(state.agentRecovery.timer);
1348
+ state.agentRecovery.timer = null;
1349
+ }
1350
+ if (state.agentRecovery) {
1351
+ state.agentRecovery.sessionName = '';
1352
+ }
1353
+ }
1354
+
1355
+ function hasPendingAgentMessagesForSession(sessionName) {
1356
+ const targetSession = String(sessionName || '').trim();
1357
+ if (!targetSession || state.active !== targetSession) {
1358
+ return false;
1359
+ }
1360
+ return state.messages.some(function (message) {
1361
+ return Boolean(
1362
+ message
1363
+ && message.mode === 'agent'
1364
+ && (message.pending === true || message.streamingReply === true)
1365
+ );
1366
+ });
1367
+ }
1368
+
1369
+ function scheduleAgentRecoveryPoll(sessionName, immediate) {
1370
+ const targetSession = String(sessionName || '').trim();
1371
+ if (!targetSession || state.active !== targetSession) {
1372
+ clearAgentRecoveryPoll();
1373
+ return;
1374
+ }
1375
+ if (isAgentRunActiveForSession(targetSession)) {
1376
+ clearAgentRecoveryPoll();
1377
+ return;
1378
+ }
1379
+ if (!hasPendingAgentMessagesForSession(targetSession)) {
1380
+ clearAgentRecoveryPoll();
1381
+ return;
1382
+ }
1383
+ if (state.agentRecovery.sessionName !== targetSession) {
1384
+ clearAgentRecoveryPoll();
1385
+ state.agentRecovery.sessionName = targetSession;
1386
+ }
1387
+ if (state.agentRecovery.timer) {
1388
+ return;
1389
+ }
1390
+
1391
+ const delay = immediate === true ? 0 : 1500;
1392
+ state.agentRecovery.timer = window.setTimeout(async function () {
1393
+ state.agentRecovery.timer = null;
1394
+ if (state.active !== targetSession || isAgentRunActiveForSession(targetSession)) {
1395
+ clearAgentRecoveryPoll();
1396
+ syncUi();
1397
+ return;
1398
+ }
1399
+ try {
1400
+ await refreshSessionsSilent({ preferredName: targetSession });
1401
+ await Promise.all([
1402
+ loadMessagesForSession(targetSession, { silent: true }),
1403
+ loadSessionDetailForSession(targetSession)
1404
+ ]);
1405
+ } catch (e) {
1406
+ // 静默失败,等待下一轮轮询
1407
+ }
1408
+ if (state.active === targetSession && !isAgentRunActiveForSession(targetSession) && hasPendingAgentMessagesForSession(targetSession)) {
1409
+ scheduleAgentRecoveryPoll(targetSession, false);
1410
+ } else {
1411
+ clearAgentRecoveryPoll();
1412
+ }
1413
+ syncUi();
1414
+ }, delay);
1415
+ }
1416
+
1417
+ function syncAgentRecoveryForSession(sessionName) {
1418
+ const targetSession = String(sessionName || '').trim();
1419
+ if (!targetSession || state.active !== targetSession) {
1420
+ clearAgentRecoveryPoll();
1421
+ return;
1422
+ }
1423
+ if (isAgentRunActiveForSession(targetSession)) {
1424
+ clearAgentRecoveryPoll();
1425
+ return;
1426
+ }
1427
+ if (hasPendingAgentMessagesForSession(targetSession)) {
1428
+ scheduleAgentRecoveryPoll(targetSession, false);
1429
+ return;
1430
+ }
1431
+ if (state.agentRecovery.sessionName === targetSession) {
1432
+ clearAgentRecoveryPoll();
1433
+ }
1434
+ }
1435
+
1436
+ async function recoverAgentRunFromServer(sessionName) {
1437
+ const targetSession = String(sessionName || '').trim();
1438
+ if (!targetSession) {
1439
+ return false;
1440
+ }
1441
+ try {
1442
+ await refreshSessionsSilent({ preferredName: targetSession });
1443
+ await Promise.all([
1444
+ loadMessagesForSession(targetSession, { silent: true }),
1445
+ loadSessionDetailForSession(targetSession)
1446
+ ]);
1447
+ } catch (e) {
1448
+ return false;
1449
+ }
1450
+ return hasPendingAgentMessagesForSession(targetSession);
1451
+ }
1452
+
1337
1453
  function isActiveSessionHistoryOnly() {
1338
1454
  const session = getActiveSession();
1339
1455
  return sessionStatusInfo(session && session.status).tone === 'history';
@@ -2076,7 +2192,7 @@
2076
2192
  });
2077
2193
  }
2078
2194
 
2079
- const activeAgentRunning = isAgentRunActiveForSession(state.active);
2195
+ const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
2080
2196
  const busy = state.loadingSessions || state.loadingMessages || state.sending;
2081
2197
  refreshBtn.disabled = busy;
2082
2198
  if (addAgentBtn) {
@@ -2084,14 +2200,14 @@
2084
2200
  }
2085
2201
  removeBtn.disabled = !state.active || busy;
2086
2202
  removeAllBtn.disabled = !state.active || busy;
2087
- sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
2203
+ sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && (!agentEnabled || activeAgentRunning));
2088
2204
  if (stopBtn) {
2089
2205
  stopBtn.disabled = !activityTab || !agentMode || !activeAgentRunning || state.agentRun.stopping;
2090
2206
  }
2091
2207
  if (agentTemplateBtn) {
2092
2208
  agentTemplateBtn.disabled = !state.active || state.agentTemplateSaving;
2093
2209
  }
2094
- commandInput.disabled = !activityTab || !state.active || (agentMode && !agentEnabled);
2210
+ commandInput.disabled = !activityTab || !state.active || (agentMode && (!agentEnabled || activeAgentRunning));
2095
2211
  if (commandInput) {
2096
2212
  commandInput.placeholder = agentMode
2097
2213
  ? '输入提示词,例如:请帮我分析当前项目结构并给出重构建议'
@@ -3282,6 +3398,7 @@
3282
3398
  state.messageRequestId += 1;
3283
3399
  state.messages = [];
3284
3400
  state.loadingMessages = false;
3401
+ clearAgentRecoveryPoll();
3285
3402
  renderMessages(state.messages);
3286
3403
  syncUi();
3287
3404
  return;
@@ -3314,6 +3431,9 @@
3314
3431
  state.messages = Array.isArray(data && data.messages) ? data.messages : [];
3315
3432
  }
3316
3433
  if (!requestError || targetSession === state.active) {
3434
+ if (targetSession === state.active) {
3435
+ syncAgentRecoveryForSession(targetSession);
3436
+ }
3317
3437
  renderMessages(state.messages);
3318
3438
  syncUi();
3319
3439
  } else {
@@ -3451,6 +3571,7 @@
3451
3571
  state.agentRun.stopping = false;
3452
3572
  state.agentRun.sessionName = '';
3453
3573
  state.agentRun.traceMessageId = '';
3574
+ clearAgentRecoveryPoll();
3454
3575
  }
3455
3576
 
3456
3577
  function appendAssistantMessageLocal(sessionName, result, mode) {
@@ -3524,6 +3645,7 @@
3524
3645
  state.agentRun.stopping = false;
3525
3646
  state.agentRun.controller = new window.AbortController();
3526
3647
  state.agentRun.traceMessageId = traceMessageId;
3648
+ clearAgentRecoveryPoll();
3527
3649
  renderMessages(state.messages, { stickToBottom: true });
3528
3650
  syncUi();
3529
3651
 
@@ -3884,13 +4006,20 @@
3884
4006
  });
3885
4007
  }
3886
4008
  } catch (e) {
3887
- if (state.active === submitSession) {
3888
- state.messages = state.messages.filter(function (message) {
3889
- return !(message && message.id === pendingMessage.id);
3890
- });
3891
- renderMessages(state.messages, { stickToBottom: true });
4009
+ if (mode === 'agent') {
4010
+ const recovered = await recoverAgentRunFromServer(submitSession);
4011
+ if (!recovered && document.visibilityState !== 'hidden') {
4012
+ alert(e.message);
4013
+ }
4014
+ } else {
4015
+ if (state.active === submitSession) {
4016
+ state.messages = state.messages.filter(function (message) {
4017
+ return !(message && message.id === pendingMessage.id);
4018
+ });
4019
+ renderMessages(state.messages, { stickToBottom: true });
4020
+ }
4021
+ alert(e.message);
3892
4022
  }
3893
- alert(e.message);
3894
4023
  } finally {
3895
4024
  state.sending = false;
3896
4025
  syncUi();
@@ -4153,6 +4282,24 @@
4153
4282
  }
4154
4283
  });
4155
4284
 
4285
+ document.addEventListener('visibilitychange', function () {
4286
+ if (document.visibilityState !== 'visible' || !state.active) {
4287
+ return;
4288
+ }
4289
+ recoverAgentRunFromServer(state.active).catch(function () {
4290
+ // 静默恢复失败不打断当前交互
4291
+ });
4292
+ });
4293
+
4294
+ window.addEventListener('focus', function () {
4295
+ if (!state.active) {
4296
+ return;
4297
+ }
4298
+ recoverAgentRunFromServer(state.active).catch(function () {
4299
+ // 静默恢复失败不打断当前交互
4300
+ });
4301
+ });
4302
+
4156
4303
  if (typeof MOBILE_LAYOUT_MEDIA.addEventListener === 'function') {
4157
4304
  MOBILE_LAYOUT_MEDIA.addEventListener('change', onLayoutMediaChange);
4158
4305
  } else if (typeof MOBILE_LAYOUT_MEDIA.addListener === 'function') {
package/lib/web/server.js CHANGED
@@ -384,6 +384,10 @@ function listWebAgentSessions(history, options = {}) {
384
384
  });
385
385
  }
386
386
 
387
+ function createWebSessionMessageId() {
388
+ return `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
389
+ }
390
+
387
391
  function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role, content, extra = {}) {
388
392
  const sessionRef = typeof sessionRefOrContainerName === 'string'
389
393
  ? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
@@ -391,13 +395,14 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
391
395
  const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
392
396
  const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
393
397
  const timestamp = new Date().toISOString();
394
- agentSession.messages.push({
395
- id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`,
398
+ const message = {
399
+ id: createWebSessionMessageId(),
396
400
  role,
397
401
  content,
398
402
  timestamp,
399
403
  ...extra
400
- });
404
+ };
405
+ agentSession.messages.push(message);
401
406
 
402
407
  if (agentSession.messages.length > WEB_HISTORY_MAX_MESSAGES) {
403
408
  agentSession.messages = agentSession.messages.slice(-WEB_HISTORY_MAX_MESSAGES);
@@ -406,6 +411,57 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
406
411
  agentSession.updatedAt = timestamp;
407
412
  history.updatedAt = timestamp;
408
413
  saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
414
+ return message;
415
+ }
416
+
417
+ function patchWebSessionMessage(webHistoryDir, sessionRefOrContainerName, messageId, patch) {
418
+ const sessionRef = typeof sessionRefOrContainerName === 'string'
419
+ ? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
420
+ : sessionRefOrContainerName;
421
+ const targetId = String(messageId || '').trim();
422
+ if (!targetId) {
423
+ return null;
424
+ }
425
+ const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
426
+ const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
427
+ const nextPatch = patch && typeof patch === 'object' ? patch : {};
428
+ const message = agentSession.messages.find(item => item && String(item.id || '') === targetId);
429
+ if (!message) {
430
+ return null;
431
+ }
432
+ Object.keys(nextPatch).forEach(key => {
433
+ if (key === 'id') {
434
+ return;
435
+ }
436
+ message[key] = nextPatch[key];
437
+ });
438
+ const timestamp = new Date().toISOString();
439
+ agentSession.updatedAt = timestamp;
440
+ history.updatedAt = timestamp;
441
+ saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
442
+ return message;
443
+ }
444
+
445
+ function removeWebSessionMessage(webHistoryDir, sessionRefOrContainerName, messageId) {
446
+ const sessionRef = typeof sessionRefOrContainerName === 'string'
447
+ ? { containerName: sessionRefOrContainerName, agentId: WEB_DEFAULT_AGENT_ID }
448
+ : sessionRefOrContainerName;
449
+ const targetId = String(messageId || '').trim();
450
+ if (!targetId) {
451
+ return null;
452
+ }
453
+ const history = loadWebSessionHistory(webHistoryDir, sessionRef.containerName);
454
+ const agentSession = getWebAgentSession(history, sessionRef.agentId, { create: true });
455
+ const index = agentSession.messages.findIndex(item => item && String(item.id || '') === targetId);
456
+ if (index === -1) {
457
+ return null;
458
+ }
459
+ const removed = agentSession.messages.splice(index, 1)[0] || null;
460
+ const timestamp = new Date().toISOString();
461
+ agentSession.updatedAt = timestamp;
462
+ history.updatedAt = timestamp;
463
+ saveWebSessionHistory(webHistoryDir, sessionRef.containerName, history);
464
+ return removed;
409
465
  }
410
466
 
411
467
  function setWebSessionAgentPromptCommand(webHistoryDir, containerName, agentPromptCommand) {
@@ -816,6 +872,7 @@ function hasAgentConversationHistory(history) {
816
872
  for (const message of messages) {
817
873
  if (!message || typeof message !== 'object') continue;
818
874
  if (message.mode !== 'agent') continue;
875
+ if (message.pending === true) continue;
819
876
  if (message.streamTrace === true) continue;
820
877
  if (message.role === 'user' || message.role === 'assistant') {
821
878
  return true;
@@ -838,6 +895,7 @@ function buildAgentPromptWithHistory(history, prompt) {
838
895
  .filter(message => (
839
896
  message
840
897
  && message.mode === 'agent'
898
+ && message.pending !== true
841
899
  && message.streamTrace !== true
842
900
  && (message.role === 'user' || message.role === 'assistant')
843
901
  ))
@@ -1547,9 +1605,9 @@ function finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, m
1547
1605
  function appendWebAgentTraceMessage(webHistoryDir, sessionRefOrContainerName, content, extra = {}) {
1548
1606
  const text = String(content || '').trim();
1549
1607
  if (!text) {
1550
- return;
1608
+ return null;
1551
1609
  }
1552
- appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, 'assistant', text, {
1610
+ return appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, 'assistant', text, {
1553
1611
  mode: 'agent',
1554
1612
  streamTrace: true,
1555
1613
  ...extra
@@ -3827,10 +3885,26 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3827
3885
  return;
3828
3886
  }
3829
3887
 
3888
+ const userMessage = appendWebSessionMessage(state.webHistoryDir, sessionRef, 'user', prompt, {
3889
+ mode: 'agent',
3890
+ pending: true
3891
+ });
3892
+ const traceMessage = appendWebAgentTraceMessage(
3893
+ state.webHistoryDir,
3894
+ sessionRef,
3895
+ '[执行过程]\n等待 Agent 启动…',
3896
+ {
3897
+ traceEvents: [],
3898
+ pending: true
3899
+ }
3900
+ );
3901
+
3830
3902
  let prepared = null;
3831
3903
  try {
3832
3904
  prepared = await prepareWebAgentExecution(ctx, state, sessionRef, prompt);
3833
3905
  } catch (e) {
3906
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id);
3907
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id);
3834
3908
  sendJson(res, 400, { error: e && e.message ? e.message : 'Agent 执行准备失败' });
3835
3909
  return;
3836
3910
  }
@@ -3838,8 +3912,9 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3838
3912
  const { agentSession, agentMeta, command, contextMode, resumeAttempted, resumeSucceeded, resumeError } = prepared;
3839
3913
  const traceLines = ['[执行过程]'];
3840
3914
  const traceEvents = [];
3841
- appendWebSessionMessage(state.webHistoryDir, sessionRef, 'user', prompt, {
3842
- mode: 'agent',
3915
+ let streamingReplyMessageId = '';
3916
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
3917
+ pending: true,
3843
3918
  contextMode
3844
3919
  });
3845
3920
 
@@ -3863,6 +3938,14 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3863
3938
  if (resumeAttempted) {
3864
3939
  traceLines.push(resumeSucceeded ? '会话恢复成功' : '会话恢复失败,已回退到历史注入');
3865
3940
  }
3941
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
3942
+ content: traceLines.join('\n'),
3943
+ traceEvents: traceEvents.slice(),
3944
+ contextMode,
3945
+ resumeAttempted,
3946
+ resumeSucceeded,
3947
+ pending: true
3948
+ });
3866
3949
 
3867
3950
  try {
3868
3951
  const result = await execAgentInWebContainerStream(ctx, state, sessionRef, command, {
@@ -3873,18 +3956,57 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3873
3956
  if (event.traceEvent && typeof event.traceEvent === 'object') {
3874
3957
  traceEvents.push(event.traceEvent);
3875
3958
  }
3959
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
3960
+ content: traceLines.join('\n'),
3961
+ traceEvents: traceEvents.slice(),
3962
+ pending: true
3963
+ });
3964
+ }
3965
+ if (event && event.type === 'content_delta' && typeof event.content === 'string') {
3966
+ if (!streamingReplyMessageId) {
3967
+ const streamingReplyMessage = appendWebSessionMessage(
3968
+ state.webHistoryDir,
3969
+ sessionRef,
3970
+ 'assistant',
3971
+ event.content,
3972
+ {
3973
+ mode: 'agent',
3974
+ streamingReply: true,
3975
+ pending: true
3976
+ }
3977
+ );
3978
+ streamingReplyMessageId = streamingReplyMessage && streamingReplyMessage.id
3979
+ ? streamingReplyMessage.id
3980
+ : '';
3981
+ } else {
3982
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId, {
3983
+ content: event.content,
3984
+ pending: true
3985
+ });
3986
+ }
3876
3987
  }
3877
3988
  sendNdjson(res, event);
3878
3989
  }
3879
3990
  });
3880
3991
  traceLines.push(result.interrupted === true ? '[任务] 已停止' : '[任务] 已完成');
3881
- appendWebAgentTraceMessage(state.webHistoryDir, sessionRef, traceLines.join('\n'), {
3992
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
3993
+ pending: false,
3994
+ contextMode,
3995
+ resumeAttempted,
3996
+ resumeSucceeded
3997
+ });
3998
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
3999
+ content: traceLines.join('\n'),
3882
4000
  traceEvents,
3883
4001
  contextMode,
3884
4002
  resumeAttempted,
3885
4003
  resumeSucceeded,
3886
- interrupted: result.interrupted === true
4004
+ interrupted: result.interrupted === true,
4005
+ pending: false
3887
4006
  });
4007
+ if (streamingReplyMessageId) {
4008
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
4009
+ }
3888
4010
  finalizeWebAgentExecution(state, sessionRef, agentSession, agentMeta, {
3889
4011
  contextMode,
3890
4012
  resumeAttempted,
@@ -3902,13 +4024,24 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3902
4024
  });
3903
4025
  } catch (e) {
3904
4026
  traceLines.push(`[错误] ${e && e.message ? e.message : 'Agent 执行失败'}`);
3905
- appendWebAgentTraceMessage(state.webHistoryDir, sessionRef, traceLines.join('\n'), {
4027
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, userMessage && userMessage.id, {
4028
+ pending: false,
4029
+ contextMode,
4030
+ resumeAttempted,
4031
+ resumeSucceeded
4032
+ });
4033
+ patchWebSessionMessage(state.webHistoryDir, sessionRef, traceMessage && traceMessage.id, {
4034
+ content: traceLines.join('\n'),
3906
4035
  traceEvents,
3907
4036
  contextMode,
3908
4037
  resumeAttempted,
3909
4038
  resumeSucceeded,
3910
- interrupted: true
4039
+ interrupted: true,
4040
+ pending: false
3911
4041
  });
4042
+ if (streamingReplyMessageId) {
4043
+ removeWebSessionMessage(state.webHistoryDir, sessionRef, streamingReplyMessageId);
4044
+ }
3912
4045
  sendNdjson(res, {
3913
4046
  type: 'error',
3914
4047
  error: e && e.message ? e.message : 'Agent 执行失败'
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.9.0",
3
+ "version": "5.9.2",
4
4
  "imageVersion": "1.9.0-common",
5
5
  "playwrightCliVersion": "0.1.1",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",