@xcanwin/manyoyo 5.9.1 → 5.9.3

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
  }
@@ -58,9 +58,10 @@
58
58
  >更多</button>
59
59
  <div class="header-actions" id="headerActions">
60
60
  <button type="button" id="refreshBtn" class="secondary">刷新</button>
61
- <button type="button" id="addAgentBtn" class="secondary">新建AGENT</button>
62
- <button type="button" id="removeBtn" class="danger-outline">删除容器</button>
63
- <button type="button" id="removeAllBtn" class="danger">删除对话</button>
61
+ <button type="button" id="openCreateMenuBtn" class="secondary">新建容器</button>
62
+ <button type="button" id="removeBtn" class="danger">删除容器</button>
63
+ <button type="button" id="addAgentBtn" class="secondary">新建 AGENT</button>
64
+ <button type="button" id="removeAllBtn" class="danger">删除 AGENT</button>
64
65
  </div>
65
66
  </div>
66
67
  </div>
@@ -57,6 +57,7 @@
57
57
  configSaveMessage: '',
58
58
  createLoading: false,
59
59
  createSubmitting: false,
60
+ creatingAgent: false,
60
61
  agentTemplateSaving: false,
61
62
  configSnapshot: null,
62
63
  sessionDetail: null,
@@ -131,6 +132,7 @@
131
132
  const sidebarBackdrop = document.getElementById('sidebarBackdrop');
132
133
  const openConfigBtn = document.getElementById('openConfigBtn');
133
134
  const openCreateBtn = document.getElementById('openCreateBtn');
135
+ const openCreateMenuBtn = document.getElementById('openCreateMenuBtn');
134
136
  const configModal = document.getElementById('configModal');
135
137
  const configModalTitle = document.getElementById('configModalTitle');
136
138
  const configPath = document.getElementById('configPath');
@@ -385,34 +387,6 @@
385
387
  return 'neutral';
386
388
  }
387
389
 
388
- function shouldCompactTraceEvent(traceEvent) {
389
- const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
390
- const phase = event.phase ? String(event.phase) : '';
391
- const kind = event.kind ? String(event.kind) : '';
392
- if (phase !== 'completed') {
393
- return false;
394
- }
395
- return kind === 'command' || kind === 'mcp' || kind === 'tool';
396
- }
397
-
398
- function buildCompactTraceText(traceEvent) {
399
- const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
400
- if (event.kind === 'command') {
401
- const suffix = typeof event.exitCode === 'number'
402
- ? `exit ${event.exitCode}`
403
- : (event.status ? String(event.status) : 'completed');
404
- return `${event.command || event.text || '命令'} · ${suffix}`;
405
- }
406
- if (event.kind === 'mcp') {
407
- const toolLabel = [event.server, event.tool].filter(Boolean).join('.') || event.text || 'MCP';
408
- return event.argumentSummary ? `${toolLabel} · ${event.argumentSummary}` : toolLabel;
409
- }
410
- if (event.kind === 'tool') {
411
- return event.toolName || event.text || '工具';
412
- }
413
- return event.text || '事件';
414
- }
415
-
416
390
  function appendTraceCardBody(cardBody, label, value) {
417
391
  const text = stringifyPrettyJson(value).trim();
418
392
  if (!text) {
@@ -436,12 +410,19 @@
436
410
 
437
411
  function createTraceEventCard(traceEvent) {
438
412
  const event = traceEvent && typeof traceEvent === 'object' ? traceEvent : {};
439
- const compact = shouldCompactTraceEvent(event);
440
413
  const bodyParts = [];
441
- if (!compact && event.kind === 'command' && event.command) {
414
+ if (event.kind === 'command' && event.command) {
442
415
  bodyParts.push({ label: '命令', value: event.command });
416
+ if (typeof event.exitCode === 'number') {
417
+ bodyParts.push({ label: '退出码', value: String(event.exitCode) });
418
+ } else if (event.status) {
419
+ bodyParts.push({ label: '状态', value: event.status });
420
+ }
443
421
  }
444
- if (!compact && event.kind === 'mcp') {
422
+ if (event.kind === 'mcp') {
423
+ if (event.server || event.tool) {
424
+ bodyParts.push({ label: '工具', value: [event.server, event.tool].filter(Boolean).join('.') });
425
+ }
445
426
  if (event.argumentSummary) {
446
427
  bodyParts.push({ label: '参数摘要', value: event.argumentSummary });
447
428
  }
@@ -455,7 +436,7 @@
455
436
  bodyParts.push({ label: '错误', value: event.error });
456
437
  }
457
438
  }
458
- if (!compact && event.kind === 'tool' && event.toolName) {
439
+ if (event.kind === 'tool' && event.toolName) {
459
440
  bodyParts.push({ label: '工具', value: event.toolName });
460
441
  }
461
442
  if ((event.kind === 'agent_message' || event.kind === 'status' || event.kind === 'error') && event.detail) {
@@ -464,7 +445,7 @@
464
445
 
465
446
  const hasBody = bodyParts.length > 0;
466
447
  const card = document.createElement(hasBody ? 'details' : 'div');
467
- card.className = 'trace-card trace-tone-' + resolveTraceTone(event) + (compact ? ' trace-card-compact' : '');
448
+ card.className = 'trace-card trace-tone-' + resolveTraceTone(event);
468
449
  if (hasBody && event.kind === 'error') {
469
450
  card.open = true;
470
451
  }
@@ -479,7 +460,7 @@
479
460
 
480
461
  const title = document.createElement('span');
481
462
  title.className = 'trace-card-title';
482
- title.textContent = compact ? buildCompactTraceText(event) : (event && event.text ? String(event.text) : '事件');
463
+ title.textContent = event && event.text ? String(event.text) : '事件';
483
464
  header.appendChild(title);
484
465
 
485
466
  const phaseText = humanizeTracePhase(event);
@@ -498,9 +479,44 @@
498
479
  bodyParts.forEach(function (part) {
499
480
  appendTraceCardBody(body, part.label, part.value);
500
481
  });
501
- card.appendChild(body);
482
+ card.appendChild(body);
483
+ }
484
+
485
+ return card;
486
+ }
487
+
488
+ function resolveResidualTraceTone(line) {
489
+ const text = String(line || '').trim();
490
+ if (!text) {
491
+ return 'neutral';
502
492
  }
493
+ if (text.startsWith('[stderr]') || text.startsWith('[错误]')) {
494
+ return 'error';
495
+ }
496
+ if (text.startsWith('[任务]') || text.includes('上下文模式') || text.includes('会话恢复') || text.includes('等待 Agent 启动')) {
497
+ return 'status';
498
+ }
499
+ return 'neutral';
500
+ }
501
+
502
+ function createResidualTraceCard(line) {
503
+ const card = document.createElement('div');
504
+ card.className = 'trace-card trace-tone-' + resolveResidualTraceTone(line) + ' trace-card-residual';
503
505
 
506
+ const header = document.createElement('div');
507
+ header.className = 'trace-card-summary';
508
+
509
+ const badge = document.createElement('span');
510
+ badge.className = 'trace-card-badge';
511
+ badge.textContent = '状态';
512
+ header.appendChild(badge);
513
+
514
+ const title = document.createElement('span');
515
+ title.className = 'trace-card-title';
516
+ title.textContent = String(line || '');
517
+ header.appendChild(title);
518
+
519
+ card.appendChild(header);
504
520
  return card;
505
521
  }
506
522
 
@@ -509,21 +525,11 @@
509
525
  const container = document.createElement('div');
510
526
  container.className = 'trace-structured';
511
527
 
512
- const residualLines = buildStructuredTraceResidualLines(message);
513
- if (residualLines.length) {
514
- const summary = document.createElement('div');
515
- summary.className = 'trace-summary';
516
- residualLines.forEach(function (line) {
517
- const item = document.createElement('div');
518
- item.className = 'trace-summary-line';
519
- item.textContent = line;
520
- summary.appendChild(item);
521
- });
522
- container.appendChild(summary);
523
- }
524
-
525
528
  const flow = document.createElement('div');
526
529
  flow.className = 'trace-flow';
530
+ buildStructuredTraceResidualLines(message).forEach(function (line) {
531
+ flow.appendChild(createResidualTraceCard(line));
532
+ });
527
533
  (Array.isArray(message && message.traceEvents) ? message.traceEvents : []).forEach(function (traceEvent) {
528
534
  flow.appendChild(createTraceEventCard(traceEvent));
529
535
  });
@@ -2189,10 +2195,11 @@
2189
2195
  }
2190
2196
 
2191
2197
  const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
2192
- const busy = state.loadingSessions || state.loadingMessages || state.sending;
2198
+ const busy = state.loadingSessions || state.loadingMessages || state.sending || state.creatingAgent;
2193
2199
  refreshBtn.disabled = busy;
2194
2200
  if (addAgentBtn) {
2195
2201
  addAgentBtn.disabled = !state.active || busy;
2202
+ addAgentBtn.textContent = state.creatingAgent ? '新建中...' : '新建 AGENT';
2196
2203
  }
2197
2204
  removeBtn.disabled = !state.active || busy;
2198
2205
  removeAllBtn.disabled = !state.active || busy;
@@ -2217,6 +2224,9 @@
2217
2224
  if (openCreateBtn) {
2218
2225
  openCreateBtn.disabled = state.createLoading || state.createSubmitting;
2219
2226
  }
2227
+ if (openCreateMenuBtn) {
2228
+ openCreateMenuBtn.disabled = state.createLoading || state.createSubmitting;
2229
+ }
2220
2230
  if (openConfigBtn) {
2221
2231
  openConfigBtn.disabled = state.configLoading || state.configSaving;
2222
2232
  }
@@ -2247,6 +2257,9 @@
2247
2257
  if (!state.active) {
2248
2258
  sendState.textContent = '未选择会话';
2249
2259
  sendState.classList.remove('is-active');
2260
+ } else if (state.creatingAgent) {
2261
+ sendState.textContent = '正在新建 AGENT…';
2262
+ sendState.classList.add('is-active');
2250
2263
  } else if (activeAgentRunning && agentMode) {
2251
2264
  sendState.textContent = state.agentRun.stopping ? '正在停止 Agent…' : 'Agent 执行中';
2252
2265
  sendState.classList.add('is-active');
@@ -2300,6 +2313,8 @@
2300
2313
  );
2301
2314
  if (!state.active) {
2302
2315
  sendState.textContent = '未选择会话';
2316
+ } else if (state.creatingAgent) {
2317
+ sendState.textContent = '正在新建 AGENT…';
2303
2318
  } else if (agentMode && !agentEnabled) {
2304
2319
  sendState.textContent = '当前会话未配置 AGENT 模板';
2305
2320
  } else if (state.sending) {
@@ -2309,7 +2324,7 @@
2309
2324
  } else {
2310
2325
  sendState.textContent = '就绪';
2311
2326
  }
2312
- sendState.classList.toggle('is-active', state.sending);
2327
+ sendState.classList.toggle('is-active', state.sending || state.creatingAgent);
2313
2328
  if (composer) {
2314
2329
  composer.hidden = !activityTab;
2315
2330
  }
@@ -2695,6 +2710,11 @@
2695
2710
  if (!targetContainer) {
2696
2711
  return;
2697
2712
  }
2713
+ if (state.creatingAgent) {
2714
+ return;
2715
+ }
2716
+ state.creatingAgent = true;
2717
+ syncUi();
2698
2718
  try {
2699
2719
  const data = await api('/api/sessions/' + encodeURIComponent(targetContainer) + '/agents', {
2700
2720
  method: 'POST',
@@ -2708,6 +2728,9 @@
2708
2728
  }
2709
2729
  } catch (e) {
2710
2730
  alert(e.message);
2731
+ } finally {
2732
+ state.creatingAgent = false;
2733
+ syncUi();
2711
2734
  }
2712
2735
  }
2713
2736
 
@@ -2751,9 +2774,7 @@
2751
2774
  });
2752
2775
  group.containers.forEach(function (containerGroup) {
2753
2776
  containerGroup.sessions.sort(function (a, b) {
2754
- const timeA = a && a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
2755
- const timeB = b && b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
2756
- return timeB - timeA;
2777
+ return compareSessionByCreatedDesc(a, b);
2757
2778
  });
2758
2779
  });
2759
2780
  return group;
@@ -2768,6 +2789,72 @@
2768
2789
  return (parts || []).filter(Boolean).join(' · ');
2769
2790
  }
2770
2791
 
2792
+ function getSessionCreatedTime(session) {
2793
+ if (session && session.createdAt) {
2794
+ const time = new Date(session.createdAt).getTime();
2795
+ if (Number.isFinite(time)) {
2796
+ return time;
2797
+ }
2798
+ }
2799
+ return 0;
2800
+ }
2801
+
2802
+ function getSessionUpdatedTime(session) {
2803
+ if (session && session.updatedAt) {
2804
+ const time = new Date(session.updatedAt).getTime();
2805
+ if (Number.isFinite(time)) {
2806
+ return time;
2807
+ }
2808
+ }
2809
+ return 0;
2810
+ }
2811
+
2812
+ function getSessionAgentCreationRank(session) {
2813
+ const agentId = session && session.agentId ? String(session.agentId) : '';
2814
+ if (!agentId || agentId === 'default') {
2815
+ return 1;
2816
+ }
2817
+ const matched = agentId.match(/^agent-(\d+)$/);
2818
+ return matched ? (Number(matched[1]) || 0) : 0;
2819
+ }
2820
+
2821
+ function compareSessionByCreatedDesc(a, b) {
2822
+ const createdA = getSessionCreatedTime(a);
2823
+ const createdB = getSessionCreatedTime(b);
2824
+ if (createdA !== createdB) {
2825
+ return createdB - createdA;
2826
+ }
2827
+ if (a && b && a.containerName && a.containerName === b.containerName) {
2828
+ const rankA = getSessionAgentCreationRank(a);
2829
+ const rankB = getSessionAgentCreationRank(b);
2830
+ if (rankA !== rankB) {
2831
+ return rankB - rankA;
2832
+ }
2833
+ }
2834
+ const updatedA = getSessionUpdatedTime(a);
2835
+ const updatedB = getSessionUpdatedTime(b);
2836
+ if (updatedA !== updatedB) {
2837
+ return updatedB - updatedA;
2838
+ }
2839
+ return String((a && a.name) || '').localeCompare(String((b && b.name) || ''), 'zh-CN');
2840
+ }
2841
+
2842
+ function findLatestCreatedSessionName(sessions, preferredContainerName) {
2843
+ const list = Array.isArray(sessions) ? sessions.filter(Boolean) : [];
2844
+ if (!list.length) {
2845
+ return '';
2846
+ }
2847
+ const targetContainer = String(preferredContainerName || '').trim();
2848
+ const scoped = targetContainer
2849
+ ? list.filter(function (session) {
2850
+ return session && session.containerName === targetContainer;
2851
+ })
2852
+ : list;
2853
+ const candidates = scoped.length ? scoped : list;
2854
+ const sorted = candidates.slice().sort(compareSessionByCreatedDesc);
2855
+ return sorted.length && sorted[0] && sorted[0].name ? sorted[0].name : '';
2856
+ }
2857
+
2771
2858
  function createDisclosureButton(expanded, label) {
2772
2859
  const button = document.createElement('button');
2773
2860
  button.type = 'button';
@@ -2912,7 +2999,7 @@
2912
2999
  const addAgentBtn = document.createElement('button');
2913
3000
  addAgentBtn.type = 'button';
2914
3001
  addAgentBtn.className = 'secondary tree-node-menu-item';
2915
- addAgentBtn.textContent = '新建AGENT';
3002
+ addAgentBtn.textContent = '新建 AGENT';
2916
3003
  addAgentBtn.addEventListener('click', function (event) {
2917
3004
  event.stopPropagation();
2918
3005
  createAgentSession(containerName);
@@ -3298,7 +3385,7 @@
3298
3385
  }
3299
3386
  }
3300
3387
 
3301
- function applySessionsSnapshot(rawSessions, preferredName) {
3388
+ function applySessionsSnapshot(rawSessions, preferredName, preferredContainerName) {
3302
3389
  const previousActive = state.active;
3303
3390
  state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
3304
3391
  pruneSidebarTreeState();
@@ -3313,7 +3400,7 @@
3313
3400
  state.sessionDetailError = '';
3314
3401
  }
3315
3402
  if (!state.active && state.sessions.length) {
3316
- state.active = state.sessions[0].name;
3403
+ state.active = findLatestCreatedSessionName(state.sessions, preferredContainerName) || state.sessions[0].name;
3317
3404
  }
3318
3405
  if (state.active && state.active !== previousActive) {
3319
3406
  ensureSessionPathExpanded(state.active);
@@ -3337,7 +3424,7 @@
3337
3424
  let requestError = null;
3338
3425
  try {
3339
3426
  const data = await api('/api/sessions');
3340
- applySessionsSnapshot(data.sessions, opts.preferredName);
3427
+ applySessionsSnapshot(data.sessions, opts.preferredName, opts.preferredContainerName);
3341
3428
  } catch (e) {
3342
3429
  requestError = e;
3343
3430
  } finally {
@@ -3763,6 +3850,13 @@
3763
3850
  });
3764
3851
  }
3765
3852
 
3853
+ if (openCreateMenuBtn) {
3854
+ openCreateMenuBtn.addEventListener('click', function () {
3855
+ closeMobileActionsMenu();
3856
+ openCreateModal();
3857
+ });
3858
+ }
3859
+
3766
3860
  if (agentTemplateBtn) {
3767
3861
  agentTemplateBtn.addEventListener('click', function () {
3768
3862
  openAgentTemplateModal().catch(function (e) {
@@ -4343,14 +4437,18 @@
4343
4437
  closeMobileActionsMenu();
4344
4438
  const activeSession = getActiveSession();
4345
4439
  const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
4346
- const yes = confirm('确认删除对话 ' + targetAgent + ' ?');
4440
+ const yes = confirm('确认删除 AGENT ' + targetAgent + ' ?');
4347
4441
  if (!yes) return;
4348
4442
  try {
4349
- const current = state.active;
4350
- await api('/api/sessions/' + encodeURIComponent(current) + '/remove-with-history', {
4443
+ const targetContainerName = activeSession && activeSession.containerName ? activeSession.containerName : '';
4444
+ await api('/api/sessions/' + encodeURIComponent(state.active) + '/remove-with-history', {
4351
4445
  method: 'POST'
4352
4446
  });
4353
- await loadSessions(current);
4447
+ await refreshSessions({
4448
+ preferredContainerName: targetContainerName,
4449
+ withLoading: true,
4450
+ reloadMessages: true
4451
+ });
4354
4452
  } catch (e) {
4355
4453
  alert(e.message);
4356
4454
  }
package/lib/web/server.js CHANGED
@@ -165,6 +165,7 @@ function createEmptyWebAgentSession(agentId, agentName) {
165
165
  agentId,
166
166
  agentName: normalizeWebAgentName(agentId, agentName),
167
167
  agentPromptCommand: '',
168
+ createdAt: null,
168
169
  updatedAt: null,
169
170
  messages: [],
170
171
  lastResumeAt: null,
@@ -181,6 +182,7 @@ function normalizeWebAgentSessionRecord(agentId, rawAgent) {
181
182
  agentPromptCommand: typeof source.agentPromptCommand === 'string'
182
183
  ? normalizeAgentPromptCommandTemplate(source.agentPromptCommand, `agents.${agentId}.agentPromptCommand`)
183
184
  : '',
185
+ createdAt: typeof source.createdAt === 'string' ? source.createdAt : null,
184
186
  updatedAt: typeof source.updatedAt === 'string' ? source.updatedAt : null,
185
187
  messages: Array.isArray(source.messages) ? source.messages : [],
186
188
  lastResumeAt: typeof source.lastResumeAt === 'string' ? source.lastResumeAt : null,
@@ -384,6 +386,45 @@ function listWebAgentSessions(history, options = {}) {
384
386
  });
385
387
  }
386
388
 
389
+ function getWebAgentCreationRank(agentId) {
390
+ if (agentId === WEB_DEFAULT_AGENT_ID) {
391
+ return 1;
392
+ }
393
+ const matched = String(agentId || '').match(/^agent-(\d+)$/);
394
+ return matched ? (Number(matched[1]) || 0) : 0;
395
+ }
396
+
397
+ function getWebSessionCreatedTime(sessionSummary) {
398
+ if (sessionSummary && sessionSummary.createdAt) {
399
+ const time = new Date(sessionSummary.createdAt).getTime();
400
+ if (Number.isFinite(time)) {
401
+ return time;
402
+ }
403
+ }
404
+ return 0;
405
+ }
406
+
407
+ function compareWebSessionCreatedDesc(a, b) {
408
+ const timeA = getWebSessionCreatedTime(a);
409
+ const timeB = getWebSessionCreatedTime(b);
410
+ if (timeA !== timeB) {
411
+ return timeB - timeA;
412
+ }
413
+ if (a && b && a.containerName === b.containerName) {
414
+ const rankA = getWebAgentCreationRank(a.agentId);
415
+ const rankB = getWebAgentCreationRank(b.agentId);
416
+ if (rankA !== rankB) {
417
+ return rankB - rankA;
418
+ }
419
+ }
420
+ const updatedA = a && a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
421
+ const updatedB = b && b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
422
+ if (updatedA !== updatedB) {
423
+ return updatedB - updatedA;
424
+ }
425
+ return String((a && a.name) || '').localeCompare(String((b && b.name) || ''), 'zh-CN');
426
+ }
427
+
387
428
  function createWebSessionMessageId() {
388
429
  return `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
389
430
  }
@@ -402,6 +443,9 @@ function appendWebSessionMessage(webHistoryDir, sessionRefOrContainerName, role,
402
443
  timestamp,
403
444
  ...extra
404
445
  };
446
+ if (!agentSession.createdAt) {
447
+ agentSession.createdAt = timestamp;
448
+ }
405
449
  agentSession.messages.push(message);
406
450
 
407
451
  if (agentSession.messages.length > WEB_HISTORY_MAX_MESSAGES) {
@@ -584,7 +628,11 @@ function createWebAgentSession(history) {
584
628
  }
585
629
  const agentId = `agent-${agentIndex}`;
586
630
  const agentSession = createEmptyWebAgentSession(agentId, `AGENT ${agentIndex}`);
631
+ const timestamp = new Date().toISOString();
632
+ agentSession.createdAt = timestamp;
633
+ agentSession.updatedAt = timestamp;
587
634
  sessionHistory.agents[agentId] = agentSession;
635
+ sessionHistory.updatedAt = timestamp;
588
636
  return agentSession;
589
637
  }
590
638
 
@@ -3041,6 +3089,7 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
3041
3089
  status: containerInfo.status || 'history',
3042
3090
  defaultCommand: containerInfo.defaultCommand || ''
3043
3091
  });
3092
+ const createdAt = agentSession.createdAt || containerInfo.createdAt || null;
3044
3093
  const updatedAt = agentSession.updatedAt || history.updatedAt || (latestMessage && latestMessage.timestamp) || containerInfo.createdAt || null;
3045
3094
  return {
3046
3095
  name: buildWebSessionKey(containerName, agentId),
@@ -3049,6 +3098,7 @@ function buildSessionSummary(ctx, state, containerMap, sessionRef) {
3049
3098
  agentName: agentSession.agentName,
3050
3099
  status: containerInfo.status || 'history',
3051
3100
  image: containerInfo.image || '',
3101
+ createdAt,
3052
3102
  updatedAt,
3053
3103
  messageCount: agentSession.messages.length,
3054
3104
  agentEnabled: isAgentPromptCommandEnabled(effectiveAgentPromptCommand),
@@ -3594,11 +3644,7 @@ async function handleWebApi(req, res, pathname, ctx, state) {
3594
3644
  }))
3595
3645
  .filter(Boolean);
3596
3646
  })
3597
- .sort((a, b) => {
3598
- const timeA = a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
3599
- const timeB = b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
3600
- return timeB - timeA;
3601
- });
3647
+ .sort(compareWebSessionCreatedDesc);
3602
3648
 
3603
3649
  sendJson(res, 200, { sessions });
3604
3650
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "5.9.1",
3
+ "version": "5.9.3",
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",