@xcanwin/manyoyo 5.9.3 → 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.
@@ -60,6 +60,7 @@
60
60
  creatingAgent: false,
61
61
  agentTemplateSaving: false,
62
62
  configSnapshot: null,
63
+ configEditor: null,
63
64
  sessionDetail: null,
64
65
  sessionDetailError: '',
65
66
  sessionDetailRequestId: 0,
@@ -82,6 +83,8 @@
82
83
  title: '',
83
84
  tip: '',
84
85
  currentPath: '',
86
+ pathDraft: '',
87
+ parentPath: '',
85
88
  entries: [],
86
89
  error: ''
87
90
  },
@@ -167,11 +170,13 @@
167
170
  const directoryPickerModal = document.getElementById('directoryPickerModal');
168
171
  const directoryPickerTitle = document.getElementById('directoryPickerTitle');
169
172
  const directoryPickerTip = document.getElementById('directoryPickerTip');
170
- 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');
171
177
  const directoryPickerList = document.getElementById('directoryPickerList');
172
178
  const directoryPickerError = document.getElementById('directoryPickerError');
173
179
  const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
174
- const directoryPickerUpBtn = document.getElementById('directoryPickerUpBtn');
175
180
  const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
176
181
  const activeTitle = document.getElementById('activeTitle');
177
182
  const activeMeta = document.getElementById('activeMeta');
@@ -810,6 +815,70 @@
810
815
  directoryPickerError.textContent = text;
811
816
  }
812
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
+
813
882
  function envMapToText(envMap) {
814
883
  if (!envMap || typeof envMap !== 'object') {
815
884
  return '';
@@ -2448,8 +2517,7 @@
2448
2517
  configPath.textContent = lines.filter(Boolean).join('\n');
2449
2518
  }
2450
2519
  if (configEditor) {
2451
- configEditor.readOnly = config.editable === false;
2452
- configEditor.value = typeof config.raw === 'string' ? config.raw : '';
2520
+ setConfigEditorValue(typeof config.raw === 'string' ? config.raw : '', config.editable === false);
2453
2521
  }
2454
2522
  }
2455
2523
 
@@ -2493,7 +2561,7 @@
2493
2561
  try {
2494
2562
  await api('/api/config', {
2495
2563
  method: 'PUT',
2496
- body: JSON.stringify({ raw: configEditor.value || '' })
2564
+ body: JSON.stringify({ raw: getConfigEditorValue() })
2497
2565
  });
2498
2566
  const config = await fetchConfigSnapshot();
2499
2567
  renderConfigModalSnapshot(config);
@@ -2553,39 +2621,73 @@
2553
2621
  if (directoryPickerTip) {
2554
2622
  directoryPickerTip.textContent = picker.tip || '';
2555
2623
  }
2556
- if (directoryPickerCurrent) {
2557
- directoryPickerCurrent.textContent = picker.currentPath || '未选择目录';
2624
+ if (directoryPickerPathInput) {
2625
+ directoryPickerPathInput.value = picker.pathDraft || picker.currentPath || '/';
2558
2626
  }
2559
2627
  showDirectoryPickerError(picker.error);
2560
- if (directoryPickerUpBtn) {
2561
- 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;
2562
2633
  }
2563
2634
  if (directoryPickerSelectBtn) {
2564
2635
  directoryPickerSelectBtn.disabled = picker.loading || !picker.currentPath;
2565
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
+ }
2566
2646
  if (!directoryPickerList) {
2567
2647
  return;
2568
2648
  }
2569
2649
  directoryPickerList.innerHTML = '';
2570
2650
  if (picker.loading) {
2571
2651
  const loading = document.createElement('div');
2572
- loading.className = 'empty';
2652
+ loading.className = 'files-empty';
2573
2653
  loading.textContent = '目录加载中...';
2574
2654
  directoryPickerList.appendChild(loading);
2575
2655
  return;
2576
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
+ }
2577
2673
  if (!picker.entries.length) {
2578
2674
  const empty = document.createElement('div');
2579
- empty.className = 'empty';
2580
- empty.textContent = '当前目录下没有可选子目录';
2675
+ empty.className = 'files-empty';
2676
+ empty.innerHTML = '&nbsp;';
2581
2677
  directoryPickerList.appendChild(empty);
2582
2678
  return;
2583
2679
  }
2584
2680
  picker.entries.forEach(function (entry) {
2585
2681
  const btn = document.createElement('button');
2586
2682
  btn.type = 'button';
2587
- btn.className = 'dir-picker-item secondary';
2588
- 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
+ `;
2589
2691
  btn.addEventListener('click', function () {
2590
2692
  loadDirectoryPicker(entry.path);
2591
2693
  });
@@ -2597,15 +2699,15 @@
2597
2699
  const picker = state.directoryPicker;
2598
2700
  picker.loading = true;
2599
2701
  picker.error = '';
2600
- if (targetPath) {
2601
- picker.currentPath = targetPath;
2602
- }
2702
+ const nextPath = String(targetPath || picker.pathDraft || picker.currentPath || '/').trim() || '/';
2703
+ picker.pathDraft = nextPath;
2603
2704
  renderDirectoryPicker();
2604
2705
  try {
2605
2706
  const params = new URLSearchParams();
2606
- params.set('path', picker.currentPath || '/');
2707
+ params.set('path', nextPath);
2607
2708
  const data = await api('/api/fs/directories?' + params.toString());
2608
- picker.currentPath = data.currentPath || picker.currentPath;
2709
+ picker.currentPath = data.currentPath || nextPath;
2710
+ picker.pathDraft = picker.currentPath;
2609
2711
  picker.parentPath = data.parentPath || '';
2610
2712
  picker.entries = Array.isArray(data.entries) ? data.entries : [];
2611
2713
  } catch (e) {
@@ -2617,6 +2719,32 @@
2617
2719
  }
2618
2720
  }
2619
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
+
2620
2748
  function closeDirectoryPicker() {
2621
2749
  state.directoryPicker.open = false;
2622
2750
  state.directoryPicker.loading = false;
@@ -2624,6 +2752,7 @@
2624
2752
  state.directoryPicker.title = '';
2625
2753
  state.directoryPicker.tip = '';
2626
2754
  state.directoryPicker.currentPath = '';
2755
+ state.directoryPicker.pathDraft = '';
2627
2756
  state.directoryPicker.parentPath = '';
2628
2757
  state.directoryPicker.entries = [];
2629
2758
  state.directoryPicker.error = '';
@@ -2651,6 +2780,7 @@
2651
2780
  picker.title = '选择 hostPath';
2652
2781
  picker.tip = '浏览宿主机目录,选中后会回填 create 表单。';
2653
2782
  picker.currentPath = (createHostPath.value || '').trim() || '/';
2783
+ picker.pathDraft = picker.currentPath;
2654
2784
  renderDirectoryPicker();
2655
2785
  loadDirectoryPicker(picker.currentPath);
2656
2786
  }
@@ -2855,6 +2985,42 @@
2855
2985
  return sorted.length && sorted[0] && sorted[0].name ? sorted[0].name : '';
2856
2986
  }
2857
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
+
2858
3024
  function createDisclosureButton(expanded, label) {
2859
3025
  const button = document.createElement('button');
2860
3026
  button.type = 'button';
@@ -3140,12 +3306,15 @@
3140
3306
  }
3141
3307
 
3142
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;
3143
3312
  const containerCount = new Set(state.sessions.map(function (session) {
3144
3313
  return session && session.containerName ? session.containerName : '';
3145
3314
  }).filter(Boolean)).size;
3146
3315
  sessionCount.textContent = state.loadingSessions
3147
3316
  ? '加载中...'
3148
- : `${state.sessions.length} 个 AGENT / ${containerCount} 个容器`;
3317
+ : `${directoryCount} 个 目录 / ${containerCount} 个容器 / ${state.sessions.length} 个 AGENT`;
3149
3318
 
3150
3319
  if (state.loadingSessions) {
3151
3320
  renderSessionsLoading();
@@ -3884,16 +4053,6 @@
3884
4053
  });
3885
4054
  }
3886
4055
 
3887
- if (configEditor) {
3888
- configEditor.addEventListener('input', function () {
3889
- if (!state.configSaveMessage) {
3890
- return;
3891
- }
3892
- state.configSaveMessage = '';
3893
- showConfigStatus('');
3894
- });
3895
- }
3896
-
3897
4056
  if (createCancelBtn) {
3898
4057
  createCancelBtn.addEventListener('click', function () {
3899
4058
  closeCreateModal();
@@ -3919,14 +4078,32 @@
3919
4078
  });
3920
4079
  }
3921
4080
 
3922
- if (directoryPickerUpBtn) {
3923
- directoryPickerUpBtn.addEventListener('click', function () {
3924
- if (state.directoryPicker.parentPath) {
3925
- 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();
3926
4089
  }
3927
4090
  });
3928
4091
  }
3929
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
+
3930
4107
  if (directoryPickerSelectBtn) {
3931
4108
  directoryPickerSelectBtn.addEventListener('click', function () {
3932
4109
  applyPickedDirectory();
@@ -4440,11 +4617,14 @@
4440
4617
  const yes = confirm('确认删除 AGENT ' + targetAgent + ' ?');
4441
4618
  if (!yes) return;
4442
4619
  try {
4620
+ const current = state.active;
4443
4621
  const targetContainerName = activeSession && activeSession.containerName ? activeSession.containerName : '';
4622
+ const fallbackSessionName = findPreferredSessionNameAfterRemoval(state.sessions, current);
4444
4623
  await api('/api/sessions/' + encodeURIComponent(state.active) + '/remove-with-history', {
4445
4624
  method: 'POST'
4446
4625
  });
4447
4626
  await refreshSessions({
4627
+ preferredName: fallbackSessionName || '',
4448
4628
  preferredContainerName: targetContainerName,
4449
4629
  withLoading: true,
4450
4630
  reloadMessages: true
@@ -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({
@@ -31583,8 +31583,10 @@
31583
31583
  const initialDoc = String(options.doc || "");
31584
31584
  const initialLanguage = String(options.language || "text").trim();
31585
31585
  const initialReadOnly = options.readOnly !== false;
31586
+ const onChange = typeof options.onChange === "function" ? options.onChange : null;
31586
31587
  const languageCompartment = new Compartment();
31587
31588
  const readOnlyCompartment = new Compartment();
31589
+ let suppressChange = false;
31588
31590
  const view = new EditorView({
31589
31591
  parent: target,
31590
31592
  state: EditorState.create({
@@ -31592,6 +31594,12 @@
31592
31594
  extensions: [
31593
31595
  basicSetup,
31594
31596
  EditorView.lineWrapping,
31597
+ EditorView.updateListener.of(function(update) {
31598
+ if (!update.docChanged || suppressChange || !onChange) {
31599
+ return;
31600
+ }
31601
+ onChange(update.state.doc.toString());
31602
+ }),
31595
31603
  readOnlyCompartment.of([
31596
31604
  EditorState.readOnly.of(initialReadOnly),
31597
31605
  EditorView.editable.of(!initialReadOnly)
@@ -31612,6 +31620,7 @@
31612
31620
  return {
31613
31621
  setValue(nextValue) {
31614
31622
  const text = String(nextValue == null ? "" : nextValue);
31623
+ suppressChange = true;
31615
31624
  view.dispatch({
31616
31625
  changes: {
31617
31626
  from: 0,
@@ -31619,6 +31628,10 @@
31619
31628
  insert: text
31620
31629
  }
31621
31630
  });
31631
+ suppressChange = false;
31632
+ },
31633
+ getValue() {
31634
+ return view.state.doc.toString();
31622
31635
  },
31623
31636
  setLanguage(nextLanguage) {
31624
31637
  view.dispatch({