@xcanwin/manyoyo 6.1.3 → 6.2.0

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.
@@ -47,8 +47,8 @@
47
47
  loadingMessages: false,
48
48
  loadingSessionDetail: false,
49
49
  mobileSidebarOpen: false,
50
- mobileActionsOpen: false,
51
50
  workspaceSwitcherOpen: false,
51
+ composerOptionsOpen: false,
52
52
  configModalOpen: false,
53
53
  createModalOpen: false,
54
54
  agentTemplateModalOpen: false,
@@ -123,8 +123,6 @@
123
123
  const sessionList = document.getElementById('sessionList');
124
124
  const sessionCount = document.getElementById('sessionCount');
125
125
  const mobileSessionToggle = document.getElementById('mobileSessionToggle');
126
- const mobileActionsToggle = document.getElementById('mobileActionsToggle');
127
- const headerActions = document.getElementById('headerActions');
128
126
  const viewActivityBtn = document.getElementById('viewActivityBtn');
129
127
  const workspaceSwitcherToggle = document.getElementById('workspaceSwitcherToggle');
130
128
  const workspaceSwitcherPanel = document.getElementById('workspaceSwitcherPanel');
@@ -133,12 +131,10 @@
133
131
  const viewDetailBtn = document.getElementById('viewDetailBtn');
134
132
  const viewConfigBtn = document.getElementById('viewConfigBtn');
135
133
  const viewCheckBtn = document.getElementById('viewCheckBtn');
136
- const addAgentBtn = document.getElementById('addAgentBtn');
137
134
  const mobileSidebarClose = document.getElementById('mobileSidebarClose');
138
135
  const sidebarBackdrop = document.getElementById('sidebarBackdrop');
139
136
  const openConfigBtn = document.getElementById('openConfigBtn');
140
137
  const openCreateBtn = document.getElementById('openCreateBtn');
141
- const openCreateMenuBtn = document.getElementById('openCreateMenuBtn');
142
138
  const configModal = document.getElementById('configModal');
143
139
  const configModalTitle = document.getElementById('configModalTitle');
144
140
  const configPath = document.getElementById('configPath');
@@ -181,12 +177,12 @@
181
177
  const directoryPickerError = document.getElementById('directoryPickerError');
182
178
  const directoryPickerCancelBtn = document.getElementById('directoryPickerCancelBtn');
183
179
  const directoryPickerSelectBtn = document.getElementById('directoryPickerSelectBtn');
184
- const activeTitle = document.getElementById('activeTitle');
185
- const activeMeta = document.getElementById('activeMeta');
186
180
  const activityCommandBtn = document.getElementById('activityCommandBtn');
187
181
  const activityAgentBtn = document.getElementById('activityAgentBtn');
188
182
  const agentTemplateBtn = document.getElementById('agentTemplateBtn');
189
183
  const activityModelChip = document.getElementById('activityModelChip');
184
+ const composerOptionsToggle = document.getElementById('composerOptionsToggle');
185
+ const composerOptionsPanel = document.getElementById('composerOptionsPanel');
190
186
  const messagesNode = document.getElementById('messages');
191
187
  const terminalPanel = document.getElementById('terminalPanel');
192
188
  const filesPanel = document.getElementById('filesPanel');
@@ -199,10 +195,7 @@
199
195
  const terminalScreen = document.getElementById('terminalScreen');
200
196
  const composer = document.getElementById('composer');
201
197
  const commandInput = document.getElementById('commandInput');
202
- const composerHint = document.getElementById('composerHint');
203
- const sendState = document.getElementById('sendState');
204
198
  const sendBtn = document.getElementById('sendBtn');
205
- const stopBtn = document.getElementById('stopBtn');
206
199
  const agentTemplateModal = document.getElementById('agentTemplateModal');
207
200
  const agentTemplatePrimary = document.getElementById('agentTemplatePrimary');
208
201
  const containerCliSelect = document.getElementById('containerCliSelect');
@@ -218,9 +211,6 @@
218
211
  const externalLinkUrl = document.getElementById('externalLinkUrl');
219
212
  const externalLinkCancelBtn = document.getElementById('externalLinkCancelBtn');
220
213
  const externalLinkOpenBtn = document.getElementById('externalLinkOpenBtn');
221
- const refreshBtn = document.getElementById('refreshBtn');
222
- const removeBtn = document.getElementById('removeBtn');
223
- const removeAllBtn = document.getElementById('removeAllBtn');
224
214
  const MOBILE_LAYOUT_MEDIA = window.matchMedia('(max-width: 980px)');
225
215
  const MOBILE_COMPACT_MEDIA = window.matchMedia('(max-width: 640px)');
226
216
  const TERMINAL_FIT_DEBOUNCE_MS = 60;
@@ -328,7 +318,7 @@
328
318
  return Promise.reject(new Error('clipboard unavailable'));
329
319
  }
330
320
 
331
- function appendMessageCopyActions(meta, items) {
321
+ function appendMessageCopyActions(container, items) {
332
322
  const actions = document.createElement('div');
333
323
  actions.className = 'msg-actions';
334
324
  items.forEach(function (item) {
@@ -352,7 +342,7 @@
352
342
  });
353
343
  actions.appendChild(btn);
354
344
  });
355
- meta.appendChild(actions);
345
+ container.appendChild(actions);
356
346
  }
357
347
 
358
348
  function stringifyPrettyJson(value) {
@@ -605,7 +595,7 @@
605
595
  const hasError = traceEvents.some(function (event) {
606
596
  return event && event.kind === 'error';
607
597
  });
608
- const defaultOpen = hasError || traceEvents.length === 0;
598
+ const defaultOpen = hasError;
609
599
  const toggle = document.createElement('details');
610
600
  toggle.className = 'trace-flow-toggle';
611
601
  toggle.open = traceFlowExpandedState.has(messageId) ? traceFlowExpandedState.get(messageId) : defaultOpen;
@@ -632,7 +622,7 @@
632
622
  return 'AGENT 实时回复';
633
623
  }
634
624
  if (message && message.streamTrace) {
635
- return 'AGENT 过程';
625
+ return 'AGENT 回复';
636
626
  }
637
627
  if (message && message.mode === 'agent') {
638
628
  return 'AGENT 回复';
@@ -1635,17 +1625,6 @@
1635
1625
  return '自动';
1636
1626
  }
1637
1627
 
1638
- function buildActiveMeta(session) {
1639
- if (!session) {
1640
- return '会话不可用';
1641
- }
1642
- const status = sessionStatusInfo(session.status);
1643
- const messageCount = safeMessageCount(session.messageCount);
1644
- const updatedAt = formatDateTime(session.updatedAt) || '暂无更新';
1645
- const containerName = session.containerName || '未绑定容器';
1646
- return `${containerName} · ${status.label} · ${messageCount} 条对话 · ${updatedAt}`;
1647
- }
1648
-
1649
1628
  function buildMessageMetaLines(message) {
1650
1629
  const lines = [];
1651
1630
  const timeText = formatDateTime(message && message.timestamp);
@@ -1980,18 +1959,6 @@
1980
1959
  setMobileSessionPanel(false);
1981
1960
  }
1982
1961
 
1983
- function setMobileActionsMenu(open) {
1984
- state.mobileActionsOpen = Boolean(open);
1985
- document.body.classList.toggle('mobile-actions-open', state.mobileActionsOpen);
1986
- if (mobileActionsToggle) {
1987
- mobileActionsToggle.setAttribute('aria-expanded', state.mobileActionsOpen ? 'true' : 'false');
1988
- }
1989
- }
1990
-
1991
- function closeMobileActionsMenu() {
1992
- setMobileActionsMenu(false);
1993
- }
1994
-
1995
1962
  function setWorkspaceSwitcherMenu(open) {
1996
1963
  state.workspaceSwitcherOpen = Boolean(open);
1997
1964
  document.body.classList.toggle('workspace-switcher-open', state.workspaceSwitcherOpen);
@@ -2007,6 +1974,21 @@
2007
1974
  setWorkspaceSwitcherMenu(false);
2008
1975
  }
2009
1976
 
1977
+ function setComposerOptionsMenu(open) {
1978
+ state.composerOptionsOpen = Boolean(open);
1979
+ document.body.classList.toggle('composer-options-open', state.composerOptionsOpen);
1980
+ if (composerOptionsToggle) {
1981
+ composerOptionsToggle.setAttribute('aria-expanded', state.composerOptionsOpen ? 'true' : 'false');
1982
+ }
1983
+ if (composerOptionsPanel) {
1984
+ composerOptionsPanel.hidden = !state.composerOptionsOpen;
1985
+ }
1986
+ }
1987
+
1988
+ function closeComposerOptionsMenu() {
1989
+ setComposerOptionsMenu(false);
1990
+ }
1991
+
2010
1992
  const VIEW_LABELS = {
2011
1993
  activity: '活动',
2012
1994
  terminal: '终端',
@@ -2274,31 +2256,17 @@
2274
2256
  }
2275
2257
  }
2276
2258
 
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
-
2291
2259
  function syncUi() {
2292
2260
  if (!state.active) {
2293
- activeTitle.textContent = '未选择会话';
2294
- activeMeta.textContent = '请选择左侧会话';
2261
+ document.title = window.ManyoyoChatBehavior.buildDocumentTitle('');
2295
2262
  if (isComposerMode()) {
2296
2263
  commandInput.value = '';
2297
2264
  }
2298
2265
  } else {
2299
2266
  const activeSession = getActiveSession();
2300
- activeTitle.textContent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
2301
- activeMeta.textContent = buildActiveMeta(activeSession);
2267
+ document.title = window.ManyoyoChatBehavior.buildDocumentTitle(
2268
+ activeSession && activeSession.agentName ? activeSession.agentName : state.active
2269
+ );
2302
2270
  }
2303
2271
 
2304
2272
  const activityTab = state.activeTab === 'activity';
@@ -2377,16 +2345,15 @@
2377
2345
 
2378
2346
  const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
2379
2347
  const busy = state.loadingSessions || state.loadingMessages || state.sending || state.creatingAgent;
2380
- refreshBtn.disabled = busy;
2381
- if (addAgentBtn) {
2382
- addAgentBtn.disabled = !state.active || busy;
2383
- addAgentBtn.textContent = state.creatingAgent ? '新建中...' : '新建 AGENT';
2384
- }
2385
- removeBtn.disabled = !state.active || busy;
2386
- removeAllBtn.disabled = !state.active || busy;
2387
- sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && (!agentEnabled || activeAgentRunning));
2388
- if (stopBtn) {
2389
- stopBtn.disabled = !activityTab || !agentMode || !activeAgentRunning || state.agentRun.stopping;
2348
+ const showStop = agentMode && activeAgentRunning;
2349
+ if (showStop) {
2350
+ sendBtn.disabled = !activityTab || !state.active || state.agentRun.stopping;
2351
+ sendBtn.textContent = state.agentRun.stopping ? '停止中…' : '停止';
2352
+ sendBtn.classList.add('danger-outline');
2353
+ } else {
2354
+ sendBtn.disabled = !activityTab || !state.active || busy || (agentMode && !agentEnabled);
2355
+ sendBtn.textContent = '发送';
2356
+ sendBtn.classList.remove('danger-outline');
2390
2357
  }
2391
2358
  if (agentTemplateBtn) {
2392
2359
  agentTemplateBtn.disabled = !state.active || state.agentTemplateSaving;
@@ -2397,18 +2364,9 @@
2397
2364
  ? '输入提示词,例如:请帮我分析当前项目结构并给出重构建议'
2398
2365
  : '输入容器命令,例如: ls -la';
2399
2366
  }
2400
- if (composerHint) {
2401
- composerHint.textContent = agentMode
2402
- ? 'Enter 发送提示词 · Shift/Alt + Enter 换行 · 执行中可停止'
2403
- : 'Enter 发送 · Shift/Alt + Enter 换行';
2404
- }
2405
- syncComposerExpanded();
2406
2367
  if (openCreateBtn) {
2407
2368
  openCreateBtn.disabled = state.createLoading || state.createSubmitting;
2408
2369
  }
2409
- if (openCreateMenuBtn) {
2410
- openCreateMenuBtn.disabled = state.createLoading || state.createSubmitting;
2411
- }
2412
2370
  if (openConfigBtn) {
2413
2371
  openConfigBtn.disabled = state.configLoading || state.configSaving;
2414
2372
  }
@@ -2435,24 +2393,6 @@
2435
2393
  if (createCancelBtn) {
2436
2394
  createCancelBtn.disabled = state.createSubmitting;
2437
2395
  }
2438
- if (sendState) {
2439
- if (!state.active) {
2440
- sendState.textContent = '未选择会话';
2441
- sendState.classList.remove('is-active');
2442
- } else if (state.creatingAgent) {
2443
- sendState.textContent = '正在新建 AGENT…';
2444
- sendState.classList.add('is-active');
2445
- } else if (activeAgentRunning && agentMode) {
2446
- sendState.textContent = state.agentRun.stopping ? '正在停止 Agent…' : 'Agent 执行中';
2447
- sendState.classList.add('is-active');
2448
- } else if (busy) {
2449
- sendState.textContent = '处理中';
2450
- sendState.classList.add('is-active');
2451
- } else {
2452
- sendState.textContent = agentMode ? 'Agent 就绪' : '命令就绪';
2453
- sendState.classList.remove('is-active');
2454
- }
2455
- }
2456
2396
  if (configModal) {
2457
2397
  configModal.hidden = !state.configModalOpen;
2458
2398
  }
@@ -2493,25 +2433,10 @@
2493
2433
  'modal-open',
2494
2434
  state.configModalOpen || state.createModalOpen || state.directoryPicker.open || state.agentTemplateModalOpen || state.externalLinkModalOpen
2495
2435
  );
2496
- if (!state.active) {
2497
- sendState.textContent = '未选择会话';
2498
- } else if (state.creatingAgent) {
2499
- sendState.textContent = '正在新建 AGENT…';
2500
- } else if (agentMode && !agentEnabled) {
2501
- sendState.textContent = '当前会话未配置 AGENT 模板';
2502
- } else if (state.sending) {
2503
- sendState.textContent = '发送中...';
2504
- } else if (state.loadingSessions || state.loadingMessages) {
2505
- sendState.textContent = '加载中...';
2506
- } else {
2507
- sendState.textContent = '就绪';
2508
- }
2509
- sendState.classList.toggle('is-active', state.sending || state.creatingAgent);
2510
2436
  if (composer) {
2511
2437
  composer.hidden = !activityTab;
2512
2438
  }
2513
2439
  setMobileSessionPanel(state.mobileSidebarOpen);
2514
- setMobileActionsMenu(state.mobileActionsOpen);
2515
2440
  renderSessionDetailPanels();
2516
2441
  }
2517
2442
 
@@ -3134,6 +3059,122 @@
3134
3059
  return findLatestCreatedSessionName(remaining, removedRef.containerName);
3135
3060
  }
3136
3061
 
3062
+ let openTreeNodeMenu = null;
3063
+
3064
+ function closeOpenTreeNodeMenu() {
3065
+ if (openTreeNodeMenu) {
3066
+ const menu = openTreeNodeMenu;
3067
+ openTreeNodeMenu = null;
3068
+ menu.close();
3069
+ }
3070
+ }
3071
+
3072
+ function createTreeNodeMenu(items) {
3073
+ const wrap = document.createElement('div');
3074
+ wrap.className = 'tree-node-menu';
3075
+
3076
+ const trigger = document.createElement('button');
3077
+ trigger.type = 'button';
3078
+ trigger.className = 'secondary tree-node-menu-trigger';
3079
+ trigger.setAttribute('aria-haspopup', 'true');
3080
+ trigger.setAttribute('aria-expanded', 'false');
3081
+ trigger.setAttribute('aria-label', '更多操作');
3082
+ trigger.innerHTML = '<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>';
3083
+
3084
+ const panel = document.createElement('div');
3085
+ panel.className = 'tree-node-menu-panel';
3086
+ panel.hidden = true;
3087
+
3088
+ function close() {
3089
+ panel.hidden = true;
3090
+ trigger.setAttribute('aria-expanded', 'false');
3091
+ wrap.classList.remove('is-open');
3092
+ }
3093
+
3094
+ trigger.addEventListener('click', function (event) {
3095
+ event.stopPropagation();
3096
+ if (panel.hidden) {
3097
+ closeOpenTreeNodeMenu();
3098
+ panel.hidden = false;
3099
+ trigger.setAttribute('aria-expanded', 'true');
3100
+ wrap.classList.add('is-open');
3101
+ openTreeNodeMenu = { wrap, close };
3102
+ } else {
3103
+ close();
3104
+ openTreeNodeMenu = null;
3105
+ }
3106
+ });
3107
+
3108
+ (Array.isArray(items) ? items : []).forEach(function (item) {
3109
+ const itemBtn = document.createElement('button');
3110
+ itemBtn.type = 'button';
3111
+ itemBtn.className = 'tree-node-menu-panel-item' + (item.danger ? ' is-danger' : '');
3112
+ itemBtn.textContent = item.label;
3113
+ itemBtn.addEventListener('click', function (event) {
3114
+ event.stopPropagation();
3115
+ close();
3116
+ openTreeNodeMenu = null;
3117
+ if (typeof item.onClick === 'function') {
3118
+ item.onClick();
3119
+ }
3120
+ });
3121
+ panel.appendChild(itemBtn);
3122
+ });
3123
+
3124
+ wrap.appendChild(trigger);
3125
+ wrap.appendChild(panel);
3126
+ return wrap;
3127
+ }
3128
+
3129
+ async function removeContainerByName(containerName) {
3130
+ const target = String(containerName || '').trim();
3131
+ if (!target) return;
3132
+ const yes = confirm('确认删除容器 ' + target + ' ?');
3133
+ if (!yes) return;
3134
+ try {
3135
+ const wasActiveContainer = parseSessionKey(state.active).containerName === target;
3136
+ if (parseSessionKey(state.terminal.sessionName).containerName === target
3137
+ && (state.terminal.connected || state.terminal.connecting)) {
3138
+ disconnectTerminal('容器删除,终端已断开', true);
3139
+ }
3140
+ await api('/api/sessions/' + encodeURIComponent(target) + '/remove', {
3141
+ method: 'POST'
3142
+ });
3143
+ await refreshSessions({
3144
+ preferredName: wasActiveContainer ? target : (state.active || ''),
3145
+ withLoading: true,
3146
+ reloadMessages: wasActiveContainer
3147
+ });
3148
+ } catch (e) {
3149
+ alert(e.message);
3150
+ }
3151
+ }
3152
+
3153
+ async function removeAgentSessionByName(sessionName, agentLabel) {
3154
+ const target = String(sessionName || '').trim();
3155
+ if (!target) return;
3156
+ const yes = confirm('确认删除 AGENT ' + (agentLabel || target) + ' ?');
3157
+ if (!yes) return;
3158
+ const isActive = target === state.active;
3159
+ try {
3160
+ const containerName = parseSessionKey(target).containerName;
3161
+ const fallbackSessionName = isActive
3162
+ ? findPreferredSessionNameAfterRemoval(state.sessions, target)
3163
+ : '';
3164
+ await api('/api/sessions/' + encodeURIComponent(target) + '/remove-with-history', {
3165
+ method: 'POST'
3166
+ });
3167
+ await refreshSessions({
3168
+ preferredName: isActive ? (fallbackSessionName || '') : (state.active || ''),
3169
+ preferredContainerName: containerName,
3170
+ withLoading: true,
3171
+ reloadMessages: isActive
3172
+ });
3173
+ } catch (e) {
3174
+ alert(e.message);
3175
+ }
3176
+ }
3177
+
3137
3178
  function createDisclosureButton(expanded, label) {
3138
3179
  const button = document.createElement('button');
3139
3180
  button.type = 'button';
@@ -3272,18 +3313,21 @@
3272
3313
  const status = sessionStatusInfo(containerGroup && containerGroup.status);
3273
3314
  const historyClassName = status.tone === 'history' ? 'tree-node-tone-history' : '';
3274
3315
  const containerName = String(containerGroup && containerGroup.containerName ? containerGroup.containerName : '').trim() || '未命名容器';
3275
- const hoverMenu = document.createElement('div');
3276
- hoverMenu.className = 'tree-node-hover-menu';
3277
-
3278
- const addAgentBtn = document.createElement('button');
3279
- addAgentBtn.type = 'button';
3280
- addAgentBtn.className = 'secondary tree-node-menu-item';
3281
- addAgentBtn.textContent = '新建 AGENT';
3282
- addAgentBtn.addEventListener('click', function (event) {
3283
- event.stopPropagation();
3284
- createAgentSession(containerName);
3285
- });
3286
- hoverMenu.appendChild(addAgentBtn);
3316
+ const containerMenu = createTreeNodeMenu([
3317
+ {
3318
+ label: '新建 AGENT',
3319
+ onClick: function () {
3320
+ createAgentSession(containerName);
3321
+ }
3322
+ },
3323
+ {
3324
+ label: '删除容器',
3325
+ danger: true,
3326
+ onClick: function () {
3327
+ removeContainerByName(containerName);
3328
+ }
3329
+ }
3330
+ ]);
3287
3331
 
3288
3332
  return {
3289
3333
  kind: 'container',
@@ -3294,18 +3338,28 @@
3294
3338
  disclosureLabel: containerName,
3295
3339
  expanded: isContainerExpanded(containerGroup),
3296
3340
  hasActive: containerContainsActiveSession(containerGroup),
3297
- overlayNode: hoverMenu,
3341
+ overlayNode: containerMenu,
3298
3342
  onToggle: function (expanded) {
3299
3343
  setSidebarContainerExpanded(containerGroup.containerName, expanded);
3300
3344
  },
3301
3345
  children: (Array.isArray(containerGroup.sessions) ? containerGroup.sessions : []).map(function (session) {
3346
+ const agentTitle = session.agentName || session.name;
3302
3347
  return {
3303
3348
  kind: 'agent',
3304
- title: session.agentName || session.name,
3349
+ title: agentTitle,
3305
3350
  meta: formatDateTime(session.updatedAt) || '暂无更新',
3306
3351
  className: historyClassName,
3307
3352
  active: state.active === session.name,
3308
- sessionName: session.name
3353
+ sessionName: session.name,
3354
+ overlayNode: createTreeNodeMenu([
3355
+ {
3356
+ label: '删除 AGENT',
3357
+ danger: true,
3358
+ onClick: function () {
3359
+ removeAgentSessionByName(session.name, agentTitle);
3360
+ }
3361
+ }
3362
+ ])
3309
3363
  };
3310
3364
  })
3311
3365
  };
@@ -3493,10 +3547,12 @@
3493
3547
  }
3494
3548
 
3495
3549
  function getMessageRenderKey(msg, index) {
3550
+ const pairedTrace = msg && msg.pairedTrace ? msg.pairedTrace : null;
3551
+ const tracePart = pairedTrace ? `|paired:${String(pairedTrace.content || '')}` : '';
3496
3552
  if (msg && msg.id && msg.streamingReply) {
3497
3553
  const content = msg.content ? String(msg.content) : '';
3498
3554
  const timestamp = msg.timestamp ? String(msg.timestamp) : '';
3499
- return `id:${msg.id}|streaming|${timestamp}|${content}`;
3555
+ return `id:${msg.id}|streaming|${timestamp}|${content}${tracePart}`;
3500
3556
  }
3501
3557
  if (msg && msg.id && msg.streamTrace) {
3502
3558
  const content = msg.content ? String(msg.content) : '';
@@ -3505,7 +3561,7 @@
3505
3561
  return `id:${msg.id}|trace|${timestamp}|${content}|${running}`;
3506
3562
  }
3507
3563
  if (msg && msg.id) {
3508
- return `id:${msg.id}`;
3564
+ return `id:${msg.id}${tracePart}`;
3509
3565
  }
3510
3566
  const role = msg && msg.role ? String(msg.role) : '';
3511
3567
  const mode = msg && msg.mode ? String(msg.mode) : '';
@@ -3513,7 +3569,7 @@
3513
3569
  const exitCode = msg && typeof msg.exitCode === 'number' ? String(msg.exitCode) : '';
3514
3570
  const pending = msg && msg.pending ? '1' : '0';
3515
3571
  const content = msg && msg.content ? String(msg.content) : '';
3516
- return `idx:${index}|${role}|${mode}|${timestamp}|${exitCode}|${pending}|${content}`;
3572
+ return `idx:${index}|${role}|${mode}|${timestamp}|${exitCode}|${pending}|${content}${tracePart}`;
3517
3573
  }
3518
3574
 
3519
3575
  function resolveMessageOrigin(msg) {
@@ -3550,13 +3606,12 @@
3550
3606
  const bubble = document.createElement('div');
3551
3607
  bubble.className = 'bubble';
3552
3608
 
3609
+ if (msg && msg.pairedTrace) {
3610
+ appendStructuredTraceContent(bubble, msg.pairedTrace);
3611
+ }
3612
+
3553
3613
  const isStreamingReply = Boolean(msg && msg.streamingReply);
3554
- const shouldRenderStructuredTrace = Boolean(
3555
- msg
3556
- && msg.streamTrace
3557
- && Array.isArray(msg.traceEvents)
3558
- && msg.traceEvents.length
3559
- );
3614
+ const shouldRenderStructuredTrace = Boolean(msg && msg.streamTrace);
3560
3615
  const shouldRenderMarkdown = Boolean(!msg.streamTrace && !msg.streamingReply && markdownRenderer && markdownRenderer.shouldRenderMessage(msg));
3561
3616
  if (isStreamingReply) {
3562
3617
  bubble.classList.add('streaming-reply');
@@ -3596,7 +3651,7 @@
3596
3651
  if (renderedMarkdown) {
3597
3652
  markdownNode.innerHTML = renderedMarkdown;
3598
3653
  bubble.appendChild(markdownNode);
3599
- appendMessageCopyActions(meta, [
3654
+ appendMessageCopyActions(bubble, [
3600
3655
  { label: '复制文本', getText: function () { return markdownNode.innerText || markdownNode.textContent || ''; } },
3601
3656
  { label: '复制 Markdown', getText: function () { return msg.content; } }
3602
3657
  ]);
@@ -3606,7 +3661,7 @@
3606
3661
  } else {
3607
3662
  appendPlainMessageContent(bubble, msg.content);
3608
3663
  if (msg.role === 'user') {
3609
- appendMessageCopyActions(meta, [
3664
+ appendMessageCopyActions(bubble, [
3610
3665
  { label: '复制', getText: function () { return msg.content; } }
3611
3666
  ]);
3612
3667
  }
@@ -3617,7 +3672,8 @@
3617
3672
  return row;
3618
3673
  }
3619
3674
 
3620
- function renderMessages(messages, options) {
3675
+ function renderMessages(rawMessages, options) {
3676
+ const messages = window.ManyoyoChatBehavior.mergeTraceIntoReply(rawMessages);
3621
3677
  const renderOptions = options && typeof options === 'object' ? options : {};
3622
3678
  const stickToBottom = renderOptions.stickToBottom === true || isMessagesNearBottom(40);
3623
3679
 
@@ -4149,15 +4205,9 @@
4149
4205
  });
4150
4206
  }
4151
4207
 
4152
- if (openCreateMenuBtn) {
4153
- openCreateMenuBtn.addEventListener('click', function () {
4154
- closeMobileActionsMenu();
4155
- openCreateModal();
4156
- });
4157
- }
4158
-
4159
4208
  if (agentTemplateBtn) {
4160
4209
  agentTemplateBtn.addEventListener('click', function () {
4210
+ closeComposerOptionsMenu();
4161
4211
  openAgentTemplateModal().catch(function (e) {
4162
4212
  alert(e && e.message ? e.message : '加载 Agent 模板失败');
4163
4213
  });
@@ -4349,10 +4399,29 @@
4349
4399
  composer.addEventListener('submit', async function (event) {
4350
4400
  event.preventDefault();
4351
4401
  if (!state.active) return;
4352
- if (state.sending) return;
4353
- if (state.loadingSessions || state.loadingMessages) return;
4354
4402
  if (state.activeTab !== 'activity') return;
4403
+
4355
4404
  const mode = state.mode === 'agent' ? 'agent' : 'command';
4405
+ const activeAgentRunning = isAgentRunActiveForSession(state.active) || hasPendingAgentMessagesForSession(state.active);
4406
+ if (mode === 'agent' && activeAgentRunning) {
4407
+ if (state.agentRun.stopping) return;
4408
+ state.agentRun.stopping = true;
4409
+ syncUi();
4410
+ try {
4411
+ await api('/api/sessions/' + encodeURIComponent(state.active) + '/agent/stop', {
4412
+ method: 'POST',
4413
+ body: JSON.stringify({})
4414
+ });
4415
+ } catch (e) {
4416
+ alert(e.message);
4417
+ state.agentRun.stopping = false;
4418
+ syncUi();
4419
+ }
4420
+ return;
4421
+ }
4422
+
4423
+ if (state.sending) return;
4424
+ if (state.loadingSessions || state.loadingMessages) return;
4356
4425
  if (mode === 'agent' && !isActiveAgentEnabled()) {
4357
4426
  syncUi();
4358
4427
  return;
@@ -4420,40 +4489,9 @@
4420
4489
  } finally {
4421
4490
  state.sending = false;
4422
4491
  syncUi();
4423
- commandInput.focus();
4424
4492
  }
4425
4493
  });
4426
4494
 
4427
- if (stopBtn) {
4428
- stopBtn.addEventListener('click', async function () {
4429
- if (!state.active || !isAgentRunActiveForSession(state.active) || state.agentRun.stopping) {
4430
- return;
4431
- }
4432
- state.agentRun.stopping = true;
4433
- syncUi();
4434
- try {
4435
- await api('/api/sessions/' + encodeURIComponent(state.active) + '/agent/stop', {
4436
- method: 'POST',
4437
- body: JSON.stringify({})
4438
- });
4439
- } catch (e) {
4440
- alert(e.message);
4441
- state.agentRun.stopping = false;
4442
- syncUi();
4443
- }
4444
- });
4445
- }
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
-
4457
4495
  commandInput.addEventListener('keydown', function (event) {
4458
4496
  if (event.key !== 'Enter' || event.isComposing) {
4459
4497
  return;
@@ -4476,6 +4514,7 @@
4476
4514
  activityCommandBtn.addEventListener('click', function () {
4477
4515
  state.mode = 'command';
4478
4516
  renderMessages(state.messages, { forceFullRender: true });
4517
+ closeComposerOptionsMenu();
4479
4518
  syncUi();
4480
4519
  commandInput.focus();
4481
4520
  });
@@ -4485,11 +4524,18 @@
4485
4524
  activityAgentBtn.addEventListener('click', function () {
4486
4525
  state.mode = 'agent';
4487
4526
  renderMessages(state.messages, { forceFullRender: true });
4527
+ closeComposerOptionsMenu();
4488
4528
  syncUi();
4489
4529
  commandInput.focus();
4490
4530
  });
4491
4531
  }
4492
4532
 
4533
+ if (composerOptionsToggle) {
4534
+ composerOptionsToggle.addEventListener('click', function () {
4535
+ setComposerOptionsMenu(!state.composerOptionsOpen);
4536
+ });
4537
+ }
4538
+
4493
4539
  if (viewActivityBtn) {
4494
4540
  viewActivityBtn.addEventListener('click', function () {
4495
4541
  closeWorkspaceSwitcherMenu();
@@ -4533,11 +4579,6 @@
4533
4579
  });
4534
4580
  }
4535
4581
 
4536
- refreshBtn.addEventListener('click', function () {
4537
- closeMobileActionsMenu();
4538
- loadSessions(state.active).catch(function (e) { alert(e.message); });
4539
- });
4540
-
4541
4582
  const TERM_KEY_SEQUENCES = {
4542
4583
  esc: '\x1b',
4543
4584
  tab: '\x09',
@@ -4579,12 +4620,6 @@
4579
4620
  });
4580
4621
  }
4581
4622
 
4582
- if (mobileActionsToggle) {
4583
- mobileActionsToggle.addEventListener('click', function () {
4584
- setMobileActionsMenu(!state.mobileActionsOpen);
4585
- });
4586
- }
4587
-
4588
4623
  if (workspaceSwitcherToggle) {
4589
4624
  workspaceSwitcherToggle.addEventListener('click', function () {
4590
4625
  setWorkspaceSwitcherMenu(!state.workspaceSwitcherOpen);
@@ -4603,18 +4638,6 @@
4603
4638
  });
4604
4639
  }
4605
4640
 
4606
- if (addAgentBtn) {
4607
- addAgentBtn.addEventListener('click', function () {
4608
- const activeSession = getActiveSession();
4609
- const targetContainer = activeSession ? getSessionContainerName(activeSession) : '';
4610
- if (!targetContainer) {
4611
- return;
4612
- }
4613
- closeMobileActionsMenu();
4614
- createAgentSession(targetContainer);
4615
- });
4616
- }
4617
-
4618
4641
  if (configModal) {
4619
4642
  configModal.addEventListener('click', function (event) {
4620
4643
  if (event.target === configModal && !state.configSaving) {
@@ -4682,17 +4705,19 @@
4682
4705
  if (event.key === 'Escape' && state.mobileSidebarOpen) {
4683
4706
  closeMobileSessionPanel();
4684
4707
  }
4685
- if (event.key === 'Escape' && state.mobileActionsOpen) {
4686
- closeMobileActionsMenu();
4687
- }
4688
4708
  if (event.key === 'Escape' && state.workspaceSwitcherOpen) {
4689
4709
  closeWorkspaceSwitcherMenu();
4690
4710
  }
4711
+ if (event.key === 'Escape' && state.composerOptionsOpen) {
4712
+ closeComposerOptionsMenu();
4713
+ }
4714
+ if (event.key === 'Escape' && openTreeNodeMenu) {
4715
+ closeOpenTreeNodeMenu();
4716
+ }
4691
4717
  });
4692
4718
 
4693
4719
  function onLayoutMediaChange() {
4694
4720
  setMobileSessionPanel(state.mobileSidebarOpen);
4695
- setMobileActionsMenu(state.mobileActionsOpen);
4696
4721
  if (state.activeTab === 'terminal' && state.terminal.terminalReady) {
4697
4722
  scheduleTerminalFit(false);
4698
4723
  }
@@ -4736,64 +4761,22 @@
4736
4761
 
4737
4762
  document.addEventListener('click', function (event) {
4738
4763
  const target = event.target;
4739
- if (state.mobileActionsOpen) {
4740
- if (mobileActionsToggle && mobileActionsToggle.contains(target)) return;
4741
- if (headerActions && headerActions.contains(target)) return;
4742
- closeMobileActionsMenu();
4743
- }
4744
4764
  if (state.workspaceSwitcherOpen) {
4745
4765
  if (workspaceSwitcherToggle && workspaceSwitcherToggle.contains(target)) return;
4746
4766
  if (workspaceSwitcherPanel && workspaceSwitcherPanel.contains(target)) return;
4747
4767
  closeWorkspaceSwitcherMenu();
4748
4768
  }
4749
- });
4750
-
4751
- removeBtn.addEventListener('click', async function () {
4752
- if (!state.active) return;
4753
- closeMobileActionsMenu();
4754
- const activeSession = getActiveSession();
4755
- const targetContainer = activeSession && activeSession.containerName ? activeSession.containerName : state.active;
4756
- const yes = confirm('确认删除容器 ' + targetContainer + ' ?');
4757
- if (!yes) return;
4758
- try {
4759
- const current = state.active;
4760
- if (state.terminal.sessionName === current && (state.terminal.connected || state.terminal.connecting)) {
4761
- disconnectTerminal('容器删除,终端已断开', true);
4762
- }
4763
- await api('/api/sessions/' + encodeURIComponent(current) + '/remove', {
4764
- method: 'POST'
4765
- });
4766
- await loadSessions(current);
4767
- } catch (e) {
4768
- alert(e.message);
4769
+ if (state.composerOptionsOpen) {
4770
+ if (composerOptionsToggle && composerOptionsToggle.contains(target)) return;
4771
+ if (composerOptionsPanel && composerOptionsPanel.contains(target)) return;
4772
+ closeComposerOptionsMenu();
4769
4773
  }
4770
- });
4771
-
4772
- removeAllBtn.addEventListener('click', async function () {
4773
- if (!state.active) return;
4774
- closeMobileActionsMenu();
4775
- const activeSession = getActiveSession();
4776
- const targetAgent = activeSession && activeSession.agentName ? activeSession.agentName : state.active;
4777
- const yes = confirm('确认删除 AGENT ' + targetAgent + ' ?');
4778
- if (!yes) return;
4779
- try {
4780
- const current = state.active;
4781
- const targetContainerName = activeSession && activeSession.containerName ? activeSession.containerName : '';
4782
- const fallbackSessionName = findPreferredSessionNameAfterRemoval(state.sessions, current);
4783
- await api('/api/sessions/' + encodeURIComponent(state.active) + '/remove-with-history', {
4784
- method: 'POST'
4785
- });
4786
- await refreshSessions({
4787
- preferredName: fallbackSessionName || '',
4788
- preferredContainerName: targetContainerName,
4789
- withLoading: true,
4790
- reloadMessages: true
4791
- });
4792
- } catch (e) {
4793
- alert(e.message);
4774
+ if (openTreeNodeMenu && !openTreeNodeMenu.wrap.contains(target)) {
4775
+ closeOpenTreeNodeMenu();
4794
4776
  }
4795
4777
  });
4796
4778
 
4779
+
4797
4780
  if (externalLinkCancelBtn) {
4798
4781
  externalLinkCancelBtn.addEventListener('click', function () {
4799
4782
  closeExternalLinkModalView();