ec-web 0.0.4 → 0.0.5

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.
@@ -296,10 +296,19 @@ const translations = {
296
296
  'sessions.turnType.toolUse': '工具使用',
297
297
  'sessions.turnType.toolResult': '工具结果',
298
298
  'sessions.turnType.msgSend': '发送消息',
299
- 'sessions.count': '{filtered} / {total} 个会话',
299
+ 'sessions.count': '{count} 个会话',
300
300
  'sessions.filter.valid': '有效',
301
301
  'sessions.filter.validTitle': '只显示有效会话(≥1 条用户消息)',
302
+ 'sessions.filter.all': '全部',
303
+ 'sessions.filter.allTitle': '显示全部会话(包括无用户消息的会话)',
304
+ 'sessions.filter.sourceTitle': '按会话类型筛选',
305
+ 'sessions.filter.sourceAll': '全部类型',
302
306
  'sessions.messageCountTitle': '用户输入 {user} 条 / 共 {total} 条消息',
307
+ 'sessions.source.trigger': 'Trigger',
308
+ 'sessions.source.agent': 'Agent',
309
+ 'sessions.source.cli': 'CLI',
310
+ 'sessions.source.user': '用户',
311
+ 'sessions.source.unknown': 'Unknown',
303
312
  'sessions.selectLog': '选择会话查看 CC 日志',
304
313
  'sessions.chatView': '💬 对话视图',
305
314
  'sessions.fullView': '📜 完整视图',
@@ -309,7 +318,16 @@ const translations = {
309
318
  'sessions.empty.noDialogue': '该会话没有用户对话消息',
310
319
  'sessions.turnType.userInput': '用户输入',
311
320
  'sessions.turnType.toolCall': '工具调用',
321
+ 'sessions.turnType.subtask': '子任务',
312
322
  'sessions.turnType.system': '系统',
323
+ 'sessions.subtask.status.running': '运行中',
324
+ 'sessions.subtask.status.completed': '已完成',
325
+ 'sessions.subtask.status.failed': '失败',
326
+ 'sessions.subtask.status.stopped': '已停止',
327
+ 'sessions.subtask.status.killed': '已终止',
328
+ 'sessions.subtask.tools': '{count} 次工具',
329
+ 'sessions.subtask.tokens': '{count} tokens',
330
+ 'sessions.subtask.reason': '原因:{reason}',
313
331
  'sessions.block.thinking': '思考',
314
332
  'sessions.block.result': '结果',
315
333
  'sessions.block.resultError': '结果',
@@ -639,6 +657,7 @@ const translations = {
639
657
  'triggers.feedback.defaultFailure': '默认:触发器失败:{{error.message}}',
640
658
  'triggers.field.model': '模型',
641
659
  'triggers.field.effort': '推理强度',
660
+ 'triggers.baseagent.mismatch': 'Trigger 固定:{trigger},Agent 当前:{agent}',
642
661
  'triggers.field.permission': '权限模式',
643
662
  'triggers.delivery.on': '启用',
644
663
  'triggers.delivery.off': '禁用',
@@ -1152,10 +1171,19 @@ const translations = {
1152
1171
  'sessions.turnType.toolUse': 'Tool Use',
1153
1172
  'sessions.turnType.toolResult': 'Tool Result',
1154
1173
  'sessions.turnType.msgSend': 'Send Message',
1155
- 'sessions.count': '{filtered} / {total} sessions',
1174
+ 'sessions.count': '{count} sessions',
1156
1175
  'sessions.filter.valid': 'Valid',
1157
1176
  'sessions.filter.validTitle': 'Only show valid sessions (at least 1 user message)',
1177
+ 'sessions.filter.all': 'All',
1178
+ 'sessions.filter.allTitle': 'Show all sessions, including sessions without user messages',
1179
+ 'sessions.filter.sourceTitle': 'Filter by session type',
1180
+ 'sessions.filter.sourceAll': 'All types',
1158
1181
  'sessions.messageCountTitle': '{user} user messages / {total} total messages',
1182
+ 'sessions.source.trigger': 'Trigger',
1183
+ 'sessions.source.agent': 'Agent',
1184
+ 'sessions.source.cli': 'CLI',
1185
+ 'sessions.source.user': 'User',
1186
+ 'sessions.source.unknown': 'Unknown',
1159
1187
  'sessions.selectLog': 'Select a session to view the CC log',
1160
1188
  'sessions.chatView': '💬 Chat View',
1161
1189
  'sessions.fullView': '📜 Full View',
@@ -1165,7 +1193,16 @@ const translations = {
1165
1193
  'sessions.empty.noDialogue': 'No user dialogue messages in this session',
1166
1194
  'sessions.turnType.userInput': 'User Input',
1167
1195
  'sessions.turnType.toolCall': 'Tool Call',
1196
+ 'sessions.turnType.subtask': 'Subtask',
1168
1197
  'sessions.turnType.system': 'System',
1198
+ 'sessions.subtask.status.running': 'Running',
1199
+ 'sessions.subtask.status.completed': 'Completed',
1200
+ 'sessions.subtask.status.failed': 'Failed',
1201
+ 'sessions.subtask.status.stopped': 'Stopped',
1202
+ 'sessions.subtask.status.killed': 'Killed',
1203
+ 'sessions.subtask.tools': '{count} tool uses',
1204
+ 'sessions.subtask.tokens': '{count} tokens',
1205
+ 'sessions.subtask.reason': 'Reason: {reason}',
1169
1206
  'sessions.block.thinking': 'Thinking',
1170
1207
  'sessions.block.result': 'Result',
1171
1208
  'sessions.block.resultError': 'Result',
@@ -1495,6 +1532,7 @@ const translations = {
1495
1532
  'triggers.feedback.defaultFailure': 'Default: 触发器失败:{{error.message}}',
1496
1533
  'triggers.field.model': 'Model',
1497
1534
  'triggers.field.effort': 'Effort',
1535
+ 'triggers.baseagent.mismatch': 'Trigger fixed: {trigger}; current Agent: {agent}',
1498
1536
  'triggers.field.permission': 'Permission Mode',
1499
1537
  'triggers.delivery.on': 'Enabled',
1500
1538
  'triggers.delivery.off': 'Disabled',
@@ -2030,7 +2068,8 @@ let _triggerEdit = null;
2030
2068
  let _triggerHistory = null;
2031
2069
  let _triggerTest = null;
2032
2070
  let sessSearch = '';
2033
- let sessFilterNormal = false; // true=只显示有效会话(userMsgs >= 1)
2071
+ let sessShowAll = false; // false=只显示有效会话;true=显示全部
2072
+ let sessSourceFilter = 'all'; // all / trigger / agent / cli / user / unknown
2034
2073
  let sessChatMode = false; // false=完整视图,true=对话视图(折叠处理过程)
2035
2074
  let monRange = '2m'; // Monitor 时间窗口:2m / 10m / 1h
2036
2075
  let logSel = { source: null, level: 'all', module: 'all', search: '', limit: 500, follow: true };
@@ -2414,8 +2453,13 @@ const MSG_TYPE_META = {
2414
2453
  file: { icon: '📎', label: () => t('messages.msgType.file') },
2415
2454
  command: { icon: '⌘', label: () => t('messages.msgType.command') }
2416
2455
  };
2417
- function msgTagsHtml(kind, encrypt, chatmode, dir) {
2418
- let h = '';
2456
+ // 渠道标:AUN 是默认渠道,不标;其余渠道标出来,免得混排后分不清来路
2457
+ function channelTagHtml(channel, cls) {
2458
+ if (!channel || channel === 'aun') return '';
2459
+ return `<span class="${cls}">${esc(channel)}</span>`;
2460
+ }
2461
+ function msgTagsHtml(kind, encrypt, chatmode, dir, channel) {
2462
+ let h = channelTagHtml(channel, 'mtag mtag-channel');
2419
2463
  // 'send' 仅出向才是「回复」;入向是用户输入,不打回复标记
2420
2464
  const km = (kind === 'send' && dir === 'in') ? null : MSG_KIND_META[kind];
2421
2465
  if (km) h += `<span class="mtag${kind === 'send' ? ' mtag-reply' : ''}">${km.icon}${km.label()}</span>`;
@@ -2425,24 +2469,39 @@ function msgTagsHtml(kind, encrypt, chatmode, dir) {
2425
2469
  }
2426
2470
 
2427
2471
  // 消息行:方向箭头 + 标记 + 对端 + 文字
2472
+ function latestAgentPreviewMessage(s) {
2473
+ const recent = Array.isArray(s.recentMessages) ? s.recentMessages[0] : null;
2474
+ if (recent?.text) return recent;
2475
+ const recvTs = s.lastReceivedAt || 0;
2476
+ const sentTs = s.lastSentAt || 0;
2477
+ if (sentTs > recvTs && s.lastSentText) {
2478
+ return {
2479
+ dir: 'out', peer: s.lastSentTo, text: s.lastSentText, kind: s.lastSentKind,
2480
+ encrypt: s.lastSentEncrypt, chatmode: s.lastSentChatmode, channel: s.lastSentChannel,
2481
+ };
2482
+ }
2483
+ if (s.lastReceivedText) {
2484
+ return {
2485
+ dir: 'in', peer: s.lastReceivedFrom, text: s.lastReceivedText, kind: s.lastReceivedKind,
2486
+ encrypt: s.lastReceivedEncrypt, chatmode: s.lastReceivedChatmode, channel: s.lastReceivedChannel,
2487
+ };
2488
+ }
2489
+ return null;
2490
+ }
2491
+
2428
2492
  function agentPreviewHtml(s) {
2429
2493
  const clip = (t) => esc(String(t).replace(/\n/g, ' ').slice(0, 80));
2430
- const line = (dir, peer, text, kind, encrypt, chatmode) => {
2494
+ const line = (dir, peer, text, kind, encrypt, chatmode, channel) => {
2431
2495
  const arrow = dir === 'in' ? '<span class="arrow-in">↓</span>' : '<span class="arrow-out">↑</span>';
2432
- const tags = msgTagsHtml(kind, encrypt, chatmode, dir);
2496
+ const tags = msgTagsHtml(kind, encrypt, chatmode, dir, channel);
2433
2497
  const peerHtml = peer ? `<span class="peer">${esc(shortAid(peer))}</span>: ` : '';
2434
2498
  const textCls = dir === 'in' ? 'text-in' : 'text-out';
2435
2499
  return `${arrow}${tags ? ' ' + tags + ' ' : ' '}${peerHtml}<span class="${textCls}">${clip(text)}</span>`;
2436
2500
  };
2437
- if ((s.processing || 0) > 0 && s.lastReceivedText)
2438
- return line('in', s.lastReceivedFrom, s.lastReceivedText, s.lastReceivedKind, s.lastReceivedEncrypt, s.lastReceivedChatmode);
2439
- const recvTs = s.lastReceivedAt || 0, sentTs = s.lastSentAt || 0;
2440
- if (!recvTs && !sentTs) return '';
2441
- if (sentTs > recvTs && s.lastSentText)
2442
- return line('out', s.lastSentTo, s.lastSentText, s.lastSentKind, s.lastSentEncrypt, s.lastSentChatmode);
2443
- if (s.lastReceivedText)
2444
- return line('in', s.lastReceivedFrom, s.lastReceivedText, s.lastReceivedKind, s.lastReceivedEncrypt, s.lastReceivedChatmode);
2445
- return '';
2501
+ const latest = latestAgentPreviewMessage(s);
2502
+ return latest
2503
+ ? line(latest.dir, latest.peer, latest.text, latest.kind, latest.encrypt, latest.chatmode, latest.channel)
2504
+ : '';
2446
2505
  }
2447
2506
 
2448
2507
  // HTML tooltip(最近 N 轮):时间 + 彩色箭头 + 方式 + 对端 + 文字
@@ -2458,10 +2517,11 @@ function recentMsgTooltipHtml(recent) {
2458
2517
  const kh = km ? `<span class="tip-kind${m.kind === 'send' ? ' tip-kind-reply' : ''}">${km.icon}${km.label()}</span>` : '';
2459
2518
  const enc = m.encrypt != null ? `<span class="tip-flag">${m.encrypt ? t('messages.tag.encrypted') : t('messages.tag.plain')}</span>` : '';
2460
2519
  const mode = m.chatmode ? `<span class="tip-flag">${m.chatmode === 'proactive' ? t('messages.tag.proactive') : (m.chatmode === 'inject' ? t('messages.tag.inject') : t('messages.tag.responsive'))}</span>` : '';
2520
+ const chan = channelTagHtml(m.channel, 'tip-flag tip-flag-channel');
2461
2521
  const peer = m.peer ? esc(shortAid(m.peer)) : '';
2462
2522
  const text = esc(String(m.text).replace(/\n/g, ' ').slice(0, 140));
2463
2523
  const time = m.ts ? `<span class="tip-time">${fmtTime(m.ts)}</span>` : '';
2464
- h += `<div class="tip-row ${rcls}">${time}${arrow}${kh}${enc}${mode} <b>${peer}</b> ${text}</div>`;
2524
+ h += `<div class="tip-row ${rcls}">${time}${arrow}${chan}${kh}${enc}${mode} <b>${peer}</b> ${text}</div>`;
2465
2525
  }
2466
2526
  return h + '</div>';
2467
2527
  }
@@ -2857,6 +2917,14 @@ function card(label, value, valCls, sub) {
2857
2917
  }
2858
2918
 
2859
2919
  // ── Messages 视图 ──
2920
+ function msgChannelTone(channelType) {
2921
+ const normalized = String(channelType || '').trim().toLowerCase();
2922
+ if (normalized === 'qq') return 'qqbot';
2923
+ return ['aun', 'wechat', 'wecom', 'feishu', 'dingtalk', 'qqbot', 'daemon'].includes(normalized)
2924
+ ? normalized
2925
+ : 'other';
2926
+ }
2927
+
2860
2928
  function renderMsg(data) {
2861
2929
  if (!data) return;
2862
2930
  const aids = data.scopes || data.aids || [];
@@ -2889,7 +2957,7 @@ function renderMsg(data) {
2889
2957
  let aidsHtml = `<div class="col-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
2890
2958
  for (const a of aids) {
2891
2959
  const sel = a.aid === msgSel.aid ? ' sel' : '';
2892
- const name = a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown';
2960
+ const name = a.displayName || (a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown');
2893
2961
  const privateCount = Math.max(0, (a.peerCount || 0) - (a.groupCount || 0));
2894
2962
  const privateBit = tf('messages.privateCount', { count: privateCount });
2895
2963
  const groupBit = a.groupCount ? ` · ${tf('messages.groupCount', { count: a.groupCount })}` : '';
@@ -2933,7 +3001,9 @@ function renderMsg(data) {
2933
3001
  ? t('messages.tag.topic')
2934
3002
  : p.partition === 'legacy' ? t('messages.tag.legacy') : t('messages.tag.main');
2935
3003
  const partitionMark = `<span class="msg-partition-mark msg-partition-${esc(p.partition || 'main')}">${esc(partitionLabel)}</span>`;
2936
- const channelTag = channelLabel ? `<span class="msg-tag">${esc(channelLabel)}</span>` : '';
3004
+ const channelTag = channelLabel
3005
+ ? `<span class="msg-tag msg-channel msg-channel-${msgChannelTone(p.channelType)}">${esc(channelLabel)}</span>`
3006
+ : '';
2937
3007
  peersHtml += `<div class="list-item${sel}" data-peer="${esc(p.peerId)}">` +
2938
3008
  `<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
2939
3009
  `<div class="sub">${p.partition === 'thread' ? `${esc(shortId(displayName))} · ` : ''}↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
@@ -3020,6 +3090,20 @@ function renderMsgSessionHeader(context) {
3020
3090
  }
3021
3091
 
3022
3092
  // ── Sessions 视图 ──
3093
+ function filterSessionTranscripts(transcripts, showAll, sourceType = 'all') {
3094
+ return (transcripts || []).filter(session => {
3095
+ const valid = showAll || (session.userMsgs || 0) >= 1;
3096
+ const sourceMatches = sourceType === 'all' || (session.sourceType || 'unknown') === sourceType;
3097
+ return valid && sourceMatches;
3098
+ });
3099
+ }
3100
+
3101
+ function renderSessionSourceBadge(sourceType) {
3102
+ const type = ['trigger', 'agent', 'cli', 'user'].includes(sourceType) ? sourceType : 'unknown';
3103
+ const label = t(`sessions.source.${type}`);
3104
+ return `<span class="session-source-badge source-${type}" title="${esc(label)}">${esc(label)}</span>`;
3105
+ }
3106
+
3023
3107
  function renderSession(data) {
3024
3108
  if (!data) return;
3025
3109
  const projects = data.projects || [];
@@ -3034,8 +3118,7 @@ function renderSession(data) {
3034
3118
  }
3035
3119
 
3036
3120
  // 有效筛选
3037
- const filtered = transcripts
3038
- .filter(t => !sessFilterNormal || (t.userMsgs || 0) >= 1);
3121
+ const filtered = filterSessionTranscripts(transcripts, sessShowAll, sessSourceFilter);
3039
3122
 
3040
3123
  // 左栏:过滤条 + 列表
3041
3124
  const projOpts = projects.map(p =>
@@ -3050,18 +3133,25 @@ function renderSession(data) {
3050
3133
  if (availableBaseagents.codex) {
3051
3134
  baseagentOpts += `<option value="codex"${sessSel.baseagent === 'codex' ? ' selected' : ''}>Codex</option>`;
3052
3135
  }
3136
+ const sourceTypes = ['all', 'user', 'agent', 'trigger', 'cli', 'unknown'];
3137
+ const sourceOpts = sourceTypes.map(type => {
3138
+ const label = type === 'all' ? t('sessions.filter.sourceAll') : t(`sessions.source.${type}`);
3139
+ return `<option value="${type}"${sessSourceFilter === type ? ' selected' : ''}>${esc(label)}</option>`;
3140
+ }).join('');
3053
3141
 
3054
- const normalCount = transcripts.filter(t => (t.userMsgs || 0) >= 1).length;
3142
+ const filterLabelKey = sessShowAll ? 'sessions.filter.all' : 'sessions.filter.valid';
3143
+ const filterTitleKey = sessShowAll ? 'sessions.filter.allTitle' : 'sessions.filter.validTitle';
3055
3144
  let listHtml = '<div class="sess-filter">' +
3056
3145
  '<div class="sess-select-row">' +
3057
3146
  `<select id="sess-baseagent" title="Base Agent">${baseagentOpts}</select>` +
3058
3147
  `<select id="sess-project" title="${t('sessions.header.project')}">${projOpts}</select>` +
3148
+ `<select id="sess-source-type" title="${t('sessions.filter.sourceTitle')}">${sourceOpts}</select>` +
3059
3149
  '</div>' +
3060
3150
  '<div class="sess-filter-row">' +
3061
- `<div class="sess-count">${tf('sessions.count', { filtered: filtered.length, total: transcripts.length })}</div>` +
3062
- `<label class="sess-switch" title="${t('sessions.filter.validTitle')}">` +
3063
- `<span>${t('sessions.filter.valid')}</span>` +
3064
- `<input id="sess-filter-toggle" type="checkbox"${sessFilterNormal ? ' checked' : ''}>` +
3151
+ `<div class="sess-count">${tf('sessions.count', { count: filtered.length })}</div>` +
3152
+ `<label class="sess-switch" title="${t(filterTitleKey)}">` +
3153
+ `<span>${t(filterLabelKey)}</span>` +
3154
+ `<input id="sess-filter-toggle" type="checkbox"${sessShowAll ? ' checked' : ''}>` +
3065
3155
  '<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>' +
3066
3156
  '</label>' +
3067
3157
  '</div></div>' +
@@ -3076,12 +3166,13 @@ function renderSession(data) {
3076
3166
  let badge = '';
3077
3167
  if (t.bound) {
3078
3168
  const dot = t.online ? '<span class="dot on"></span>' : '<span class="dot idle"></span>';
3079
- badge = `<span class="bind-badge">${dot}${esc(t.boundChannel || '')}·${esc(shortAid(t.boundPeer || ''))}</span>`;
3169
+ badge = `<span class="bind-badge">${dot}${esc(t.boundChannel || '')}·${esc(shortAid(t.boundSelf || ''))}</span>`;
3080
3170
  }
3171
+ const sourceBadge = renderSessionSourceBadge(t.sourceType);
3081
3172
  const msgs = `<span class="msg-count" title="${tf('sessions.messageCountTitle', { user: t.userMsgs || 0, total: t.totalMsgs || 0 })}">💬 ${t.userMsgs || 0}/${t.totalMsgs || 0}</span>`;
3082
3173
  listHtml += `<div class="list-item${sel}" data-sid="${esc(t.id)}">` +
3083
3174
  `<div class="name">${esc(title)}</div>` +
3084
- `<div class="sub">${fmtAgo(t.lastActivity)} · ${msgs}${t.gitBranch ? ' · ' + esc(t.gitBranch) : ''}${badge}</div>` +
3175
+ `<div class="sub">${fmtAgo(t.lastActivity)} · ${msgs} · ${sourceBadge}${badge}</div>` +
3085
3176
  '</div>';
3086
3177
  }
3087
3178
  listHtml += '</div>';
@@ -3101,7 +3192,12 @@ function renderSession(data) {
3101
3192
  subscribe('session', { project: sessSel.project, baseagent: sessSel.baseagent });
3102
3193
  };
3103
3194
  const filterToggle = $('#sess-filter-toggle');
3104
- if (filterToggle) filterToggle.onchange = () => { sessFilterNormal = filterToggle.checked; renderSession(state.session); };
3195
+ if (filterToggle) filterToggle.onchange = () => { sessShowAll = filterToggle.checked; renderSession(state.session); };
3196
+ const sourceTypeSelect = $('#sess-source-type');
3197
+ if (sourceTypeSelect) sourceTypeSelect.onchange = () => {
3198
+ sessSourceFilter = sourceTypeSelect.value;
3199
+ renderSession(state.session);
3200
+ };
3105
3201
  $('#sess-list').querySelectorAll('.list-item').forEach(item => {
3106
3202
  item.onclick = () => { sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent }; subscribe('session', sessSel); };
3107
3203
  });
@@ -3220,6 +3316,7 @@ const CAT_META = {
3220
3316
  tool_call: { label: () => t('sessions.turnType.toolCall'), icon: '🟣' },
3221
3317
  tool_result: { label: () => t('sessions.turnType.toolResult'), icon: '🟠' },
3222
3318
  msg_send: { label: () => t('sessions.turnType.msgSend'), icon: '📤' },
3319
+ subtask: { label: () => t('sessions.turnType.subtask'), icon: '◆' },
3223
3320
  system: { label: () => t('sessions.turnType.system'), icon: '⚪' },
3224
3321
  };
3225
3322
 
@@ -3236,7 +3333,7 @@ function renderSessHeader(h, baseagent) {
3236
3333
  let bind = '';
3237
3334
  if (h.bound) {
3238
3335
  const dot = h.online ? `<span class="dot on"></span>${t('common.online')}` : `<span class="dot idle"></span>${t('common.offline')}`;
3239
- bind = `<span class="sh-stat">🔗 ${esc(h.boundChannel || '')} · ${esc(shortAid(h.boundPeer || ''))} ${dot}</span>`;
3336
+ bind = `<span class="sh-stat">🔗 ${esc(h.boundChannel || '')} · ${esc(shortAid(h.boundSelf || ''))} ${dot}</span>`;
3240
3337
  }
3241
3338
  return '<div class="sess-header">' +
3242
3339
  '<div class="sh-title-row">' +
@@ -3269,6 +3366,7 @@ function renderCatBar(counts) {
3269
3366
  ['tool_call', counts.toolCall],
3270
3367
  ['tool_result', counts.toolResult],
3271
3368
  ['msg_send', counts.msgSend],
3369
+ ['subtask', counts.subtask],
3272
3370
  ];
3273
3371
  let s = '<div class="sh-cats">';
3274
3372
  for (const [cat, n] of items) {
@@ -3316,6 +3414,28 @@ function renderBlocks(blocks) {
3316
3414
  } else if (b.kind === 'tool_result') {
3317
3415
  const cls = b.isError ? 'blk-result err' : 'blk-result';
3318
3416
  out += `<details class="blk ${cls}" open><summary>${b.isError ? '✗ ' + t('sessions.block.resultError') : '↳ ' + t('sessions.block.result')}</summary><pre class="result-body">${esc(b.text)}</pre></details>`;
3417
+ } else if (b.kind === 'subtask') {
3418
+ const knownStatuses = ['running', 'completed', 'failed', 'stopped', 'killed'];
3419
+ const status = knownStatuses.includes(String(b.status)) ? String(b.status) : 'running';
3420
+ const statusLabel = t(`sessions.subtask.status.${status}`);
3421
+ const meta = [];
3422
+ if (b.subagentType) meta.push(esc(b.subagentType));
3423
+ if (b.toolUses) meta.push(esc(tf('sessions.subtask.tools', { count: fmtNum(b.toolUses) })));
3424
+ if (b.totalTokens) meta.push(esc(tf('sessions.subtask.tokens', { count: fmtNum(b.totalTokens) })));
3425
+ if (b.durationMs) meta.push(esc(fmtDur(b.durationMs / 1000)));
3426
+ if (b.terminalReason) meta.push(esc(tf('sessions.subtask.reason', { reason: b.terminalReason })));
3427
+ const progress = b.progress && b.progress !== b.description
3428
+ ? `<div class="subtask-progress">${esc(b.progress)}</div>`
3429
+ : '';
3430
+ out += `<div class="blk blk-subtask status-${status}">` +
3431
+ '<div class="subtask-main">' +
3432
+ `<span class="subtask-status-dot" aria-hidden="true"></span>` +
3433
+ `<span class="subtask-status">${esc(statusLabel)}</span>` +
3434
+ `<span class="subtask-title">${esc(b.description || b.taskId || '')}</span>` +
3435
+ '</div>' +
3436
+ (meta.length ? `<div class="subtask-meta">${meta.join('<span>·</span>')}</div>` : '') +
3437
+ progress +
3438
+ '</div>';
3319
3439
  }
3320
3440
  }
3321
3441
  return out;
@@ -5474,12 +5594,18 @@ function triggerOriginalEditState(t) {
5474
5594
  const id = triggerIdOf(t);
5475
5595
  const agentAid = triggerAgentOf(t);
5476
5596
  const rawFeedbackStrategy = String(feedback.strategy ?? t?.feedbackStrategy ?? 'target');
5597
+ const executionType = String(execution.type ?? t?.executionType ?? 'target_session');
5598
+ const agentBaseagent = String(triggerAgentInfo(agentAid)?.baseagent ?? triggerActiveBaseagentForAgent(agentAid));
5599
+ const persistedBaseagent = typeof execution.baseagent === 'string' && execution.baseagent.trim()
5600
+ ? execution.baseagent.trim()
5601
+ : (typeof t?.baseagent === 'string' && t.baseagent.trim() ? t.baseagent.trim() : '');
5602
+ const baseagent = persistedBaseagent || agentBaseagent;
5477
5603
  return {
5478
5604
  id,
5479
5605
  agentAid,
5480
5606
  name: String(definition?.name ?? t?.name ?? t?.label ?? ''),
5481
5607
  enabled: triggerIsEnabled(t),
5482
- executionType: String(execution.type ?? t?.executionType ?? 'target_session'),
5608
+ executionType,
5483
5609
  scriptPath: String(execution.script?.path ?? ''),
5484
5610
  scriptRuntime: String(execution.script?.runtime ?? 'node'),
5485
5611
  scriptTimeoutMs: Number(execution.script?.timeoutMs ?? 30_000),
@@ -5487,7 +5613,11 @@ function triggerOriginalEditState(t) {
5487
5613
  scheduleValue: source.scheduleValue,
5488
5614
  eventFilter: definition?.source?.type === 'event' ? (definition.source.filter ?? null) : null,
5489
5615
  timezone: String(source.timezone ?? ''),
5490
- baseagent: String(triggerAgentInfo(agentAid)?.baseagent ?? t?.baseagent ?? triggerActiveBaseagentForAgent(agentAid)),
5616
+ baseagent,
5617
+ agentBaseagent,
5618
+ baseagentMismatch: executionType === 'trigger_session'
5619
+ && Boolean(persistedBaseagent)
5620
+ && persistedBaseagent !== agentBaseagent,
5491
5621
  prompt: execution.type === 'script' ? '' : triggerPrompt(definition, t),
5492
5622
  model: String(execution.model ?? t?.model ?? ''),
5493
5623
  effort: String(execution.effort ?? t?.effort ?? ''),
@@ -6710,6 +6840,7 @@ function openTriggerEditDrawer(id, agent) {
6710
6840
  `<label class="ag-edit-field trig-script-field"><span>${t('triggers.field.scriptRuntime')}</span><select id="trig-edit-script-runtime">${triggerOptionHtml(['node', 'python', 'bash'], original.scriptRuntime, null)}</select></label>` +
6711
6841
  `<label class="ag-edit-field trig-script-field trig-script-path-field"><span>${t('triggers.field.scriptPath')}</span><input id="trig-edit-script-path" type="text" autocomplete="off" spellcheck="false" value="${esc(original.scriptPath)}" placeholder="${t('triggers.placeholder.scriptPath')}"></label>` +
6712
6842
  `</div>` +
6843
+ (original.baseagentMismatch ? `<div class="trig-baseagent-note">${esc(tf('triggers.baseagent.mismatch', { trigger: original.baseagent, agent: original.agentBaseagent }))}</div>` : '') +
6713
6844
  `<div class="trig-script-feedback">` +
6714
6845
  `<div class="trig-feedback-templates-title">${t('triggers.field.outputTemplate')}</div>` +
6715
6846
  `<div class="trig-feedback-templates">` +
@@ -6990,6 +7121,13 @@ function updateThemeButton() {
6990
7121
  }
6991
7122
 
6992
7123
  function disposeWindowChart(key) {
7124
+ const observerKey = '__ecwebChartResizeObserver_' + key;
7125
+ const observer = window[observerKey];
7126
+ if (observer) {
7127
+ observer.disconnect();
7128
+ window[observerKey] = null;
7129
+ }
7130
+
6993
7131
  const chart = window[key];
6994
7132
  if (chart && typeof chart.dispose === 'function') {
6995
7133
  chart.dispose();
@@ -7048,6 +7186,28 @@ function initTheme() {
7048
7186
  }
7049
7187
  }
7050
7188
 
7189
+ function chartThemeValue(name, fallback) {
7190
+ var value = getComputedStyle(document.documentElement).getPropertyValue(name).trim();
7191
+ return value || fallback;
7192
+ }
7193
+
7194
+ function chartTooltipOptions(isDark) {
7195
+ return {
7196
+ backgroundColor: chartThemeValue('--bg2', isDark ? '#161b22' : '#ffffff'),
7197
+ borderColor: chartThemeValue('--border', isDark ? '#30363d' : '#e2e6ed'),
7198
+ borderWidth: 1,
7199
+ padding: [10, 12],
7200
+ textStyle: {
7201
+ color: chartThemeValue('--fg', isDark ? '#e6edf3' : '#1a202c'),
7202
+ fontSize: 12,
7203
+ lineHeight: 20,
7204
+ },
7205
+ extraCssText: 'border-radius:8px;box-shadow:' + (isDark
7206
+ ? '0 14px 34px rgba(0,0,0,0.42)'
7207
+ : '0 14px 34px rgba(31,41,55,0.16)') + ';',
7208
+ };
7209
+ }
7210
+
7051
7211
  // ── Usage 相关函数 ──
7052
7212
  function fmtTokens(n) {
7053
7213
  if (n >= 1e6) return (n / 1e6).toFixed(1) + 'M';
@@ -7255,12 +7415,19 @@ function formatDatetimeLocal(date) {
7255
7415
  }
7256
7416
 
7257
7417
  // 模型访问明细查询
7418
+ var _detailInited = false;
7419
+
7258
7420
  function initDetailQuery() {
7259
7421
  // 填充Agent选择器
7260
7422
  loadDetailAgentList();
7261
7423
  // 填充Model选择器(按上面总览的时间范围)
7262
7424
  loadDetailModelList();
7263
7425
 
7426
+ // 事件只绑一次:initOverviewFilters 会在每次切回 Overview 子 Tab 时重复调用,
7427
+ // 重复绑定会让一次 change 触发 N 次并发查询,慢的旧响应可能覆盖新响应(表现为“切换无效”)。
7428
+ if (_detailInited) return;
7429
+ _detailInited = true;
7430
+
7264
7431
  // 绑定分页大小变化
7265
7432
  const pageSizeEl = $('#detail-page-size');
7266
7433
  if (pageSizeEl) {
@@ -7347,10 +7514,8 @@ async function loadDetailAgentList() {
7347
7514
  option.textContent = a.agent_name || a.agent_aid.split('.')[0];
7348
7515
  selectEl.appendChild(option);
7349
7516
  });
7350
- // 默认选中第一个agent
7351
- if (agents.length > 0) {
7352
- selectEl.value = agents[0].agent_aid;
7353
- }
7517
+ // 默认查询全部智能体(首项 value=""),不要自动切到第一个 Agent。
7518
+ selectEl.value = '';
7354
7519
  // 加载完成后自动查询一次
7355
7520
  queryDetailUsage();
7356
7521
  }
@@ -7394,7 +7559,11 @@ async function loadDetailModelList() {
7394
7559
  } catch {}
7395
7560
  }
7396
7561
 
7562
+ var _detailQuerySeq = 0;
7563
+
7397
7564
  async function queryDetailUsage() {
7565
+ // 请求序号:并发查询时只认最后一次的响应,避免慢的旧响应覆盖新结果
7566
+ const seq = ++_detailQuerySeq;
7398
7567
  // 使用总览的时间范围
7399
7568
  const timeRange = window._currentOverviewTimeRange || {};
7400
7569
  const fromTs = timeRange.fromTs;
@@ -7421,13 +7590,16 @@ async function queryDetailUsage() {
7421
7590
  const resp = await fetch(authApiUrl('api/stats/detail?' + params.toString()), {
7422
7591
  headers: { Authorization: 'Bearer ' + localStorage.getItem(TOKEN_KEY) }
7423
7592
  });
7593
+ if (seq !== _detailQuerySeq) return; // 已有更新的查询发出,丢弃本次结果
7424
7594
  if (!resp.ok) {
7425
7595
  showDetailError(t('usage.detail.error'));
7426
7596
  return;
7427
7597
  }
7428
7598
  const result = await resp.json();
7599
+ if (seq !== _detailQuerySeq) return;
7429
7600
  renderDetailTable(result.data, result.total, page, pageSize);
7430
7601
  } catch {
7602
+ if (seq !== _detailQuerySeq) return;
7431
7603
  showDetailError(t('usage.detail.error'));
7432
7604
  }
7433
7605
  }
@@ -7627,9 +7799,11 @@ function initExplorer() {
7627
7799
  // 初始化时间范围选择
7628
7800
  initExplorerTimeFilters();
7629
7801
 
7630
- // 绑定查询按钮
7631
- var btn = $('#exp-query-btn');
7632
- if (btn) btn.onclick = runExplorerQuery;
7802
+ // 模型、粒度下拉框变更后立即查询,不再需要额外的查询按钮。
7803
+ var modelFilter = $('#exp-model');
7804
+ if (modelFilter) modelFilter.addEventListener('change', runExplorerQuery);
7805
+ var granularityFilter = $('#exp-granularity');
7806
+ if (granularityFilter) granularityFilter.addEventListener('change', runExplorerQuery);
7633
7807
 
7634
7808
  // Load sidebar lists
7635
7809
  loadExplorerSidebar();
@@ -8076,7 +8250,10 @@ async function runExplorerQuery() {
8076
8250
  _explorerChart = echarts.init(chartEl, isDark ? 'dark' : null);
8077
8251
  var periods = data.map(function(r) { return r.period; });
8078
8252
  _explorerChart.setOption({
8079
- tooltip: { trigger: 'axis' },
8253
+ tooltip: {
8254
+ trigger: 'axis',
8255
+ ...chartTooltipOptions(isDark),
8256
+ },
8080
8257
  legend: { data: [t('usage.card.input'), t('usage.card.output')], top: 0, textStyle: { fontSize: 11 } },
8081
8258
  grid: { top: 30, bottom: 30, left: 60, right: 16 },
8082
8259
  xAxis: { type: 'category', data: periods, axisLabel: { fontSize: 10, rotate: 30 } },
@@ -8122,6 +8299,52 @@ function disposeMonitorCharts() {
8122
8299
  ['_monCpu', '_monMem', '_monDisk', '_monNet'].forEach(disposeWindowChart);
8123
8300
  }
8124
8301
 
8302
+ function scheduleAfterLayout(callback) {
8303
+ if (typeof requestAnimationFrame === 'function') return requestAnimationFrame(callback);
8304
+ return setTimeout(callback, 16);
8305
+ }
8306
+
8307
+ function resizeChartAfterLayout(chart) {
8308
+ scheduleAfterLayout(function () {
8309
+ if (!chart || (typeof chart.isDisposed === 'function' && chart.isDisposed())) return;
8310
+ chart.resize();
8311
+ });
8312
+ }
8313
+
8314
+ function observeMonitorChartSize(el, varKey, chart) {
8315
+ const observerKey = '__ecwebChartResizeObserver_' + varKey;
8316
+ const previous = window[observerKey];
8317
+ if (previous) previous.disconnect();
8318
+
8319
+ if (typeof ResizeObserver === 'function') {
8320
+ const observer = new ResizeObserver(function () {
8321
+ if (window[varKey] !== chart || (typeof chart.isDisposed === 'function' && chart.isDisposed())) {
8322
+ observer.disconnect();
8323
+ return;
8324
+ }
8325
+ chart.resize();
8326
+ });
8327
+ observer.observe(el);
8328
+ window[observerKey] = observer;
8329
+ }
8330
+
8331
+ resizeChartAfterLayout(chart);
8332
+ }
8333
+
8334
+ function deferMonitorChartUntilVisible(el, render) {
8335
+ let attempts = 0;
8336
+ const retry = function () {
8337
+ if (!el.isConnected) return;
8338
+ if (el.clientWidth > 0 && el.clientHeight > 0) {
8339
+ render();
8340
+ return;
8341
+ }
8342
+ // The AUN proxy can deliver the first snapshot before its page layout settles.
8343
+ if (++attempts < 30) scheduleAfterLayout(retry);
8344
+ };
8345
+ scheduleAfterLayout(retry);
8346
+ }
8347
+
8125
8348
  function bindMonRangeTabs(wrap) {
8126
8349
  var tabs = wrap.querySelectorAll('.mon-range');
8127
8350
  if (!tabs.length) return;
@@ -8289,8 +8512,18 @@ function renderMonitor(data) {
8289
8512
  function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
8290
8513
  var el = $('#' + elId);
8291
8514
  if (!el) return;
8292
- if (!window[varKey]) window[varKey] = echarts.init(el, isDark ? 'dark' : null);
8293
- window[varKey].setOption({
8515
+ const render = function () {
8516
+ var chart = window[varKey];
8517
+ if (chart && typeof chart.getDom === 'function' && chart.getDom() !== el) {
8518
+ disposeWindowChart(varKey);
8519
+ chart = null;
8520
+ }
8521
+ if (!chart) {
8522
+ chart = echarts.init(el, isDark ? 'dark' : null);
8523
+ window[varKey] = chart;
8524
+ observeMonitorChartSize(el, varKey, chart);
8525
+ }
8526
+ chart.setOption({
8294
8527
  title: { text: title, left: 'center', top: 4, textStyle: { fontSize: 12, color: isDark ? '#e6edf3' : '#1a202c' } },
8295
8528
  legend: {
8296
8529
  show: series.length > 0,
@@ -8303,6 +8536,7 @@ function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
8303
8536
  },
8304
8537
  tooltip: {
8305
8538
  trigger: 'axis',
8539
+ ...chartTooltipOptions(isDark),
8306
8540
  formatter: function (params) {
8307
8541
  var lines = [params[0].axisValue];
8308
8542
  params.forEach(function (pt) {
@@ -8330,7 +8564,15 @@ function monDualLine(elId, varKey, times, isDark, title, series, fmtY, yRange) {
8330
8564
  };
8331
8565
  }),
8332
8566
  animation: false,
8333
- });
8567
+ });
8568
+ resizeChartAfterLayout(chart);
8569
+ };
8570
+
8571
+ if (el.clientWidth <= 0 || el.clientHeight <= 0) {
8572
+ deferMonitorChartUntilVisible(el, render);
8573
+ return;
8574
+ }
8575
+ render();
8334
8576
  }
8335
8577
 
8336
8578
  function renderRoles(data) {