@xcanwin/manyoyo 6.1.1 → 6.1.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.
@@ -14,9 +14,12 @@
14
14
  --line: #d9c7ad;
15
15
  --line-strong: #b59263;
16
16
  --line-soft: rgba(181, 146, 99, 0.26);
17
+ --line-medium: rgba(181, 146, 99, 0.4);
17
18
  --tree-guide: rgba(181, 146, 99, 0.44);
18
19
  --tree-guide-soft: rgba(181, 146, 99, 0.44);
19
20
 
21
+ --surface-panel: linear-gradient(180deg, rgba(255, 255, 255, 0.88) 0%, rgba(252, 246, 236, 0.88) 100%);
22
+
20
23
  --text: #1f1a14;
21
24
  --muted: #6a5f52;
22
25
 
@@ -594,7 +597,7 @@ textarea:focus-visible {
594
597
  }
595
598
 
596
599
  .session-status {
597
- border-radius: 99px;
600
+ border-radius: 999px;
598
601
  padding: 2px 8px;
599
602
  font-weight: 700;
600
603
  font-size: 11px;
@@ -1005,7 +1008,7 @@ button.tree-toggle:active {
1005
1008
  align-items: flex-start;
1006
1009
  gap: 12px;
1007
1010
  padding: 6px 8px 12px;
1008
- border-bottom: 1px solid rgba(181, 146, 99, 0.48);
1011
+ border-bottom: 1px solid var(--line-medium);
1009
1012
  }
1010
1013
 
1011
1014
  .header-main {
@@ -1062,6 +1065,56 @@ button.tree-toggle:active {
1062
1065
  border-color: var(--accent-strong);
1063
1066
  }
1064
1067
 
1068
+ .workspace-switcher {
1069
+ position: relative;
1070
+ display: inline-flex;
1071
+ }
1072
+
1073
+ .workspace-switcher-btn {
1074
+ width: 34px;
1075
+ height: 34px;
1076
+ padding: 0;
1077
+ display: inline-flex;
1078
+ align-items: center;
1079
+ justify-content: center;
1080
+ }
1081
+
1082
+ .workspace-switcher-btn svg {
1083
+ width: 16px;
1084
+ height: 16px;
1085
+ fill: currentColor;
1086
+ }
1087
+
1088
+ .workspace-switcher-btn.is-active {
1089
+ color: #ffffff;
1090
+ background: var(--accent);
1091
+ border-color: var(--accent-strong);
1092
+ }
1093
+
1094
+ .workspace-switcher-panel {
1095
+ display: none;
1096
+ position: absolute;
1097
+ top: calc(100% + 8px);
1098
+ left: 0;
1099
+ z-index: 8;
1100
+ width: max-content;
1101
+ padding: 8px;
1102
+ border: 1px solid var(--line);
1103
+ border-radius: 10px;
1104
+ background: #fffaf2;
1105
+ box-shadow: var(--shadow-strong);
1106
+ flex-direction: column;
1107
+ gap: 6px;
1108
+ }
1109
+
1110
+ body.workspace-switcher-open .workspace-switcher-panel {
1111
+ display: flex;
1112
+ }
1113
+
1114
+ .workspace-switcher-panel button {
1115
+ text-align: left;
1116
+ }
1117
+
1065
1118
  .header-actions {
1066
1119
  display: none;
1067
1120
  position: absolute;
@@ -1120,8 +1173,7 @@ body.mobile-actions-open .header-actions {
1120
1173
  flex-direction: column;
1121
1174
  gap: 14px;
1122
1175
  scroll-behavior: smooth;
1123
- background:
1124
- linear-gradient(180deg, rgba(255, 255, 255, 0.88) 0%, rgba(252, 246, 236, 0.88) 100%);
1176
+ background: var(--surface-panel);
1125
1177
  }
1126
1178
 
1127
1179
  #terminalPanel {
@@ -1139,8 +1191,7 @@ body.mobile-actions-open .header-actions {
1139
1191
  border: 1px solid var(--line);
1140
1192
  border-radius: 14px;
1141
1193
  overflow: hidden;
1142
- background:
1143
- linear-gradient(180deg, rgba(255, 255, 255, 0.88) 0%, rgba(252, 246, 236, 0.88) 100%);
1194
+ background: var(--surface-panel);
1144
1195
  }
1145
1196
 
1146
1197
  .files-browser {
@@ -1382,8 +1433,7 @@ body.mobile-actions-open .header-actions {
1382
1433
  padding: 0;
1383
1434
  border: 1px solid var(--line);
1384
1435
  border-radius: 14px;
1385
- background:
1386
- linear-gradient(180deg, rgba(255, 255, 255, 0.88) 0%, rgba(252, 246, 236, 0.88) 100%);
1436
+ background: var(--surface-panel);
1387
1437
  }
1388
1438
 
1389
1439
  .inspector-stack {
@@ -1657,6 +1707,47 @@ body.terminal-mode .composer {
1657
1707
  font-weight: 700;
1658
1708
  }
1659
1709
 
1710
+ .msg-actions {
1711
+ display: flex;
1712
+ gap: 6px;
1713
+ opacity: 0;
1714
+ transition: opacity 140ms ease;
1715
+ }
1716
+
1717
+ .msg:hover .msg-actions,
1718
+ .msg:focus-within .msg-actions {
1719
+ opacity: 1;
1720
+ }
1721
+
1722
+ @media (hover: none) {
1723
+ .msg-actions {
1724
+ opacity: 1;
1725
+ }
1726
+ }
1727
+
1728
+ .msg-copy-btn {
1729
+ border: 1px solid var(--line);
1730
+ border-radius: 10px;
1731
+ background: var(--panel-soft);
1732
+ color: var(--muted);
1733
+ font-size: 11px;
1734
+ font-weight: 600;
1735
+ padding: 2px 8px;
1736
+ cursor: pointer;
1737
+ transition: color 140ms ease, border-color 140ms ease, background-color 140ms ease;
1738
+ }
1739
+
1740
+ .msg-copy-btn:hover {
1741
+ color: var(--accent-strong);
1742
+ border-color: var(--line-strong);
1743
+ }
1744
+
1745
+ .msg-copy-btn.is-copied {
1746
+ color: var(--subaccent-strong);
1747
+ border-color: var(--subaccent);
1748
+ background: var(--subaccent-soft);
1749
+ }
1750
+
1660
1751
  .msg-exit {
1661
1752
  font-family: var(--font-mono);
1662
1753
  font-size: 11px;
@@ -1739,6 +1830,52 @@ body.command-mode .msg.origin-agent .bubble {
1739
1830
  gap: 8px;
1740
1831
  }
1741
1832
 
1833
+ .trace-flow-toggle {
1834
+ border: 1px solid rgba(181, 146, 99, 0.26);
1835
+ border-radius: 10px;
1836
+ background: rgba(255, 250, 242, 0.7);
1837
+ overflow: hidden;
1838
+ }
1839
+
1840
+ .trace-flow-toggle[open] {
1841
+ background: transparent;
1842
+ border-color: transparent;
1843
+ }
1844
+
1845
+ .trace-flow-toggle-summary {
1846
+ list-style: none;
1847
+ display: flex;
1848
+ align-items: center;
1849
+ gap: 6px;
1850
+ padding: 8px 10px;
1851
+ cursor: pointer;
1852
+ font-size: 12px;
1853
+ color: var(--muted);
1854
+ }
1855
+
1856
+ .trace-flow-toggle-summary::-webkit-details-marker {
1857
+ display: none;
1858
+ }
1859
+
1860
+ .trace-flow-toggle-summary::before {
1861
+ content: '';
1862
+ width: 8px;
1863
+ height: 8px;
1864
+ flex: 0 0 auto;
1865
+ border-right: 1.6px solid currentColor;
1866
+ border-bottom: 1.6px solid currentColor;
1867
+ transform: rotate(-45deg);
1868
+ transition: transform 140ms ease;
1869
+ }
1870
+
1871
+ .trace-flow-toggle[open] > .trace-flow-toggle-summary::before {
1872
+ transform: rotate(45deg);
1873
+ }
1874
+
1875
+ .trace-flow-toggle > .trace-flow {
1876
+ padding: 0 10px 10px;
1877
+ }
1878
+
1742
1879
  .trace-card {
1743
1880
  border: 1px solid rgba(181, 146, 99, 0.32);
1744
1881
  border-left-width: 3px;
@@ -1861,7 +1998,7 @@ details.trace-card > .trace-card-summary {
1861
1998
  }
1862
1999
 
1863
2000
  .composer {
1864
- border-top: 1px solid rgba(181, 146, 99, 0.45);
2001
+ border-top: 1px solid var(--line-medium);
1865
2002
  margin-top: 12px;
1866
2003
  padding: 12px 8px 4px;
1867
2004
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.32) 0%, rgba(255, 249, 240, 0.78) 100%);
@@ -1872,7 +2009,17 @@ details.trace-card > .trace-card-summary {
1872
2009
  align-items: center;
1873
2010
  justify-content: space-between;
1874
2011
  gap: 10px;
2012
+ max-height: 0;
2013
+ margin-bottom: 0;
2014
+ opacity: 0;
2015
+ overflow: hidden;
2016
+ transition: max-height 180ms ease, opacity 160ms ease, margin-bottom 180ms ease;
2017
+ }
2018
+
2019
+ .composer.is-expanded .composer-toolbar {
2020
+ max-height: 56px;
1875
2021
  margin-bottom: 10px;
2022
+ opacity: 1;
1876
2023
  }
1877
2024
 
1878
2025
  .composer-toolbar-meta {
@@ -2134,21 +2281,30 @@ details.trace-card > .trace-card-summary {
2134
2281
  }
2135
2282
 
2136
2283
  .workbench-tabs {
2137
- display: grid;
2138
- grid-template-columns: repeat(6, minmax(0, 1fr));
2284
+ display: flex;
2139
2285
  gap: 6px;
2140
2286
  }
2141
2287
 
2142
- .workbench-tabs button {
2288
+ .workbench-tabs > #viewActivityBtn {
2289
+ flex: 1;
2143
2290
  padding: 8px 10px;
2144
2291
  font-size: 12px;
2145
2292
  }
2146
2293
 
2294
+ .workspace-switcher-panel {
2295
+ left: auto;
2296
+ right: 0;
2297
+ }
2298
+
2147
2299
  .composer-toolbar {
2148
2300
  flex-direction: column;
2149
2301
  align-items: flex-start;
2150
2302
  }
2151
2303
 
2304
+ .composer.is-expanded .composer-toolbar {
2305
+ max-height: 120px;
2306
+ }
2307
+
2152
2308
  .files-layout {
2153
2309
  grid-template-columns: minmax(0, 1fr);
2154
2310
  grid-template-rows: minmax(180px, 38%) minmax(0, 1fr);
@@ -68,11 +68,24 @@
68
68
  <div id="activeMeta">请选择左侧会话</div>
69
69
  <div class="workbench-tabs" id="workbenchTabs" aria-label="工作台视图">
70
70
  <button type="button" id="viewActivityBtn" class="secondary is-active">活动</button>
71
- <button type="button" id="viewTerminalBtn" class="secondary">终端</button>
72
- <button type="button" id="viewFilesBtn" class="secondary">文件</button>
73
- <button type="button" id="viewDetailBtn" class="secondary">详情</button>
74
- <button type="button" id="viewConfigBtn" class="secondary">配置</button>
75
- <button type="button" id="viewCheckBtn" class="secondary">检查</button>
71
+ <div class="workspace-switcher">
72
+ <button
73
+ type="button"
74
+ id="workspaceSwitcherToggle"
75
+ class="secondary workspace-switcher-btn"
76
+ aria-haspopup="true"
77
+ aria-expanded="false"
78
+ aria-controls="workspaceSwitcherPanel"
79
+ aria-label="更多视图:终端、文件、详情、配置、检查"
80
+ ><svg viewBox="0 0 16 16" aria-hidden="true" focusable="false"><circle cx="3" cy="8" r="1.4"></circle><circle cx="8" cy="8" r="1.4"></circle><circle cx="13" cy="8" r="1.4"></circle></svg></button>
81
+ <div class="workspace-switcher-panel" id="workspaceSwitcherPanel" hidden>
82
+ <button type="button" id="viewTerminalBtn" class="secondary">终端</button>
83
+ <button type="button" id="viewFilesBtn" class="secondary">文件</button>
84
+ <button type="button" id="viewDetailBtn" class="secondary">详情</button>
85
+ <button type="button" id="viewConfigBtn" class="secondary">配置</button>
86
+ <button type="button" id="viewCheckBtn" class="secondary">检查</button>
87
+ </div>
88
+ </div>
76
89
  </div>
77
90
  </div>
78
91
  </header>
@@ -306,6 +319,7 @@
306
319
  <script src="/app/vendor/xterm-addon-fit.js"></script>
307
320
  <script src="/app/vendor/marked.min.js"></script>
308
321
  <script src="/app/frontend/markdown-renderer.js"></script>
322
+ <script src="/app/frontend/chat-behavior.js"></script>
309
323
  <script src="/app/frontend/codemirror.bundle.js"></script>
310
324
  <script src="/app/frontend/file-browser.js"></script>
311
325
  <script src="/app/frontend/app.js"></script>
@@ -48,6 +48,7 @@
48
48
  loadingSessionDetail: false,
49
49
  mobileSidebarOpen: false,
50
50
  mobileActionsOpen: false,
51
+ workspaceSwitcherOpen: false,
51
52
  configModalOpen: false,
52
53
  createModalOpen: false,
53
54
  agentTemplateModalOpen: false,
@@ -125,6 +126,8 @@
125
126
  const mobileActionsToggle = document.getElementById('mobileActionsToggle');
126
127
  const headerActions = document.getElementById('headerActions');
127
128
  const viewActivityBtn = document.getElementById('viewActivityBtn');
129
+ const workspaceSwitcherToggle = document.getElementById('workspaceSwitcherToggle');
130
+ const workspaceSwitcherPanel = document.getElementById('workspaceSwitcherPanel');
128
131
  const viewTerminalBtn = document.getElementById('viewTerminalBtn');
129
132
  const viewFilesBtn = document.getElementById('viewFilesBtn');
130
133
  const viewDetailBtn = document.getElementById('viewDetailBtn');
@@ -318,6 +321,40 @@
318
321
  bubble.appendChild(pre);
319
322
  }
320
323
 
324
+ function copyTextToClipboard(text) {
325
+ if (navigator.clipboard && typeof navigator.clipboard.writeText === 'function') {
326
+ return navigator.clipboard.writeText(text);
327
+ }
328
+ return Promise.reject(new Error('clipboard unavailable'));
329
+ }
330
+
331
+ function appendMessageCopyActions(meta, items) {
332
+ const actions = document.createElement('div');
333
+ actions.className = 'msg-actions';
334
+ items.forEach(function (item) {
335
+ const btn = document.createElement('button');
336
+ btn.type = 'button';
337
+ btn.className = 'msg-copy-btn';
338
+ btn.textContent = item.label;
339
+ btn.addEventListener('click', function () {
340
+ const text = String(item.getText() || '');
341
+ if (!text) {
342
+ return;
343
+ }
344
+ copyTextToClipboard(text).then(function () {
345
+ btn.textContent = '已复制';
346
+ btn.classList.add('is-copied');
347
+ window.setTimeout(function () {
348
+ btn.textContent = item.label;
349
+ btn.classList.remove('is-copied');
350
+ }, 1200);
351
+ }).catch(function () {});
352
+ });
353
+ actions.appendChild(btn);
354
+ });
355
+ meta.appendChild(actions);
356
+ }
357
+
321
358
  function stringifyPrettyJson(value) {
322
359
  if (value === undefined || value === null) {
323
360
  return '';
@@ -545,6 +582,8 @@
545
582
  return card;
546
583
  }
547
584
 
585
+ const traceFlowExpandedState = new Map();
586
+
548
587
  function appendStructuredTraceContent(bubble, message) {
549
588
  bubble.classList.add('trace-bubble');
550
589
  const container = document.createElement('div');
@@ -555,10 +594,33 @@
555
594
  buildStructuredTraceResidualLines(message).forEach(function (line) {
556
595
  flow.appendChild(createResidualTraceCard(line));
557
596
  });
558
- (Array.isArray(message && message.traceEvents) ? message.traceEvents : []).forEach(function (traceEvent) {
597
+ const traceEvents = Array.isArray(message && message.traceEvents) ? message.traceEvents : [];
598
+ traceEvents.forEach(function (traceEvent) {
559
599
  flow.appendChild(createTraceEventCard(traceEvent));
560
600
  });
561
- container.appendChild(flow);
601
+
602
+ const messageId = message && message.id ? message.id : '';
603
+ const pending = Boolean(state.agentRun.active && state.agentRun.traceMessageId === messageId);
604
+ const summaryInfo = window.ManyoyoChatBehavior.summarizeTraceFlow(traceEvents, { pending });
605
+ const hasError = traceEvents.some(function (event) {
606
+ return event && event.kind === 'error';
607
+ });
608
+ const defaultOpen = hasError || traceEvents.length === 0;
609
+ const toggle = document.createElement('details');
610
+ toggle.className = 'trace-flow-toggle';
611
+ toggle.open = traceFlowExpandedState.has(messageId) ? traceFlowExpandedState.get(messageId) : defaultOpen;
612
+ toggle.addEventListener('toggle', function () {
613
+ if (messageId) {
614
+ traceFlowExpandedState.set(messageId, toggle.open);
615
+ }
616
+ });
617
+
618
+ const summary = document.createElement('summary');
619
+ summary.className = 'trace-flow-toggle-summary';
620
+ summary.textContent = summaryInfo.label;
621
+ toggle.appendChild(summary);
622
+ toggle.appendChild(flow);
623
+ container.appendChild(toggle);
562
624
 
563
625
  bubble.appendChild(container);
564
626
  }
@@ -1930,6 +1992,21 @@
1930
1992
  setMobileActionsMenu(false);
1931
1993
  }
1932
1994
 
1995
+ function setWorkspaceSwitcherMenu(open) {
1996
+ state.workspaceSwitcherOpen = Boolean(open);
1997
+ document.body.classList.toggle('workspace-switcher-open', state.workspaceSwitcherOpen);
1998
+ if (workspaceSwitcherToggle) {
1999
+ workspaceSwitcherToggle.setAttribute('aria-expanded', state.workspaceSwitcherOpen ? 'true' : 'false');
2000
+ }
2001
+ if (workspaceSwitcherPanel) {
2002
+ workspaceSwitcherPanel.hidden = !state.workspaceSwitcherOpen;
2003
+ }
2004
+ }
2005
+
2006
+ function closeWorkspaceSwitcherMenu() {
2007
+ setWorkspaceSwitcherMenu(false);
2008
+ }
2009
+
1933
2010
  const VIEW_LABELS = {
1934
2011
  activity: '活动',
1935
2012
  terminal: '终端',
@@ -2197,6 +2274,20 @@
2197
2274
  }
2198
2275
  }
2199
2276
 
2277
+ function syncComposerExpanded() {
2278
+ if (!composer || !commandInput) {
2279
+ return;
2280
+ }
2281
+ // 判定条件必须是"焦点是否还在 composer 内部"而不是"是否等于 commandInput":
2282
+ // 点击 Agent/命令 切换或 CLI/模型 按钮时,commandInput 会先 blur,
2283
+ // 若立刻按 commandInput 是否聚焦来收起,会在 mousedown 与 click 之间把工具栏收起,
2284
+ // 导致按钮的点击事件因布局变化而落空(点了没反应)。
2285
+ const focused = composer.contains(document.activeElement);
2286
+ const hasDraft = Boolean(commandInput.value && commandInput.value.trim());
2287
+ const expanded = window.ManyoyoChatBehavior.shouldExpandComposer({ focused, hasDraft });
2288
+ composer.classList.toggle('is-expanded', expanded);
2289
+ }
2290
+
2200
2291
  function syncUi() {
2201
2292
  if (!state.active) {
2202
2293
  activeTitle.textContent = '未选择会话';
@@ -2253,6 +2344,7 @@
2253
2344
  if (viewDetailBtn) viewDetailBtn.classList.toggle('is-active', detailTab);
2254
2345
  if (viewConfigBtn) viewConfigBtn.classList.toggle('is-active', configTab);
2255
2346
  if (viewCheckBtn) viewCheckBtn.classList.toggle('is-active', checkTab);
2347
+ if (workspaceSwitcherToggle) workspaceSwitcherToggle.classList.toggle('is-active', !activityTab);
2256
2348
  if (messagesNode) {
2257
2349
  messagesNode.hidden = !activityTab;
2258
2350
  }
@@ -2310,6 +2402,7 @@
2310
2402
  ? 'Enter 发送提示词 · Shift/Alt + Enter 换行 · 执行中可停止'
2311
2403
  : 'Enter 发送 · Shift/Alt + Enter 换行';
2312
2404
  }
2405
+ syncComposerExpanded();
2313
2406
  if (openCreateBtn) {
2314
2407
  openCreateBtn.disabled = state.createLoading || state.createSubmitting;
2315
2408
  }
@@ -3376,9 +3469,13 @@
3376
3469
  }
3377
3470
 
3378
3471
  function isMessagesNearBottom(thresholdPx) {
3379
- const threshold = Number.isFinite(thresholdPx) ? thresholdPx : 40;
3380
3472
  if (!messagesNode) return true;
3381
- return (messagesNode.scrollHeight - (messagesNode.scrollTop + messagesNode.clientHeight)) <= threshold;
3473
+ return window.ManyoyoChatBehavior.isNearBottom(
3474
+ messagesNode.scrollTop,
3475
+ messagesNode.scrollHeight,
3476
+ messagesNode.clientHeight,
3477
+ thresholdPx
3478
+ );
3382
3479
  }
3383
3480
 
3384
3481
  function scrollMessagesToBottomImmediate() {
@@ -3404,7 +3501,8 @@
3404
3501
  if (msg && msg.id && msg.streamTrace) {
3405
3502
  const content = msg.content ? String(msg.content) : '';
3406
3503
  const timestamp = msg.timestamp ? String(msg.timestamp) : '';
3407
- return `id:${msg.id}|trace|${timestamp}|${content}`;
3504
+ const running = (state.agentRun.active && state.agentRun.traceMessageId === msg.id) ? '1' : '0';
3505
+ return `id:${msg.id}|trace|${timestamp}|${content}|${running}`;
3408
3506
  }
3409
3507
  if (msg && msg.id) {
3410
3508
  return `id:${msg.id}`;
@@ -3498,11 +3596,20 @@
3498
3596
  if (renderedMarkdown) {
3499
3597
  markdownNode.innerHTML = renderedMarkdown;
3500
3598
  bubble.appendChild(markdownNode);
3599
+ appendMessageCopyActions(meta, [
3600
+ { label: '复制文本', getText: function () { return markdownNode.innerText || markdownNode.textContent || ''; } },
3601
+ { label: '复制 Markdown', getText: function () { return msg.content; } }
3602
+ ]);
3501
3603
  } else {
3502
3604
  appendPlainMessageContent(bubble, msg.content);
3503
3605
  }
3504
3606
  } else {
3505
3607
  appendPlainMessageContent(bubble, msg.content);
3608
+ if (msg.role === 'user') {
3609
+ appendMessageCopyActions(meta, [
3610
+ { label: '复制', getText: function () { return msg.content; } }
3611
+ ]);
3612
+ }
3506
3613
  }
3507
3614
 
3508
3615
  row.appendChild(meta);
@@ -3932,7 +4039,7 @@
3932
4039
  traceLines.push(line);
3933
4040
  updateAgentTraceMessageLocal(sessionName, traceMessageId, traceLines.join('\n'), traceEvent);
3934
4041
  if (state.active === sessionName) {
3935
- renderMessages(state.messages, { stickToBottom: true });
4042
+ renderMessages(state.messages);
3936
4043
  }
3937
4044
  }
3938
4045
 
@@ -3971,7 +4078,7 @@
3971
4078
  }
3972
4079
  updateStreamingReplyLocal(sessionName, streamingReplyId, content);
3973
4080
  if (state.active === sessionName) {
3974
- renderMessages(state.messages, { stickToBottom: true });
4081
+ renderMessages(state.messages);
3975
4082
  }
3976
4083
  return;
3977
4084
  }
@@ -4007,6 +4114,9 @@
4007
4114
  }
4008
4115
  }
4009
4116
  finalizeAgentRunState();
4117
+ if (state.active === sessionName) {
4118
+ renderMessages(state.messages);
4119
+ }
4010
4120
  syncUi();
4011
4121
  }
4012
4122
 
@@ -4019,7 +4129,7 @@
4019
4129
 
4020
4130
  appendAssistantMessageLocal(sessionName, finalResult, 'agent');
4021
4131
  if (state.active === sessionName) {
4022
- renderMessages(state.messages, { stickToBottom: true });
4132
+ renderMessages(state.messages);
4023
4133
  }
4024
4134
  bumpSessionMetaAfterSend(sessionName);
4025
4135
  refreshSessionsSilent({ preferredName: sessionName }).catch(function () {
@@ -4285,7 +4395,7 @@
4285
4395
  }
4286
4396
  appendAssistantMessageLocal(submitSession, runResult, mode);
4287
4397
  if (state.active === submitSession) {
4288
- renderMessages(state.messages, { stickToBottom: true });
4398
+ renderMessages(state.messages);
4289
4399
  }
4290
4400
  bumpSessionMetaAfterSend(submitSession);
4291
4401
  refreshSessionsSilent({ preferredName: submitSession }).catch(function () {
@@ -4303,7 +4413,7 @@
4303
4413
  state.messages = state.messages.filter(function (message) {
4304
4414
  return !(message && message.id === pendingMessage.id);
4305
4415
  });
4306
- renderMessages(state.messages, { stickToBottom: true });
4416
+ renderMessages(state.messages);
4307
4417
  }
4308
4418
  alert(e.message);
4309
4419
  }
@@ -4334,6 +4444,16 @@
4334
4444
  });
4335
4445
  }
4336
4446
 
4447
+ if (composer) {
4448
+ composer.addEventListener('focusin', syncComposerExpanded);
4449
+ composer.addEventListener('focusout', function () {
4450
+ // 延后到下一个宏任务再判断,让浏览器先把焦点落到新的目标元素上
4451
+ // (比如 composer 内的 CLI/Agent 按钮),避免过早收起导致点击落空。
4452
+ window.setTimeout(syncComposerExpanded, 0);
4453
+ });
4454
+ }
4455
+ commandInput.addEventListener('input', syncComposerExpanded);
4456
+
4337
4457
  commandInput.addEventListener('keydown', function (event) {
4338
4458
  if (event.key !== 'Enter' || event.isComposing) {
4339
4459
  return;
@@ -4372,6 +4492,7 @@
4372
4492
 
4373
4493
  if (viewActivityBtn) {
4374
4494
  viewActivityBtn.addEventListener('click', function () {
4495
+ closeWorkspaceSwitcherMenu();
4375
4496
  setActiveTab('activity');
4376
4497
  commandInput.focus();
4377
4498
  });
@@ -4379,30 +4500,35 @@
4379
4500
 
4380
4501
  if (viewTerminalBtn) {
4381
4502
  viewTerminalBtn.addEventListener('click', function () {
4503
+ closeWorkspaceSwitcherMenu();
4382
4504
  setActiveTab('terminal');
4383
4505
  });
4384
4506
  }
4385
4507
 
4386
4508
  if (viewFilesBtn) {
4387
4509
  viewFilesBtn.addEventListener('click', function () {
4510
+ closeWorkspaceSwitcherMenu();
4388
4511
  setActiveTab('files');
4389
4512
  });
4390
4513
  }
4391
4514
 
4392
4515
  if (viewDetailBtn) {
4393
4516
  viewDetailBtn.addEventListener('click', function () {
4517
+ closeWorkspaceSwitcherMenu();
4394
4518
  setActiveTab('detail');
4395
4519
  });
4396
4520
  }
4397
4521
 
4398
4522
  if (viewConfigBtn) {
4399
4523
  viewConfigBtn.addEventListener('click', function () {
4524
+ closeWorkspaceSwitcherMenu();
4400
4525
  setActiveTab('config');
4401
4526
  });
4402
4527
  }
4403
4528
 
4404
4529
  if (viewCheckBtn) {
4405
4530
  viewCheckBtn.addEventListener('click', function () {
4531
+ closeWorkspaceSwitcherMenu();
4406
4532
  setActiveTab('check');
4407
4533
  });
4408
4534
  }
@@ -4459,6 +4585,12 @@
4459
4585
  });
4460
4586
  }
4461
4587
 
4588
+ if (workspaceSwitcherToggle) {
4589
+ workspaceSwitcherToggle.addEventListener('click', function () {
4590
+ setWorkspaceSwitcherMenu(!state.workspaceSwitcherOpen);
4591
+ });
4592
+ }
4593
+
4462
4594
  if (mobileSidebarClose) {
4463
4595
  mobileSidebarClose.addEventListener('click', function () {
4464
4596
  closeMobileSessionPanel();
@@ -4553,6 +4685,9 @@
4553
4685
  if (event.key === 'Escape' && state.mobileActionsOpen) {
4554
4686
  closeMobileActionsMenu();
4555
4687
  }
4688
+ if (event.key === 'Escape' && state.workspaceSwitcherOpen) {
4689
+ closeWorkspaceSwitcherMenu();
4690
+ }
4556
4691
  });
4557
4692
 
4558
4693
  function onLayoutMediaChange() {
@@ -4606,6 +4741,11 @@
4606
4741
  if (headerActions && headerActions.contains(target)) return;
4607
4742
  closeMobileActionsMenu();
4608
4743
  }
4744
+ if (state.workspaceSwitcherOpen) {
4745
+ if (workspaceSwitcherToggle && workspaceSwitcherToggle.contains(target)) return;
4746
+ if (workspaceSwitcherPanel && workspaceSwitcherPanel.contains(target)) return;
4747
+ closeWorkspaceSwitcherMenu();
4748
+ }
4609
4749
  });
4610
4750
 
4611
4751
  removeBtn.addEventListener('click', async function () {
@@ -0,0 +1,30 @@
1
+ (function () {
2
+ function isNearBottom(scrollTop, scrollHeight, clientHeight, thresholdPx) {
3
+ const threshold = Number.isFinite(thresholdPx) ? thresholdPx : 40;
4
+ const distance = scrollHeight - (scrollTop + clientHeight);
5
+ return distance <= threshold;
6
+ }
7
+
8
+ function summarizeTraceFlow(traceEvents, options) {
9
+ const events = Array.isArray(traceEvents) ? traceEvents : [];
10
+ const count = events.length;
11
+ if (count === 0) {
12
+ return { count: 0, label: '暂无步骤' };
13
+ }
14
+ const opts = options && typeof options === 'object' ? options : {};
15
+ const hasError = events.some(event => event && event.kind === 'error');
16
+ const status = hasError ? '有错误' : (opts.pending === true ? '进行中' : '已完成');
17
+ return { count, label: `${count} 步 · ${status}` };
18
+ }
19
+
20
+ function shouldExpandComposer(state) {
21
+ const opts = state && typeof state === 'object' ? state : {};
22
+ return Boolean(opts.focused || opts.hasDraft);
23
+ }
24
+
25
+ window.ManyoyoChatBehavior = {
26
+ isNearBottom,
27
+ summarizeTraceFlow,
28
+ shouldExpandComposer
29
+ };
30
+ }());
package/lib/web/server.js CHANGED
@@ -4590,6 +4590,7 @@ async function startWebServer(options) {
4590
4590
  || assetName === 'app.js'
4591
4591
  || assetName === 'markdown.css'
4592
4592
  || assetName === 'markdown-renderer.js'
4593
+ || assetName === 'chat-behavior.js'
4593
4594
  || assetName === 'file-browser.js'
4594
4595
  || assetName === 'codemirror.bundle.js')) {
4595
4596
  sendHtml(res, 404, '<h1>404 Not Found</h1>');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xcanwin/manyoyo",
3
- "version": "6.1.1",
3
+ "version": "6.1.3",
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",