@xcanwin/manyoyo 5.9.2 → 5.9.11

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.
@@ -57,8 +57,10 @@
57
57
  configSaveMessage: '',
58
58
  createLoading: false,
59
59
  createSubmitting: false,
60
+ creatingAgent: false,
60
61
  agentTemplateSaving: false,
61
62
  configSnapshot: null,
63
+ configEditor: null,
62
64
  sessionDetail: null,
63
65
  sessionDetailError: '',
64
66
  sessionDetailRequestId: 0,
@@ -81,6 +83,8 @@
81
83
  title: '',
82
84
  tip: '',
83
85
  currentPath: '',
86
+ pathDraft: '',
87
+ parentPath: '',
84
88
  entries: [],
85
89
  error: ''
86
90
  },
@@ -131,6 +135,7 @@
131
135
  const sidebarBackdrop = document.getElementById('sidebarBackdrop');
132
136
  const openConfigBtn = document.getElementById('openConfigBtn');
133
137
  const openCreateBtn = document.getElementById('openCreateBtn');
138
+ const openCreateMenuBtn = document.getElementById('openCreateMenuBtn');
134
139
  const configModal = document.getElementById('configModal');
135
140
  const configModalTitle = document.getElementById('configModalTitle');
136
141
  const configPath = document.getElementById('configPath');
@@ -165,11 +170,13 @@
165
170
  const directoryPickerModal = document.getElementById('directoryPickerModal');
166
171
  const directoryPickerTitle = document.getElementById('directoryPickerTitle');
167
172
  const directoryPickerTip = document.getElementById('directoryPickerTip');
168
- const directoryPickerCurrent = document.getElementById('directoryPickerCurrent');
173
+ const directoryPickerPathInput = document.getElementById('directoryPickerPathInput');
174
+ const directoryPickerVisitBtn = document.getElementById('directoryPickerVisitBtn');
175
+ const directoryPickerStatus = document.getElementById('directoryPickerStatus');
176
+ const directoryPickerMkdirBtn = document.getElementById('directoryPickerMkdirBtn');
169
177
  const directoryPickerList = document.getElementById('directoryPickerList');
170
178
  const directoryPickerError = document.getElementById('directoryPickerError');
171
179
  const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
172
- const directoryPickerUpBtn = document.getElementById('directoryPickerUpBtn');
173
180
  const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
174
181
  const activeTitle = document.getElementById('activeTitle');
175
182
  const activeMeta = document.getElementById('activeMeta');
@@ -808,6 +815,70 @@
808
815
  directoryPickerError.textContent = text;
809
816
  }
810
817
 
818
+ function ensureConfigCodeEditor() {
819
+ if (!configEditor || state.configEditor || !window.ManyoyoCodeEditor || typeof window.ManyoyoCodeEditor.create !== 'function') {
820
+ return state.configEditor;
821
+ }
822
+ state.configEditor = window.ManyoyoCodeEditor.create(configEditor, {
823
+ doc: '',
824
+ language: 'javascript',
825
+ readOnly: true,
826
+ onChange: function () {
827
+ if (!state.configSaveMessage) {
828
+ return;
829
+ }
830
+ state.configSaveMessage = '';
831
+ showConfigStatus('');
832
+ }
833
+ });
834
+ return state.configEditor;
835
+ }
836
+
837
+ function setConfigEditorValue(value, readOnly) {
838
+ const text = String(value == null ? '' : value);
839
+ const editor = ensureConfigCodeEditor();
840
+ if (editor) {
841
+ editor.setValue(text);
842
+ editor.setLanguage('javascript');
843
+ editor.setReadOnly(readOnly !== false);
844
+ return;
845
+ }
846
+ if ('value' in configEditor) {
847
+ configEditor.value = text;
848
+ configEditor.readOnly = readOnly !== false;
849
+ } else {
850
+ configEditor.textContent = text;
851
+ }
852
+ }
853
+
854
+ function getConfigEditorValue() {
855
+ const editor = ensureConfigCodeEditor();
856
+ if (editor) {
857
+ return editor.getValue();
858
+ }
859
+ if ('value' in configEditor) {
860
+ return configEditor.value || '';
861
+ }
862
+ return configEditor && configEditor.textContent ? configEditor.textContent : '';
863
+ }
864
+
865
+ function setDirectoryPickerStatus(message) {
866
+ if (!directoryPickerStatus) return;
867
+ directoryPickerStatus.textContent = String(message || '').trim() || '未加载';
868
+ }
869
+
870
+ function joinDirectoryPath(basePath, childName) {
871
+ const base = String(basePath || '/').trim() || '/';
872
+ const child = String(childName || '').trim();
873
+ if (!child) {
874
+ return base;
875
+ }
876
+ if (base === '/') {
877
+ return '/' + child.replace(/^\/+/, '');
878
+ }
879
+ return base.replace(/\/+$/, '') + '/' + child.replace(/^\/+/, '');
880
+ }
881
+
811
882
  function envMapToText(envMap) {
812
883
  if (!envMap || typeof envMap !== 'object') {
813
884
  return '';
@@ -2193,10 +2264,11 @@
2193
2264
  }
2194
2265
 
2195
2266
  const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
2196
- const busy = state.loadingSessions || state.loadingMessages || state.sending;
2267
+ const busy = state.loadingSessions || state.loadingMessages || state.sending || state.creatingAgent;
2197
2268
  refreshBtn.disabled = busy;
2198
2269
  if (addAgentBtn) {
2199
2270
  addAgentBtn.disabled = !state.active || busy;
2271
+ addAgentBtn.textContent = state.creatingAgent ? '新建中...' : '新建 AGENT';
2200
2272
  }
2201
2273
  removeBtn.disabled = !state.active || busy;
2202
2274
  removeAllBtn.disabled = !state.active || busy;
@@ -2221,6 +2293,9 @@
2221
2293
  if (openCreateBtn) {
2222
2294
  openCreateBtn.disabled = state.createLoading || state.createSubmitting;
2223
2295
  }
2296
+ if (openCreateMenuBtn) {
2297
+ openCreateMenuBtn.disabled = state.createLoading || state.createSubmitting;
2298
+ }
2224
2299
  if (openConfigBtn) {
2225
2300
  openConfigBtn.disabled = state.configLoading || state.configSaving;
2226
2301
  }
@@ -2251,6 +2326,9 @@
2251
2326
  if (!state.active) {
2252
2327
  sendState.textContent = '未选择会话';
2253
2328
  sendState.classList.remove('is-active');
2329
+ } else if (state.creatingAgent) {
2330
+ sendState.textContent = '正在新建 AGENT…';
2331
+ sendState.classList.add('is-active');
2254
2332
  } else if (activeAgentRunning && agentMode) {
2255
2333
  sendState.textContent = state.agentRun.stopping ? '正在停止 Agent…' : 'Agent 执行中';
2256
2334
  sendState.classList.add('is-active');
@@ -2304,6 +2382,8 @@
2304
2382
  );
2305
2383
  if (!state.active) {
2306
2384
  sendState.textContent = '未选择会话';
2385
+ } else if (state.creatingAgent) {
2386
+ sendState.textContent = '正在新建 AGENT…';
2307
2387
  } else if (agentMode && !agentEnabled) {
2308
2388
  sendState.textContent = '当前会话未配置 AGENT 模板';
2309
2389
  } else if (state.sending) {
@@ -2313,7 +2393,7 @@
2313
2393
  } else {
2314
2394
  sendState.textContent = '就绪';
2315
2395
  }
2316
- sendState.classList.toggle('is-active', state.sending);
2396
+ sendState.classList.toggle('is-active', state.sending || state.creatingAgent);
2317
2397
  if (composer) {
2318
2398
  composer.hidden = !activityTab;
2319
2399
  }
@@ -2437,8 +2517,7 @@
2437
2517
  configPath.textContent = lines.filter(Boolean).join('\n');
2438
2518
  }
2439
2519
  if (configEditor) {
2440
- configEditor.readOnly = config.editable === false;
2441
- configEditor.value = typeof config.raw === 'string' ? config.raw : '';
2520
+ setConfigEditorValue(typeof config.raw === 'string' ? config.raw : '', config.editable === false);
2442
2521
  }
2443
2522
  }
2444
2523
 
@@ -2482,7 +2561,7 @@
2482
2561
  try {
2483
2562
  await api('/api/config', {
2484
2563
  method: 'PUT',
2485
- body: JSON.stringify({ raw: configEditor.value || '' })
2564
+ body: JSON.stringify({ raw: getConfigEditorValue() })
2486
2565
  });
2487
2566
  const config = await fetchConfigSnapshot();
2488
2567
  renderConfigModalSnapshot(config);
@@ -2542,39 +2621,73 @@
2542
2621
  if (directoryPickerTip) {
2543
2622
  directoryPickerTip.textContent = picker.tip || '';
2544
2623
  }
2545
- if (directoryPickerCurrent) {
2546
- directoryPickerCurrent.textContent = picker.currentPath || '未选择目录';
2624
+ if (directoryPickerPathInput) {
2625
+ directoryPickerPathInput.value = picker.pathDraft || picker.currentPath || '/';
2547
2626
  }
2548
2627
  showDirectoryPickerError(picker.error);
2549
- if (directoryPickerUpBtn) {
2550
- directoryPickerUpBtn.disabled = picker.loading || !picker.currentPath || !picker.parentPath;
2628
+ if (directoryPickerVisitBtn) {
2629
+ directoryPickerVisitBtn.disabled = picker.loading || !(picker.pathDraft || picker.currentPath);
2630
+ }
2631
+ if (directoryPickerMkdirBtn) {
2632
+ directoryPickerMkdirBtn.disabled = picker.loading || !picker.currentPath;
2551
2633
  }
2552
2634
  if (directoryPickerSelectBtn) {
2553
2635
  directoryPickerSelectBtn.disabled = picker.loading || !picker.currentPath;
2554
2636
  }
2637
+ if (picker.loading) {
2638
+ setDirectoryPickerStatus('读取目录中');
2639
+ } else if (picker.entries.length) {
2640
+ setDirectoryPickerStatus('共 ' + picker.entries.length + ' 项');
2641
+ } else if (picker.currentPath) {
2642
+ setDirectoryPickerStatus('共 0 项');
2643
+ } else {
2644
+ setDirectoryPickerStatus('未加载');
2645
+ }
2555
2646
  if (!directoryPickerList) {
2556
2647
  return;
2557
2648
  }
2558
2649
  directoryPickerList.innerHTML = '';
2559
2650
  if (picker.loading) {
2560
2651
  const loading = document.createElement('div');
2561
- loading.className = 'empty';
2652
+ loading.className = 'files-empty';
2562
2653
  loading.textContent = '目录加载中...';
2563
2654
  directoryPickerList.appendChild(loading);
2564
2655
  return;
2565
2656
  }
2657
+ if (picker.parentPath) {
2658
+ const parentButton = document.createElement('button');
2659
+ parentButton.type = 'button';
2660
+ parentButton.className = 'files-entry files-entry-parent';
2661
+ parentButton.title = picker.parentPath;
2662
+ parentButton.innerHTML = `
2663
+ <span class="files-entry-name">
2664
+ <span class="files-entry-title">..</span>
2665
+ </span>
2666
+ <span class="files-entry-meta">上一级</span>
2667
+ `;
2668
+ parentButton.addEventListener('click', function () {
2669
+ loadDirectoryPicker(picker.parentPath);
2670
+ });
2671
+ directoryPickerList.appendChild(parentButton);
2672
+ }
2566
2673
  if (!picker.entries.length) {
2567
2674
  const empty = document.createElement('div');
2568
- empty.className = 'empty';
2569
- empty.textContent = '当前目录下没有可选子目录';
2675
+ empty.className = 'files-empty';
2676
+ empty.innerHTML = '&nbsp;';
2570
2677
  directoryPickerList.appendChild(empty);
2571
2678
  return;
2572
2679
  }
2573
2680
  picker.entries.forEach(function (entry) {
2574
2681
  const btn = document.createElement('button');
2575
2682
  btn.type = 'button';
2576
- btn.className = 'dir-picker-item secondary';
2577
- btn.textContent = entry.name;
2683
+ btn.className = 'files-entry';
2684
+ btn.title = String(entry.path || entry.name || '');
2685
+ btn.innerHTML = `
2686
+ <span class="files-entry-name">
2687
+ <span class="files-entry-title">${escapeHtml(entry.name || entry.path || '未命名')}</span>
2688
+ </span>
2689
+ <span class="files-entry-meta">目录</span>
2690
+ `;
2578
2691
  btn.addEventListener('click', function () {
2579
2692
  loadDirectoryPicker(entry.path);
2580
2693
  });
@@ -2586,15 +2699,15 @@
2586
2699
  const picker = state.directoryPicker;
2587
2700
  picker.loading = true;
2588
2701
  picker.error = '';
2589
- if (targetPath) {
2590
- picker.currentPath = targetPath;
2591
- }
2702
+ const nextPath = String(targetPath || picker.pathDraft || picker.currentPath || '/').trim() || '/';
2703
+ picker.pathDraft = nextPath;
2592
2704
  renderDirectoryPicker();
2593
2705
  try {
2594
2706
  const params = new URLSearchParams();
2595
- params.set('path', picker.currentPath || '/');
2707
+ params.set('path', nextPath);
2596
2708
  const data = await api('/api/fs/directories?' + params.toString());
2597
- picker.currentPath = data.currentPath || picker.currentPath;
2709
+ picker.currentPath = data.currentPath || nextPath;
2710
+ picker.pathDraft = picker.currentPath;
2598
2711
  picker.parentPath = data.parentPath || '';
2599
2712
  picker.entries = Array.isArray(data.entries) ? data.entries : [];
2600
2713
  } catch (e) {
@@ -2606,6 +2719,32 @@
2606
2719
  }
2607
2720
  }
2608
2721
 
2722
+ async function createDirectoryInPicker() {
2723
+ const picker = state.directoryPicker;
2724
+ if (picker.loading || !picker.currentPath) {
2725
+ return;
2726
+ }
2727
+ const input = window.prompt('请输入新目录名称');
2728
+ const name = String(input || '').trim();
2729
+ if (!name) {
2730
+ return;
2731
+ }
2732
+ picker.loading = true;
2733
+ picker.error = '';
2734
+ renderDirectoryPicker();
2735
+ try {
2736
+ await api('/api/fs/directories/mkdir', {
2737
+ method: 'POST',
2738
+ body: JSON.stringify({ path: joinDirectoryPath(picker.currentPath, name) })
2739
+ });
2740
+ await loadDirectoryPicker(picker.currentPath);
2741
+ } catch (e) {
2742
+ picker.loading = false;
2743
+ picker.error = e && e.message ? e.message : '创建目录失败';
2744
+ renderDirectoryPicker();
2745
+ }
2746
+ }
2747
+
2609
2748
  function closeDirectoryPicker() {
2610
2749
  state.directoryPicker.open = false;
2611
2750
  state.directoryPicker.loading = false;
@@ -2613,6 +2752,7 @@
2613
2752
  state.directoryPicker.title = '';
2614
2753
  state.directoryPicker.tip = '';
2615
2754
  state.directoryPicker.currentPath = '';
2755
+ state.directoryPicker.pathDraft = '';
2616
2756
  state.directoryPicker.parentPath = '';
2617
2757
  state.directoryPicker.entries = [];
2618
2758
  state.directoryPicker.error = '';
@@ -2640,6 +2780,7 @@
2640
2780
  picker.title = '选择 hostPath';
2641
2781
  picker.tip = '浏览宿主机目录,选中后会回填 create 表单。';
2642
2782
  picker.currentPath = (createHostPath.value || '').trim() || '/';
2783
+ picker.pathDraft = picker.currentPath;
2643
2784
  renderDirectoryPicker();
2644
2785
  loadDirectoryPicker(picker.currentPath);
2645
2786
  }
@@ -2699,6 +2840,11 @@
2699
2840
  if (!targetContainer) {
2700
2841
  return;
2701
2842
  }
2843
+ if (state.creatingAgent) {
2844
+ return;
2845
+ }
2846
+ state.creatingAgent = true;
2847
+ syncUi();
2702
2848
  try {
2703
2849
  const data = await api('/api/sessions/' + encodeURIComponent(targetContainer) + '/agents', {
2704
2850
  method: 'POST',
@@ -2712,6 +2858,9 @@
2712
2858
  }
2713
2859
  } catch (e) {
2714
2860
  alert(e.message);
2861
+ } finally {
2862
+ state.creatingAgent = false;
2863
+ syncUi();
2715
2864
  }
2716
2865
  }
2717
2866
 
@@ -2755,9 +2904,7 @@
2755
2904
  });
2756
2905
  group.containers.forEach(function (containerGroup) {
2757
2906
  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;
2907
+ return compareSessionByCreatedDesc(a, b);
2761
2908
  });
2762
2909
  });
2763
2910
  return group;
@@ -2772,6 +2919,108 @@
2772
2919
  return (parts || []).filter(Boolean).join(' · ');
2773
2920
  }
2774
2921
 
2922
+ function getSessionCreatedTime(session) {
2923
+ if (session && session.createdAt) {
2924
+ const time = new Date(session.createdAt).getTime();
2925
+ if (Number.isFinite(time)) {
2926
+ return time;
2927
+ }
2928
+ }
2929
+ return 0;
2930
+ }
2931
+
2932
+ function getSessionUpdatedTime(session) {
2933
+ if (session && session.updatedAt) {
2934
+ const time = new Date(session.updatedAt).getTime();
2935
+ if (Number.isFinite(time)) {
2936
+ return time;
2937
+ }
2938
+ }
2939
+ return 0;
2940
+ }
2941
+
2942
+ function getSessionAgentCreationRank(session) {
2943
+ const agentId = session && session.agentId ? String(session.agentId) : '';
2944
+ if (!agentId || agentId === 'default') {
2945
+ return 1;
2946
+ }
2947
+ const matched = agentId.match(/^agent-(\d+)$/);
2948
+ return matched ? (Number(matched[1]) || 0) : 0;
2949
+ }
2950
+
2951
+ function compareSessionByCreatedDesc(a, b) {
2952
+ const createdA = getSessionCreatedTime(a);
2953
+ const createdB = getSessionCreatedTime(b);
2954
+ if (createdA !== createdB) {
2955
+ return createdB - createdA;
2956
+ }
2957
+ if (a && b && a.containerName && a.containerName === b.containerName) {
2958
+ const rankA = getSessionAgentCreationRank(a);
2959
+ const rankB = getSessionAgentCreationRank(b);
2960
+ if (rankA !== rankB) {
2961
+ return rankB - rankA;
2962
+ }
2963
+ }
2964
+ const updatedA = getSessionUpdatedTime(a);
2965
+ const updatedB = getSessionUpdatedTime(b);
2966
+ if (updatedA !== updatedB) {
2967
+ return updatedB - updatedA;
2968
+ }
2969
+ return String((a && a.name) || '').localeCompare(String((b && b.name) || ''), 'zh-CN');
2970
+ }
2971
+
2972
+ function findLatestCreatedSessionName(sessions, preferredContainerName) {
2973
+ const list = Array.isArray(sessions) ? sessions.filter(Boolean) : [];
2974
+ if (!list.length) {
2975
+ return '';
2976
+ }
2977
+ const targetContainer = String(preferredContainerName || '').trim();
2978
+ const scoped = targetContainer
2979
+ ? list.filter(function (session) {
2980
+ return session && session.containerName === targetContainer;
2981
+ })
2982
+ : list;
2983
+ const candidates = scoped.length ? scoped : list;
2984
+ const sorted = candidates.slice().sort(compareSessionByCreatedDesc);
2985
+ return sorted.length && sorted[0] && sorted[0].name ? sorted[0].name : '';
2986
+ }
2987
+
2988
+ function findPreferredSessionNameAfterRemoval(sessions, removedName) {
2989
+ const removedSessionName = String(removedName || '').trim();
2990
+ if (!removedSessionName) {
2991
+ return '';
2992
+ }
2993
+ const removedRef = parseSessionKey(removedSessionName);
2994
+ const remaining = (Array.isArray(sessions) ? sessions : []).filter(function (session) {
2995
+ return session
2996
+ && session.name
2997
+ && session.name !== removedSessionName
2998
+ && session.containerName === removedRef.containerName;
2999
+ });
3000
+ if (!remaining.length) {
3001
+ return '';
3002
+ }
3003
+
3004
+ const removedRank = getSessionAgentCreationRank({ agentId: removedRef.agentId });
3005
+ if (removedRef.agentId && removedRef.agentId !== WEB_DEFAULT_AGENT_ID && removedRank > 0) {
3006
+ const lowerRanked = remaining
3007
+ .filter(function (session) { return getSessionAgentCreationRank(session) < removedRank; })
3008
+ .sort(function (a, b) { return getSessionAgentCreationRank(b) - getSessionAgentCreationRank(a); });
3009
+ if (lowerRanked.length && lowerRanked[0] && lowerRanked[0].name) {
3010
+ return lowerRanked[0].name;
3011
+ }
3012
+
3013
+ const higherRanked = remaining
3014
+ .filter(function (session) { return getSessionAgentCreationRank(session) > removedRank; })
3015
+ .sort(function (a, b) { return getSessionAgentCreationRank(a) - getSessionAgentCreationRank(b); });
3016
+ if (higherRanked.length && higherRanked[0] && higherRanked[0].name) {
3017
+ return higherRanked[0].name;
3018
+ }
3019
+ }
3020
+
3021
+ return findLatestCreatedSessionName(remaining, removedRef.containerName);
3022
+ }
3023
+
2775
3024
  function createDisclosureButton(expanded, label) {
2776
3025
  const button = document.createElement('button');
2777
3026
  button.type = 'button';
@@ -2916,7 +3165,7 @@
2916
3165
  const addAgentBtn = document.createElement('button');
2917
3166
  addAgentBtn.type = 'button';
2918
3167
  addAgentBtn.className = 'secondary tree-node-menu-item';
2919
- addAgentBtn.textContent = '新建AGENT';
3168
+ addAgentBtn.textContent = '新建 AGENT';
2920
3169
  addAgentBtn.addEventListener('click', function (event) {
2921
3170
  event.stopPropagation();
2922
3171
  createAgentSession(containerName);
@@ -3057,12 +3306,15 @@
3057
3306
  }
3058
3307
 
3059
3308
  function renderSessions() {
3309
+ const directoryCount = new Set(state.sessions.map(function (session) {
3310
+ return String(session && session.hostPath ? session.hostPath : '').trim() || '未配置目录';
3311
+ }).filter(Boolean)).size;
3060
3312
  const containerCount = new Set(state.sessions.map(function (session) {
3061
3313
  return session && session.containerName ? session.containerName : '';
3062
3314
  }).filter(Boolean)).size;
3063
3315
  sessionCount.textContent = state.loadingSessions
3064
3316
  ? '加载中...'
3065
- : `${state.sessions.length} 个 AGENT / ${containerCount} 个容器`;
3317
+ : `${directoryCount} 个 目录 / ${containerCount} 个容器 / ${state.sessions.length} 个 AGENT`;
3066
3318
 
3067
3319
  if (state.loadingSessions) {
3068
3320
  renderSessionsLoading();
@@ -3302,7 +3554,7 @@
3302
3554
  }
3303
3555
  }
3304
3556
 
3305
- function applySessionsSnapshot(rawSessions, preferredName) {
3557
+ function applySessionsSnapshot(rawSessions, preferredName, preferredContainerName) {
3306
3558
  const previousActive = state.active;
3307
3559
  state.sessions = Array.isArray(rawSessions) ? rawSessions : [];
3308
3560
  pruneSidebarTreeState();
@@ -3317,7 +3569,7 @@
3317
3569
  state.sessionDetailError = '';
3318
3570
  }
3319
3571
  if (!state.active && state.sessions.length) {
3320
- state.active = state.sessions[0].name;
3572
+ state.active = findLatestCreatedSessionName(state.sessions, preferredContainerName) || state.sessions[0].name;
3321
3573
  }
3322
3574
  if (state.active && state.active !== previousActive) {
3323
3575
  ensureSessionPathExpanded(state.active);
@@ -3341,7 +3593,7 @@
3341
3593
  let requestError = null;
3342
3594
  try {
3343
3595
  const data = await api('/api/sessions');
3344
- applySessionsSnapshot(data.sessions, opts.preferredName);
3596
+ applySessionsSnapshot(data.sessions, opts.preferredName, opts.preferredContainerName);
3345
3597
  } catch (e) {
3346
3598
  requestError = e;
3347
3599
  } finally {
@@ -3767,6 +4019,13 @@
3767
4019
  });
3768
4020
  }
3769
4021
 
4022
+ if (openCreateMenuBtn) {
4023
+ openCreateMenuBtn.addEventListener('click', function () {
4024
+ closeMobileActionsMenu();
4025
+ openCreateModal();
4026
+ });
4027
+ }
4028
+
3770
4029
  if (agentTemplateBtn) {
3771
4030
  agentTemplateBtn.addEventListener('click', function () {
3772
4031
  openAgentTemplateModal().catch(function (e) {
@@ -3794,16 +4053,6 @@
3794
4053
  });
3795
4054
  }
3796
4055
 
3797
- if (configEditor) {
3798
- configEditor.addEventListener('input', function () {
3799
- if (!state.configSaveMessage) {
3800
- return;
3801
- }
3802
- state.configSaveMessage = '';
3803
- showConfigStatus('');
3804
- });
3805
- }
3806
-
3807
4056
  if (createCancelBtn) {
3808
4057
  createCancelBtn.addEventListener('click', function () {
3809
4058
  closeCreateModal();
@@ -3829,14 +4078,32 @@
3829
4078
  });
3830
4079
  }
3831
4080
 
3832
- if (directoryPickerUpBtn) {
3833
- directoryPickerUpBtn.addEventListener('click', function () {
3834
- if (state.directoryPicker.parentPath) {
3835
- loadDirectoryPicker(state.directoryPicker.parentPath);
4081
+ if (directoryPickerPathInput) {
4082
+ directoryPickerPathInput.addEventListener('input', function () {
4083
+ state.directoryPicker.pathDraft = directoryPickerPathInput.value;
4084
+ });
4085
+ directoryPickerPathInput.addEventListener('keydown', function (event) {
4086
+ if (event.key === 'Enter') {
4087
+ event.preventDefault();
4088
+ loadDirectoryPicker();
3836
4089
  }
3837
4090
  });
3838
4091
  }
3839
4092
 
4093
+ if (directoryPickerVisitBtn) {
4094
+ directoryPickerVisitBtn.addEventListener('click', function () {
4095
+ loadDirectoryPicker();
4096
+ });
4097
+ }
4098
+
4099
+ if (directoryPickerMkdirBtn) {
4100
+ directoryPickerMkdirBtn.addEventListener('click', function () {
4101
+ createDirectoryInPicker().catch(function (e) {
4102
+ showDirectoryPickerError(e && e.message ? e.message : '创建目录失败');
4103
+ });
4104
+ });
4105
+ }
4106
+
3840
4107
  if (directoryPickerSelectBtn) {
3841
4108
  directoryPickerSelectBtn.addEventListener('click', function () {
3842
4109
  applyPickedDirectory();
@@ -4347,14 +4614,21 @@
4347
4614
  closeMobileActionsMenu();
4348
4615
  const activeSession = getActiveSession();
4349
4616
  const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
4350
- const yes = confirm('确认删除对话 ' + targetAgent + ' ?');
4617
+ const yes = confirm('确认删除 AGENT ' + targetAgent + ' ?');
4351
4618
  if (!yes) return;
4352
4619
  try {
4353
4620
  const current = state.active;
4354
- await api('/api/sessions/' + encodeURIComponent(current) + '/remove-with-history', {
4621
+ const targetContainerName = activeSession && activeSession.containerName ? activeSession.containerName : '';
4622
+ const fallbackSessionName = findPreferredSessionNameAfterRemoval(state.sessions, current);
4623
+ await api('/api/sessions/' + encodeURIComponent(state.active) + '/remove-with-history', {
4355
4624
  method: 'POST'
4356
4625
  });
4357
- await loadSessions(current);
4626
+ await refreshSessions({
4627
+ preferredName: fallbackSessionName || '',
4628
+ preferredContainerName: targetContainerName,
4629
+ withLoading: true,
4630
+ reloadMessages: true
4631
+ });
4358
4632
  } catch (e) {
4359
4633
  alert(e.message);
4360
4634
  }
@@ -35,8 +35,10 @@ function createEditor(parent, options = {}) {
35
35
  const initialDoc = String(options.doc || '');
36
36
  const initialLanguage = String(options.language || 'text').trim();
37
37
  const initialReadOnly = options.readOnly !== false;
38
+ const onChange = typeof options.onChange === 'function' ? options.onChange : null;
38
39
  const languageCompartment = new Compartment();
39
40
  const readOnlyCompartment = new Compartment();
41
+ let suppressChange = false;
40
42
  const view = new EditorView({
41
43
  parent: target,
42
44
  state: EditorState.create({
@@ -44,6 +46,12 @@ function createEditor(parent, options = {}) {
44
46
  extensions: [
45
47
  basicSetup,
46
48
  EditorView.lineWrapping,
49
+ EditorView.updateListener.of(function (update) {
50
+ if (!update.docChanged || suppressChange || !onChange) {
51
+ return;
52
+ }
53
+ onChange(update.state.doc.toString());
54
+ }),
47
55
  readOnlyCompartment.of([
48
56
  EditorState.readOnly.of(initialReadOnly),
49
57
  EditorView.editable.of(!initialReadOnly)
@@ -65,6 +73,7 @@ function createEditor(parent, options = {}) {
65
73
  return {
66
74
  setValue(nextValue) {
67
75
  const text = String(nextValue == null ? '' : nextValue);
76
+ suppressChange = true;
68
77
  view.dispatch({
69
78
  changes: {
70
79
  from: 0,
@@ -72,6 +81,10 @@ function createEditor(parent, options = {}) {
72
81
  insert: text
73
82
  }
74
83
  });
84
+ suppressChange = false;
85
+ },
86
+ getValue() {
87
+ return view.state.doc.toString();
75
88
  },
76
89
  setLanguage(nextLanguage) {
77
90
  view.dispatch({