@xcanwin/manyoyo 6.2.11 → 6.2.15

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.
@@ -272,6 +272,13 @@ textarea:focus-visible {
272
272
  display: none !important;
273
273
  }
274
274
 
275
+ /* showNotice/showConfirm/showPrompt 可能从已经打开的其它 modal(如目录选择器)内部触发,
276
+ 和其它 .modal-backdrop 共用 z-index:80 时,谁在 DOM 里排后面谁盖住谁,容易被埋到底下;
277
+ 这里给一个更高的 z-index,保证它始终浮在最上层 */
278
+ #genericDialogModal {
279
+ z-index: 90;
280
+ }
281
+
275
282
  .modal {
276
283
  width: min(960px, calc(100vw - 24px));
277
284
  max-height: min(92vh, 860px);
@@ -326,6 +333,28 @@ textarea:focus-visible {
326
333
  word-break: break-all;
327
334
  }
328
335
 
336
+ .modal-message {
337
+ margin: 0;
338
+ color: var(--text);
339
+ font-size: 13px;
340
+ line-height: 1.6;
341
+ }
342
+
343
+ .modal-dialog-input {
344
+ width: 100%;
345
+ margin-top: 10px;
346
+ border: 1px solid var(--line);
347
+ border-radius: 10px;
348
+ padding: 9px 11px;
349
+ background: var(--panel-strong);
350
+ color: var(--text);
351
+ font-size: 13px;
352
+ }
353
+
354
+ .modal-dialog-input[hidden] {
355
+ display: none;
356
+ }
357
+
329
358
  .modal-error {
330
359
  margin-top: 10px;
331
360
  color: var(--danger);
@@ -577,6 +606,9 @@ textarea:focus-visible {
577
606
 
578
607
  .crumb-item:hover {
579
608
  transform: none;
609
+ /* 不显式设置背景色的话,通用 button:hover 规则的深色背景会覆盖进来,
610
+ 和同色系的文字撞色看不清,这里保持透明背景即可 */
611
+ background: transparent;
580
612
  text-decoration: underline;
581
613
  }
582
614
 
@@ -606,6 +638,11 @@ textarea:focus-visible {
606
638
  border-radius: 999px;
607
639
  }
608
640
 
641
+ .crumb-jump-active:hover {
642
+ /* 同 .crumb-item:hover,避免通用 button:hover 的深色背景和文字撞色 */
643
+ background: var(--panel-soft);
644
+ }
645
+
609
646
  .session-card {
610
647
  position: relative;
611
648
  }
@@ -623,7 +660,7 @@ textarea:focus-visible {
623
660
 
624
661
  .session-card-container .session-card-button,
625
662
  .session-card-agent .session-card-button {
626
- padding-right: 38px;
663
+ padding-right: 46px;
627
664
  }
628
665
 
629
666
  .session-card-button:hover {
@@ -761,18 +798,18 @@ textarea:focus-visible {
761
798
  }
762
799
 
763
800
  .tree-node-menu-trigger {
764
- width: 26px;
765
- height: 26px;
801
+ width: 30px;
802
+ height: 30px;
766
803
  padding: 0;
767
804
  display: inline-flex;
768
805
  align-items: center;
769
806
  justify-content: center;
770
- border-radius: 8px;
807
+ border-radius: 9px;
771
808
  }
772
809
 
773
810
  .tree-node-menu-trigger svg {
774
- width: 14px;
775
- height: 14px;
811
+ width: 16px;
812
+ height: 16px;
776
813
  fill: currentColor;
777
814
  }
778
815
 
@@ -1181,7 +1218,7 @@ body.workspace-switcher-open .workspace-switcher-panel {
1181
1218
  align-items: flex-start;
1182
1219
  justify-content: space-between;
1183
1220
  gap: 12px;
1184
- flex-wrap: wrap;
1221
+ flex-wrap: nowrap;
1185
1222
  padding: 10px 12px;
1186
1223
  border-bottom: 1px solid var(--line);
1187
1224
  background: rgba(255, 251, 244, 0.96);
@@ -1203,6 +1240,11 @@ body.workspace-switcher-open .workspace-switcher-panel {
1203
1240
  margin-left: auto;
1204
1241
  }
1205
1242
 
1243
+ .files-back-btn {
1244
+ display: none;
1245
+ flex-shrink: 0;
1246
+ }
1247
+
1206
1248
  .files-preview-title {
1207
1249
  font-family: var(--font-display);
1208
1250
  font-size: 14px;
@@ -2125,7 +2167,7 @@ body.composer-options-open .composer-options-panel {
2125
2167
 
2126
2168
  .files-layout {
2127
2169
  grid-template-columns: minmax(0, 1fr);
2128
- grid-template-rows: minmax(180px, 38%) minmax(0, 1fr);
2170
+ grid-template-rows: minmax(0, 1fr);
2129
2171
  }
2130
2172
 
2131
2173
  .files-sidebar {
@@ -2133,6 +2175,41 @@ body.composer-options-open .composer-options-panel {
2133
2175
  border-bottom: 1px solid var(--line);
2134
2176
  }
2135
2177
 
2178
+ .files-browser[data-mobile-pane="list"] .files-preview {
2179
+ display: none;
2180
+ }
2181
+
2182
+ .files-browser[data-mobile-pane="preview"] .files-sidebar {
2183
+ display: none;
2184
+ }
2185
+
2186
+ .files-browser[data-mobile-pane="preview"] .files-toolbar {
2187
+ display: none;
2188
+ }
2189
+
2190
+ .files-browser[data-mobile-pane="preview"] .files-back-btn {
2191
+ display: inline-flex;
2192
+ order: 1;
2193
+ }
2194
+
2195
+ .files-preview-head {
2196
+ flex-wrap: wrap;
2197
+ align-items: center;
2198
+ }
2199
+
2200
+ .files-preview-actions {
2201
+ order: 2;
2202
+ }
2203
+
2204
+ .files-preview-head-main {
2205
+ order: 3;
2206
+ flex-basis: 100%;
2207
+ }
2208
+
2209
+ .files-preview-meta {
2210
+ display: none;
2211
+ }
2212
+
2136
2213
  .composer-inner {
2137
2214
  align-items: stretch;
2138
2215
  }
@@ -222,7 +222,7 @@
222
222
  <button type="button" id="removeConfirmCancelBtn" class="secondary">关闭</button>
223
223
  </header>
224
224
  <div class="modal-body">
225
- <p id="removeConfirmMessage"></p>
225
+ <p id="removeConfirmMessage" class="modal-message"></p>
226
226
  </div>
227
227
  <footer class="modal-footer">
228
228
  <button type="button" id="removeConfirmKeepHistoryBtn" class="secondary">否,保留历史</button>
@@ -231,6 +231,23 @@
231
231
  </section>
232
232
  </div>
233
233
 
234
+ <div id="genericDialogModal" class="modal-backdrop" hidden>
235
+ <section class="modal modal-sm" role="dialog" aria-modal="true" aria-labelledby="genericDialogTitle">
236
+ <header class="modal-header">
237
+ <h2 id="genericDialogTitle"></h2>
238
+ <button type="button" id="genericDialogCloseBtn" class="secondary">关闭</button>
239
+ </header>
240
+ <div class="modal-body">
241
+ <p id="genericDialogMessage" class="modal-message"></p>
242
+ <input type="text" id="genericDialogInput" class="modal-dialog-input" hidden />
243
+ </div>
244
+ <footer class="modal-footer">
245
+ <button type="button" id="genericDialogCancelBtn" class="secondary" hidden>取消</button>
246
+ <button type="button" id="genericDialogConfirmBtn">确定</button>
247
+ </footer>
248
+ </section>
249
+ </div>
250
+
234
251
  <div id="directoryPickerModal" class="modal-backdrop" hidden>
235
252
  <section class="modal dir-picker-modal" role="dialog" aria-modal="true" aria-labelledby="directoryPickerTitle">
236
253
  <header class="modal-header">
@@ -185,6 +185,13 @@
185
185
  const removeConfirmCancelBtn = document.getElementById('removeConfirmCancelBtn');
186
186
  const removeConfirmKeepHistoryBtn = document.getElementById('removeConfirmKeepHistoryBtn');
187
187
  const removeConfirmWithHistoryBtn = document.getElementById('removeConfirmWithHistoryBtn');
188
+ const genericDialogModal = document.getElementById('genericDialogModal');
189
+ const genericDialogTitle = document.getElementById('genericDialogTitle');
190
+ const genericDialogMessage = document.getElementById('genericDialogMessage');
191
+ const genericDialogInput = document.getElementById('genericDialogInput');
192
+ const genericDialogCloseBtn = document.getElementById('genericDialogCloseBtn');
193
+ const genericDialogCancelBtn = document.getElementById('genericDialogCancelBtn');
194
+ const genericDialogConfirmBtn = document.getElementById('genericDialogConfirmBtn');
188
195
  const directoryPickerModal = document.getElementById('directoryPickerModal');
189
196
  const directoryPickerTitle = document.getElementById('directoryPickerTitle');
190
197
  const directoryPickerTip = document.getElementById('directoryPickerTip');
@@ -1336,7 +1343,7 @@
1336
1343
  }
1337
1344
  const detail = await ensureActiveSessionDetail();
1338
1345
  if (!detail) {
1339
- alert(state.sessionDetailError || '当前会话详情暂时不可用');
1346
+ await showNotice(state.sessionDetailError || '当前会话详情暂时不可用');
1340
1347
  return;
1341
1348
  }
1342
1349
  state.agentTemplateModalOpen = true;
@@ -1606,7 +1613,7 @@
1606
1613
  }
1607
1614
  const detail = await ensureActiveSessionDetail();
1608
1615
  if (!detail) {
1609
- alert(state.sessionDetailError || '当前会话详情暂时不可用');
1616
+ await showNotice(state.sessionDetailError || '当前会话详情暂时不可用');
1610
1617
  return;
1611
1618
  }
1612
1619
  state.modelModalOpen = true;
@@ -2641,7 +2648,7 @@
2641
2648
  state.configModalOpen = true;
2642
2649
  setModalVisible(configModal, true);
2643
2650
  } catch (e) {
2644
- alert(e.message);
2651
+ await showNotice(e.message);
2645
2652
  } finally {
2646
2653
  state.configLoading = false;
2647
2654
  syncUi();
@@ -2696,7 +2703,7 @@
2696
2703
  state.createModalOpen = true;
2697
2704
  setModalVisible(createModal, true);
2698
2705
  } catch (e) {
2699
- alert(e.message);
2706
+ await showNotice(e.message);
2700
2707
  } finally {
2701
2708
  state.createLoading = false;
2702
2709
  syncUi();
@@ -2828,7 +2835,7 @@
2828
2835
  if (picker.loading || !picker.currentPath) {
2829
2836
  return;
2830
2837
  }
2831
- const input = window.prompt('请输入新目录名称');
2838
+ const input = await showPrompt('请输入新目录名称');
2832
2839
  const name = String(input || '').trim();
2833
2840
  if (!name) {
2834
2841
  return;
@@ -2934,8 +2941,8 @@
2934
2941
  Promise.all([
2935
2942
  loadMessagesForSession(sessionName),
2936
2943
  loadSessionDetailForSession(sessionName)
2937
- ]).catch(function (e) {
2938
- alert(e.message);
2944
+ ]).catch(async function (e) {
2945
+ await showNotice(e.message);
2939
2946
  });
2940
2947
  }
2941
2948
 
@@ -2961,7 +2968,7 @@
2961
2968
  closeMobileSessionPanel();
2962
2969
  }
2963
2970
  } catch (e) {
2964
- alert(e.message);
2971
+ await showNotice(e.message);
2965
2972
  } finally {
2966
2973
  state.creatingAgent = false;
2967
2974
  syncUi();
@@ -3132,20 +3139,56 @@
3132
3139
  panel.className = 'tree-node-menu-panel';
3133
3140
  panel.hidden = true;
3134
3141
 
3142
+ // 面板展开时挂到 body 上用 position:fixed 定位,避免被 #sessionList 之类的
3143
+ // overflow:auto 滚动容器裁切(面板本来是靠 .tree-node-menu 的 position:absolute
3144
+ // 定位在卡片内的,一旦触发按钮接近滚动容器底部,面板就会被截断)。
3145
+ // 调用时 panel 必须已经是可见状态(display 不为 none),否则 offsetHeight 测不出真实高度。
3146
+ function positionPanel() {
3147
+ const rect = trigger.getBoundingClientRect();
3148
+ panel.style.position = 'fixed';
3149
+ panel.style.left = 'auto';
3150
+ panel.style.right = (window.innerWidth - rect.right) + 'px';
3151
+ panel.style.top = (rect.bottom + 4) + 'px';
3152
+ panel.style.bottom = 'auto';
3153
+ const panelHeight = panel.offsetHeight;
3154
+ if (rect.bottom + 4 + panelHeight > window.innerHeight) {
3155
+ // 下方空间不够时翻转到触发按钮上方展开,避免超出视口底部
3156
+ panel.style.top = 'auto';
3157
+ panel.style.bottom = (window.innerHeight - rect.top + 4) + 'px';
3158
+ }
3159
+ }
3160
+
3135
3161
  function close() {
3136
3162
  panel.hidden = true;
3137
3163
  trigger.setAttribute('aria-expanded', 'false');
3138
3164
  wrap.classList.remove('is-open');
3165
+ if (panel.parentNode === document.body) {
3166
+ wrap.appendChild(panel);
3167
+ }
3168
+ panel.style.position = '';
3169
+ panel.style.top = '';
3170
+ panel.style.bottom = '';
3171
+ panel.style.left = '';
3172
+ panel.style.right = '';
3139
3173
  }
3140
3174
 
3141
3175
  trigger.addEventListener('click', function (event) {
3142
3176
  event.stopPropagation();
3143
3177
  if (panel.hidden) {
3144
3178
  closeOpenTreeNodeMenu();
3179
+ document.body.appendChild(panel);
3145
3180
  panel.hidden = false;
3181
+ positionPanel();
3146
3182
  trigger.setAttribute('aria-expanded', 'true');
3147
3183
  wrap.classList.add('is-open');
3148
- openTreeNodeMenu = { wrap, close };
3184
+ openTreeNodeMenu = {
3185
+ close: close,
3186
+ containsTarget: function (target) {
3187
+ // 面板展开时是 body 的直接子节点,不再是 wrap 的后代,
3188
+ // “点击外部关闭”判断要把它也算进来
3189
+ return wrap.contains(target) || panel.contains(target);
3190
+ }
3191
+ };
3149
3192
  } else {
3150
3193
  close();
3151
3194
  openTreeNodeMenu = null;
@@ -3205,6 +3248,83 @@
3205
3248
  });
3206
3249
  }
3207
3250
 
3251
+ // 原生 alert/confirm/prompt 是同步阻塞的,会卡住整个页面(动画、其它交互全部停摆)。
3252
+ // 这里用一个通用的 Promise 化小弹窗承载 notice/confirm/prompt 三种形态,取代它们。
3253
+ function openGenericDialog(config) {
3254
+ const opts = config && typeof config === 'object' ? config : {};
3255
+ const mode = opts.mode === 'prompt' || opts.mode === 'confirm' ? opts.mode : 'notice';
3256
+ if (!genericDialogModal) {
3257
+ if (mode === 'prompt') {
3258
+ return Promise.resolve(window.prompt(opts.message || '', opts.defaultValue || ''));
3259
+ }
3260
+ if (mode === 'confirm') {
3261
+ return Promise.resolve(confirm(opts.message || ''));
3262
+ }
3263
+ alert(opts.message || '');
3264
+ return Promise.resolve();
3265
+ }
3266
+ return new Promise(function (resolve) {
3267
+ genericDialogTitle.textContent = opts.title
3268
+ || (mode === 'notice' ? '提示' : (mode === 'confirm' ? '请确认' : '请输入'));
3269
+ genericDialogMessage.textContent = opts.message || '';
3270
+ genericDialogCancelBtn.hidden = mode === 'notice';
3271
+ genericDialogInput.hidden = mode !== 'prompt';
3272
+ genericDialogInput.value = mode === 'prompt' ? String(opts.defaultValue || '') : '';
3273
+ genericDialogConfirmBtn.textContent = mode === 'notice' ? '知道了' : '确定';
3274
+
3275
+ let settled = false;
3276
+ function resolveValue() {
3277
+ if (mode === 'confirm') return true;
3278
+ if (mode === 'prompt') return genericDialogInput.value;
3279
+ return undefined;
3280
+ }
3281
+ function cancelValue() {
3282
+ if (mode === 'confirm') return false;
3283
+ if (mode === 'prompt') return null;
3284
+ return undefined;
3285
+ }
3286
+ function finish(value) {
3287
+ if (settled) return;
3288
+ settled = true;
3289
+ setModalVisible(genericDialogModal, false);
3290
+ genericDialogConfirmBtn.removeEventListener('click', onConfirm);
3291
+ genericDialogCancelBtn.removeEventListener('click', onCancel);
3292
+ genericDialogCloseBtn.removeEventListener('click', onCancel);
3293
+ genericDialogInput.removeEventListener('keydown', onInputKeydown);
3294
+ resolve(value);
3295
+ }
3296
+ function onConfirm() { finish(resolveValue()); }
3297
+ function onCancel() { finish(cancelValue()); }
3298
+ function onInputKeydown(event) {
3299
+ if (event.key === 'Enter') {
3300
+ event.preventDefault();
3301
+ onConfirm();
3302
+ }
3303
+ }
3304
+ genericDialogConfirmBtn.addEventListener('click', onConfirm);
3305
+ genericDialogCancelBtn.addEventListener('click', onCancel);
3306
+ genericDialogCloseBtn.addEventListener('click', onCancel);
3307
+ genericDialogInput.addEventListener('keydown', onInputKeydown);
3308
+ setModalVisible(genericDialogModal, true);
3309
+ if (mode === 'prompt') {
3310
+ genericDialogInput.focus();
3311
+ genericDialogInput.select();
3312
+ }
3313
+ });
3314
+ }
3315
+
3316
+ function showNotice(message, title) {
3317
+ return openGenericDialog({ mode: 'notice', message: message, title: title });
3318
+ }
3319
+
3320
+ function showConfirm(message, title) {
3321
+ return openGenericDialog({ mode: 'confirm', message: message, title: title });
3322
+ }
3323
+
3324
+ function showPrompt(message, defaultValue, title) {
3325
+ return openGenericDialog({ mode: 'prompt', message: message, defaultValue: defaultValue, title: title });
3326
+ }
3327
+
3208
3328
  async function removeContainerByName(containerName) {
3209
3329
  const target = String(containerName || '').trim();
3210
3330
  if (!target) return;
@@ -3230,7 +3350,7 @@
3230
3350
  reloadMessages: wasActiveContainer
3231
3351
  });
3232
3352
  } catch (e) {
3233
- alert(e.message);
3353
+ await showNotice(e.message);
3234
3354
  }
3235
3355
  }
3236
3356
 
@@ -3260,7 +3380,7 @@
3260
3380
  reloadMessages: isActive
3261
3381
  });
3262
3382
  } catch (e) {
3263
- alert(e.message);
3383
+ await showNotice(e.message);
3264
3384
  }
3265
3385
  }
3266
3386
 
@@ -3344,6 +3464,9 @@
3344
3464
  if (!sessionBreadcrumb) {
3345
3465
  return;
3346
3466
  }
3467
+ // 展开中的菜单面板会被挂到 body 上(见 createTreeNodeMenu),
3468
+ // 这里要重建 DOM 之前先关掉,否则旧面板会变成脱离 wrap 的孤儿节点
3469
+ closeOpenTreeNodeMenu();
3347
3470
  sessionBreadcrumb.innerHTML = '';
3348
3471
 
3349
3472
  const rootCrumb = document.createElement('button');
@@ -3431,6 +3554,7 @@
3431
3554
  }
3432
3555
 
3433
3556
  function renderContainerLevel(groups) {
3557
+ closeOpenTreeNodeMenu();
3434
3558
  sessionList.innerHTML = '';
3435
3559
  if (!groups.length) {
3436
3560
  const empty = document.createElement('div');
@@ -3445,10 +3569,10 @@
3445
3569
  kind: 'container',
3446
3570
  className: status.tone === 'history' ? 'tree-node-tone-history' : '',
3447
3571
  title: containerGroup.containerRemark || containerGroup.containerName,
3448
- meta: containerGroup.containerRemark ? containerGroup.containerName : '',
3572
+ meta: containerGroup.containerRemark ? containerGroup.containerName : containerGroup.hostPath,
3449
3573
  statusLabel: status.label,
3450
3574
  statusTone: status.tone,
3451
- subMeta: containerGroup.hostPath,
3575
+ subMeta: containerGroup.containerRemark ? containerGroup.hostPath : '',
3452
3576
  hasActive: containerContainsActiveSession(containerGroup),
3453
3577
  onClick: function () {
3454
3578
  navigateTo('agents', { container: containerGroup.containerName });
@@ -3493,7 +3617,7 @@
3493
3617
 
3494
3618
  async function editContainerRemark(containerGroup) {
3495
3619
  const current = containerGroup.containerRemark || '';
3496
- const input = window.prompt('设置容器备注(留空清除备注)', current);
3620
+ const input = await showPrompt('设置容器备注(留空清除备注)', current);
3497
3621
  if (input === null) {
3498
3622
  return;
3499
3623
  }
@@ -3505,13 +3629,13 @@
3505
3629
  });
3506
3630
  await refreshSessionsSilent({ preferredName: state.active });
3507
3631
  } catch (e) {
3508
- alert(e.message);
3632
+ await showNotice(e.message);
3509
3633
  }
3510
3634
  }
3511
3635
 
3512
3636
  async function editAgentRemark(session) {
3513
3637
  const current = session.agentRemark || '';
3514
- const input = window.prompt('设置 AGENT 备注(留空清除备注)', current);
3638
+ const input = await showPrompt('设置 AGENT 备注(留空清除备注)', current);
3515
3639
  if (input === null) {
3516
3640
  return;
3517
3641
  }
@@ -3523,11 +3647,12 @@
3523
3647
  });
3524
3648
  await refreshSessionsSilent({ preferredName: state.active });
3525
3649
  } catch (e) {
3526
- alert(e.message);
3650
+ await showNotice(e.message);
3527
3651
  }
3528
3652
  }
3529
3653
 
3530
3654
  function renderAgentLevel(containerGroup) {
3655
+ closeOpenTreeNodeMenu();
3531
3656
  sessionList.innerHTML = '';
3532
3657
  const allSessions = containerGroup && Array.isArray(containerGroup.sessions) ? containerGroup.sessions : [];
3533
3658
  const sessions = allSessions.filter(function (session) { return !session.synthetic; });
@@ -3762,7 +3887,7 @@
3762
3887
  closeCloneModal();
3763
3888
  await loadSessions(data.name);
3764
3889
  if (data.resumeMayFail) {
3765
- alert('复制完成。注意:AGENT 对话历史已复制,但底层 CLI 会话续接可能失败,如遇失败请以新对话继续。');
3890
+ await showNotice('复制完成。注意:AGENT 对话历史已复制,但底层 CLI 会话续接可能失败,如遇失败请以新对话继续。');
3766
3891
  }
3767
3892
  } catch (e) {
3768
3893
  state.cloneModal.submitting = false;
@@ -4467,8 +4592,8 @@
4467
4592
  if (agentTemplateBtn) {
4468
4593
  agentTemplateBtn.addEventListener('click', function () {
4469
4594
  closeComposerOptionsMenu();
4470
- openAgentTemplateModal().catch(function (e) {
4471
- alert(e && e.message ? e.message : '加载 Agent 模板失败');
4595
+ openAgentTemplateModal().catch(async function (e) {
4596
+ await showNotice(e && e.message ? e.message : '加载 Agent 模板失败');
4472
4597
  });
4473
4598
  });
4474
4599
  }
@@ -4476,8 +4601,8 @@
4476
4601
  if (activityModelChip) {
4477
4602
  activityModelChip.addEventListener('click', function () {
4478
4603
  closeComposerOptionsMenu();
4479
- openModelModal().catch(function (e) {
4480
- alert(e && e.message ? e.message : '加载模型信息失败');
4604
+ openModelModal().catch(async function (e) {
4605
+ await showNotice(e && e.message ? e.message : '加载模型信息失败');
4481
4606
  });
4482
4607
  });
4483
4608
  }
@@ -4711,7 +4836,7 @@
4711
4836
  body: JSON.stringify({})
4712
4837
  });
4713
4838
  } catch (e) {
4714
- alert(e.message);
4839
+ await showNotice(e.message);
4715
4840
  state.agentRun.stopping = false;
4716
4841
  syncUi();
4717
4842
  }
@@ -4773,7 +4898,7 @@
4773
4898
  if (mode === 'agent') {
4774
4899
  const recovered = await recoverAgentRunFromServer(submitSession);
4775
4900
  if (!recovered && document.visibilityState !== 'hidden') {
4776
- alert(e.message);
4901
+ await showNotice(e.message);
4777
4902
  }
4778
4903
  } else {
4779
4904
  if (state.active === submitSession) {
@@ -4782,7 +4907,7 @@
4782
4907
  });
4783
4908
  renderMessages(state.messages);
4784
4909
  }
4785
- alert(e.message);
4910
+ await showNotice(e.message);
4786
4911
  }
4787
4912
  } finally {
4788
4913
  state.sending = false;
@@ -4973,6 +5098,14 @@
4973
5098
  });
4974
5099
  }
4975
5100
 
5101
+ if (genericDialogModal) {
5102
+ genericDialogModal.addEventListener('click', function (event) {
5103
+ if (event.target === genericDialogModal) {
5104
+ genericDialogCloseBtn.click();
5105
+ }
5106
+ });
5107
+ }
5108
+
4976
5109
  if (directoryPickerModal) {
4977
5110
  directoryPickerModal.addEventListener('click', function (event) {
4978
5111
  if (event.target === directoryPickerModal && !state.directoryPicker.loading) {
@@ -5069,6 +5202,12 @@
5069
5202
  if (event.key === 'Escape' && openTreeNodeMenu) {
5070
5203
  closeOpenTreeNodeMenu();
5071
5204
  }
5205
+ if (event.key === 'Escape' && removeConfirmModal && !removeConfirmModal.hidden) {
5206
+ removeConfirmCancelBtn.click();
5207
+ }
5208
+ if (event.key === 'Escape' && genericDialogModal && !genericDialogModal.hidden) {
5209
+ genericDialogCloseBtn.click();
5210
+ }
5072
5211
  });
5073
5212
 
5074
5213
  function onLayoutMediaChange() {
@@ -5126,7 +5265,7 @@
5126
5265
  if (composerOptionsPanel && composerOptionsPanel.contains(target)) return;
5127
5266
  closeComposerOptionsMenu();
5128
5267
  }
5129
- if (openTreeNodeMenu && !openTreeNodeMenu.wrap.contains(target)) {
5268
+ if (openTreeNodeMenu && !openTreeNodeMenu.containsTarget(target)) {
5130
5269
  closeOpenTreeNodeMenu();
5131
5270
  }
5132
5271
  });
@@ -5158,8 +5297,10 @@
5158
5297
  root: filesPanel,
5159
5298
  api,
5160
5299
  onError: function (message) {
5161
- alert(message);
5162
- }
5300
+ showNotice(message);
5301
+ },
5302
+ confirmFn: showConfirm,
5303
+ promptFn: showPrompt
5163
5304
  });
5164
5305
  }
5165
5306
 
@@ -5173,7 +5314,7 @@
5173
5314
  setMobileSessionPanel(false);
5174
5315
  document.body.classList.add('agent-mode');
5175
5316
  syncUi();
5176
- loadSessions().catch(function (e) {
5177
- alert(e.message);
5317
+ loadSessions().catch(async function (e) {
5318
+ await showNotice(e.message);
5178
5319
  });
5179
5320
  })();
@@ -75,6 +75,14 @@
75
75
  const onError = options && typeof options.onError === 'function'
76
76
  ? options.onError
77
77
  : function (message) { window.alert(message); };
78
+ // confirmFn/promptFn 可由调用方注入非阻塞的自定义弹窗,缺省退回原生 confirm/prompt
79
+ // (和上面 onError 的兜底写法保持一致)。
80
+ const confirmFn = options && typeof options.confirmFn === 'function'
81
+ ? options.confirmFn
82
+ : function (message) { return Promise.resolve(window.confirm(message)); };
83
+ const promptFn = options && typeof options.promptFn === 'function'
84
+ ? options.promptFn
85
+ : function (message, defaultValue) { return Promise.resolve(window.prompt(message, defaultValue)); };
78
86
  if (!root || typeof api !== 'function') {
79
87
  return {
80
88
  sync: function () {}
@@ -99,6 +107,7 @@
99
107
  </aside>
100
108
  <section class="files-preview">
101
109
  <header class="files-preview-head">
110
+ <button type="button" class="secondary files-back-btn" data-action="back-to-list">← 返回列表</button>
102
111
  <div class="files-preview-head-main">
103
112
  <div class="files-preview-title" data-role="preview-title">未选择文件</div>
104
113
  <div class="files-preview-meta" data-role="preview-meta">请选择左侧文件或目录</div>
@@ -120,10 +129,12 @@
120
129
  const previewTitleNode = root.querySelector('[data-role="preview-title"]');
121
130
  const previewMetaNode = root.querySelector('[data-role="preview-meta"]');
122
131
  const previewBodyNode = root.querySelector('[data-role="preview-body"]');
132
+ const filesBrowserNode = root.querySelector('.files-browser');
123
133
  const visitBtn = root.querySelector('[data-action="visit"]');
124
134
  const mkdirBtn = root.querySelector('[data-action="mkdir"]');
125
135
  const saveBtn = root.querySelector('[data-action="save"]');
126
136
  const toggleMarkdownViewBtn = root.querySelector('[data-action="toggle-markdown-view"]');
137
+ const backToListBtn = root.querySelector('[data-action="back-to-list"]');
127
138
 
128
139
  const state = {
129
140
  visible: false,
@@ -147,9 +158,17 @@
147
158
  editorHost: null,
148
159
  previewReadOnly: true,
149
160
  previewDirty: false,
150
- markdownViewMode: 'rendered'
161
+ markdownViewMode: 'rendered',
162
+ mobileActivePane: 'list'
151
163
  };
152
164
 
165
+ function setMobilePane(pane) {
166
+ state.mobileActivePane = pane === 'preview' ? 'preview' : 'list';
167
+ if (filesBrowserNode) {
168
+ filesBrowserNode.setAttribute('data-mobile-pane', state.mobileActivePane);
169
+ }
170
+ }
171
+
153
172
  function setStatus(text) {
154
173
  if (statusNode) {
155
174
  statusNode.textContent = String(text || '').trim() || '就绪';
@@ -351,12 +370,14 @@
351
370
  listNode.innerHTML = '';
352
371
 
353
372
  if (!state.sessionName) {
373
+ setMobilePane('list');
354
374
  listNode.innerHTML = '<div class="files-empty">请选择左侧会话后再浏览容器文件。</div>';
355
375
  renderPreviewEmpty('未选择会话', '请选择左侧会话后再浏览容器文件。');
356
376
  setStatus('未选择会话');
357
377
  return;
358
378
  }
359
379
  if (state.historyOnly) {
380
+ setMobilePane('list');
360
381
  listNode.innerHTML = '<div class="files-empty">当前会话只有历史记录,没有可访问的运行中容器。</div>';
361
382
  renderPreviewEmpty('容器不可用', '当前会话只有历史记录,没有可访问的运行中容器。');
362
383
  setStatus('容器不可用');
@@ -421,6 +442,7 @@
421
442
  state.pathDraft = pathText;
422
443
  state.selectedPath = '';
423
444
  state.selectedEntry = null;
445
+ setMobilePane('list');
424
446
  renderList();
425
447
  try {
426
448
  const payload = await api('/api/sessions/' + encodeURIComponent(state.sessionName) + '/fs/list?path=' + encodeURIComponent(pathText));
@@ -455,7 +477,7 @@
455
477
  const fileSize = Number(selectedEntry && selectedEntry.size);
456
478
  const requiresReadonlyConfirm = Number.isFinite(fileSize) && fileSize >= FILE_EDIT_MAX_BYTES;
457
479
  if (requiresReadonlyConfirm) {
458
- const yes = window.confirm(`文件较大(${formatBytes(fileSize)}),继续后将以只读方式全量预览,无法保存。是否继续?`);
480
+ const yes = await confirmFn(`文件较大(${formatBytes(fileSize)}),继续后将以只读方式全量预览,无法保存。是否继续?`);
459
481
  if (!yes) {
460
482
  return;
461
483
  }
@@ -466,6 +488,7 @@
466
488
  state.selectedPath = pathText;
467
489
  state.selectedEntry = selectedEntry;
468
490
  state.markdownViewMode = 'rendered';
491
+ setMobilePane('preview');
469
492
  renderList();
470
493
  renderPreviewEmpty(pathText, '正在读取文件内容...');
471
494
  try {
@@ -553,7 +576,7 @@
553
576
  if (!state.sessionName || state.historyOnly === true || state.loadingList || state.loadingFile) {
554
577
  return;
555
578
  }
556
- const input = window.prompt('请输入新目录名称');
579
+ const input = await promptFn('请输入新目录名称');
557
580
  const name = String(input || '').trim();
558
581
  if (!name) {
559
582
  return;
@@ -601,6 +624,7 @@
601
624
  state.entries = [];
602
625
  state.selectedPath = '';
603
626
  state.selectedEntry = null;
627
+ setMobilePane('list');
604
628
  renderPreviewEmpty('未选择文件', '请选择左侧文件进行预览。');
605
629
  } else if (containerPathChanged) {
606
630
  state.containerPath = nextContainerPath;
@@ -660,6 +684,13 @@
660
684
  });
661
685
  }
662
686
 
687
+ if (backToListBtn) {
688
+ backToListBtn.addEventListener('click', function () {
689
+ setMobilePane('list');
690
+ });
691
+ }
692
+
693
+ setMobilePane('list');
663
694
  renderList();
664
695
 
665
696
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "6.2.11",
3
+ "version": "6.2.15",
4
4
  "imageVersion": "1.9.1-common",
5
5
  "playwrightCliVersion": "0.1.18",
6
6
  "description": "AI Agent CLI Security Sandbox for Docker and Podman",