@xcanwin/manyoyo 5.9.2 → 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.
@@ -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');
@@ -2193,10 +2195,11 @@
2193
2195
  }
2194
2196
 
2195
2197
  const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
2196
- const busy = state.loadingSessions || state.loadingMessages || state.sending;
2198
+ const busy = state.loadingSessions || state.loadingMessages || state.sending || state.creatingAgent;
2197
2199
  refreshBtn.disabled = busy;
2198
2200
  if (addAgentBtn) {
2199
2201
  addAgentBtn.disabled = !state.active || busy;
2202
+ addAgentBtn.textContent = state.creatingAgent ? '新建中...' : '新建 AGENT';
2200
2203
  }
2201
2204
  removeBtn.disabled = !state.active || busy;
2202
2205
  removeAllBtn.disabled = !state.active || busy;
@@ -2221,6 +2224,9 @@
2221
2224
  if (openCreateBtn) {
2222
2225
  openCreateBtn.disabled = state.createLoading || state.createSubmitting;
2223
2226
  }
2227
+ if (openCreateMenuBtn) {
2228
+ openCreateMenuBtn.disabled = state.createLoading || state.createSubmitting;
2229
+ }
2224
2230
  if (openConfigBtn) {
2225
2231
  openConfigBtn.disabled = state.configLoading || state.configSaving;
2226
2232
  }
@@ -2251,6 +2257,9 @@
2251
2257
  if (!state.active) {
2252
2258
  sendState.textContent = '未选择会话';
2253
2259
  sendState.classList.remove('is-active');
2260
+ } else if (state.creatingAgent) {
2261
+ sendState.textContent = '正在新建 AGENT…';
2262
+ sendState.classList.add('is-active');
2254
2263
  } else if (activeAgentRunning && agentMode) {
2255
2264
  sendState.textContent = state.agentRun.stopping ? '正在停止 Agent…' : 'Agent 执行中';
2256
2265
  sendState.classList.add('is-active');
@@ -2304,6 +2313,8 @@
2304
2313
  );
2305
2314
  if (!state.active) {
2306
2315
  sendState.textContent = '未选择会话';
2316
+ } else if (state.creatingAgent) {
2317
+ sendState.textContent = '正在新建 AGENT…';
2307
2318
  } else if (agentMode && !agentEnabled) {
2308
2319
  sendState.textContent = '当前会话未配置 AGENT 模板';
2309
2320
  } else if (state.sending) {
@@ -2313,7 +2324,7 @@
2313
2324
  } else {
2314
2325
  sendState.textContent = '就绪';
2315
2326
  }
2316
- sendState.classList.toggle('is-active', state.sending);
2327
+ sendState.classList.toggle('is-active', state.sending || state.creatingAgent);
2317
2328
  if (composer) {
2318
2329
  composer.hidden = !activityTab;
2319
2330
  }
@@ -2699,6 +2710,11 @@
2699
2710
  if (!targetContainer) {
2700
2711
  return;
2701
2712
  }
2713
+ if (state.creatingAgent) {
2714
+ return;
2715
+ }
2716
+ state.creatingAgent = true;
2717
+ syncUi();
2702
2718
  try {
2703
2719
  const data = await api('/api/sessions/' + encodeURIComponent(targetContainer) + '/agents', {
2704
2720
  method: 'POST',
@@ -2712,6 +2728,9 @@
2712
2728
  }
2713
2729
  } catch (e) {
2714
2730
  alert(e.message);
2731
+ } finally {
2732
+ state.creatingAgent = false;
2733
+ syncUi();
2715
2734
  }
2716
2735
  }
2717
2736
 
@@ -2755,9 +2774,7 @@
2755
2774
  });
2756
2775
  group.containers.forEach(function (containerGroup) {
2757
2776
  containerGroup.sessions.sort(function (a, b) {
2758
- const timeA = a && a.updatedAt ? new Date(a.updatedAt).getTime() : 0;
2759
- const timeB = b && b.updatedAt ? new Date(b.updatedAt).getTime() : 0;
2760
- return timeB - timeA;
2777
+ return compareSessionByCreatedDesc(a, b);
2761
2778
  });
2762
2779
  });
2763
2780
  return group;
@@ -2772,6 +2789,72 @@
2772
2789
  return (parts || []).filter(Boolean).join(' · ');
2773
2790
  }
2774
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
+
2775
2858
  function createDisclosureButton(expanded, label) {
2776
2859
  const button = document.createElement('button');
2777
2860
  button.type = 'button';
@@ -2916,7 +2999,7 @@
2916
2999
  const addAgentBtn = document.createElement('button');
2917
3000
  addAgentBtn.type = 'button';
2918
3001
  addAgentBtn.className = 'secondary tree-node-menu-item';
2919
- addAgentBtn.textContent = '新建AGENT';
3002
+ addAgentBtn.textContent = '新建 AGENT';
2920
3003
  addAgentBtn.addEventListener('click', function (event) {
2921
3004
  event.stopPropagation();
2922
3005
  createAgentSession(containerName);
@@ -3302,7 +3385,7 @@
3302
3385
  }
3303
3386
  }
3304
3387
 
3305
- function applySessionsSnapshot(rawSessions, preferredName) {
3388
+ function applySessionsSnapshot(rawSessions, preferredName, preferredContainerName) {
3306
3389
  const previousActive = state.active;
3307
3390
  state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
3308
3391
  pruneSidebarTreeState();
@@ -3317,7 +3400,7 @@
3317
3400
  state.sessionDetailError = '';
3318
3401
  }
3319
3402
  if (!state.active && state.sessions.length) {
3320
- state.active = state.sessions[0].name;
3403
+ state.active = findLatestCreatedSessionName(state.sessions, preferredContainerName) || state.sessions[0].name;
3321
3404
  }
3322
3405
  if (state.active && state.active !== previousActive) {
3323
3406
  ensureSessionPathExpanded(state.active);
@@ -3341,7 +3424,7 @@
3341
3424
  let requestError = null;
3342
3425
  try {
3343
3426
  const data = await api('/api/sessions');
3344
- applySessionsSnapshot(data.sessions, opts.preferredName);
3427
+ applySessionsSnapshot(data.sessions, opts.preferredName, opts.preferredContainerName);
3345
3428
  } catch (e) {
3346
3429
  requestError = e;
3347
3430
  } finally {
@@ -3767,6 +3850,13 @@
3767
3850
  });
3768
3851
  }
3769
3852
 
3853
+ if (openCreateMenuBtn) {
3854
+ openCreateMenuBtn.addEventListener('click', function () {
3855
+ closeMobileActionsMenu();
3856
+ openCreateModal();
3857
+ });
3858
+ }
3859
+
3770
3860
  if (agentTemplateBtn) {
3771
3861
  agentTemplateBtn.addEventListener('click', function () {
3772
3862
  openAgentTemplateModal().catch(function (e) {
@@ -4347,14 +4437,18 @@
4347
4437
  closeMobileActionsMenu();
4348
4438
  const activeSession = getActiveSession();
4349
4439
  const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
4350
- const yes = confirm('确认删除对话 ' + targetAgent + ' ?');
4440
+ const yes = confirm('确认删除 AGENT ' + targetAgent + ' ?');
4351
4441
  if (!yes) return;
4352
4442
  try {
4353
- const current = state.active;
4354
- 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', {
4355
4445
  method: 'POST'
4356
4446
  });
4357
- await loadSessions(current);
4447
+ await refreshSessions({
4448
+ preferredContainerName: targetContainerName,
4449
+ withLoading: true,
4450
+ reloadMessages: true
4451
+ });
4358
4452
  } catch (e) {
4359
4453
  alert(e.message);
4360
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.2",
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",