evolclaw-web 1.2.2 → 1.2.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.
- package/dist/server.js +11 -2
- package/dist/sources/aid.js +4 -2
- package/dist/sources/baseagent-detector.js +72 -0
- package/dist/sources/msg.js +366 -31
- package/dist/sources/session-codex.js +618 -0
- package/dist/sources/session.js +24 -11
- package/dist/sources/system.js +37 -2
- package/dist/static/app.js +169 -71
- package/dist/static/index.html +1 -1
- package/dist/static/style.css +1 -0
- package/package.json +1 -1
package/dist/sources/system.js
CHANGED
|
@@ -7,22 +7,57 @@
|
|
|
7
7
|
*
|
|
8
8
|
* subscribe: 30s 轮询 + JSON diff,仅变化时 push。
|
|
9
9
|
*/
|
|
10
|
+
import fs from 'fs';
|
|
11
|
+
import path from 'path';
|
|
10
12
|
import { resolvePaths } from '../paths.js';
|
|
11
13
|
import { ipcQuery } from '../ipc-client.js';
|
|
14
|
+
function readDefaultBaseagents() {
|
|
15
|
+
try {
|
|
16
|
+
const p = resolvePaths();
|
|
17
|
+
const defaultsPath = path.join(p.root, 'agents', 'defaults.json');
|
|
18
|
+
const raw = JSON.parse(fs.readFileSync(defaultsPath, 'utf-8'));
|
|
19
|
+
const baseagents = raw?.baseagents;
|
|
20
|
+
if (!baseagents || typeof baseagents !== 'object')
|
|
21
|
+
return [];
|
|
22
|
+
const active = typeof raw.active_baseagent === 'string' ? raw.active_baseagent : null;
|
|
23
|
+
return Object.entries(baseagents).map(([name, cfg]) => {
|
|
24
|
+
const c = cfg && typeof cfg === 'object' ? cfg : {};
|
|
25
|
+
return {
|
|
26
|
+
name,
|
|
27
|
+
active: name === active,
|
|
28
|
+
model: c.model ?? null,
|
|
29
|
+
effort: c.effort ?? c.reasoning ?? null,
|
|
30
|
+
};
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
12
37
|
async function menuExec(payload) {
|
|
13
38
|
const p = resolvePaths();
|
|
14
39
|
const r = await ipcQuery(p.socket, { type: 'menu.exec', payload }, 5000);
|
|
15
40
|
return r?.ok ? r.response : null;
|
|
16
41
|
}
|
|
17
42
|
async function buildSnapshot() {
|
|
18
|
-
const [listResp, sysResp] = await Promise.all([
|
|
43
|
+
const [listResp, sysResp, checkResp] = await Promise.all([
|
|
19
44
|
menuExec({ type: 'menu.list', id: 'sys-list' }),
|
|
20
45
|
menuExec({ type: 'menu.query', id: 'sys-q', name: 'system' }),
|
|
46
|
+
menuExec({ type: 'menu.action', id: 'sys-check', name: 'system', action: 'check' }),
|
|
21
47
|
]);
|
|
22
48
|
const daemonRunning = listResp !== null;
|
|
23
49
|
// sysResp 形如 { type:'menu.response', id, name, data | error }
|
|
24
50
|
const system = sysResp?.data ?? null;
|
|
25
|
-
|
|
51
|
+
// baseagents:以 defaults.json 的 active/model/effort 为主,
|
|
52
|
+
// 再从后端返回的 baseagents([{name,version}])按 name 补上 CLI 版本号。
|
|
53
|
+
let baseagents = readDefaultBaseagents();
|
|
54
|
+
if (system && Array.isArray(system.baseagents)) {
|
|
55
|
+
const verByName = new Map(system.baseagents.map((b) => [b.name, b.version ?? null]));
|
|
56
|
+
baseagents = baseagents.map((b) => ({ ...b, version: verByName.get(b.name) ?? null }));
|
|
57
|
+
}
|
|
58
|
+
// checkResp 包含 evolagents 等健康检查数据
|
|
59
|
+
const check = checkResp?.data ?? null;
|
|
60
|
+
return { daemonRunning, system: system ? { ...system, baseagents } : system, upgrade: null, check };
|
|
26
61
|
}
|
|
27
62
|
export const systemSource = {
|
|
28
63
|
kind: 'system',
|
package/dist/static/app.js
CHANGED
|
@@ -70,7 +70,6 @@ const translations = {
|
|
|
70
70
|
'agents.stats.online': '在线',
|
|
71
71
|
'agents.stats.offline': '离线',
|
|
72
72
|
'agents.stats.messages': 'Messages',
|
|
73
|
-
'agents.stats.traffic': 'Traffic',
|
|
74
73
|
'agents.stats.version': 'Version',
|
|
75
74
|
'agents.stats.pid': 'PID',
|
|
76
75
|
'agents.stats.uptime': 'Uptime',
|
|
@@ -84,9 +83,9 @@ const translations = {
|
|
|
84
83
|
'agents.th.runtime': '运行',
|
|
85
84
|
'agents.th.received': '收',
|
|
86
85
|
'agents.th.sent': '发',
|
|
87
|
-
'agents.th.
|
|
88
|
-
'agents.th.
|
|
89
|
-
'agents.th.
|
|
86
|
+
'agents.th.completed': '完',
|
|
87
|
+
'agents.th.errors': '错',
|
|
88
|
+
'agents.th.interrupts': '断',
|
|
90
89
|
'agents.th.lastActivity': '最后活动',
|
|
91
90
|
'agents.th.operations': '操作',
|
|
92
91
|
'agents.th.projectPath': '项目路径',
|
|
@@ -114,11 +113,11 @@ const translations = {
|
|
|
114
113
|
'agents.op.viewAgentMd': '查看 agent.md ↗',
|
|
115
114
|
|
|
116
115
|
// Messages view
|
|
117
|
-
'messages.colTitle.aid': '
|
|
118
|
-
'messages.colTitle.peers': '
|
|
116
|
+
'messages.colTitle.aid': 'Agent',
|
|
117
|
+
'messages.colTitle.peers': 'Chats',
|
|
119
118
|
'messages.colTitle.all': 'All',
|
|
120
|
-
'messages.empty.selectAid': '← 选择一个
|
|
121
|
-
'messages.empty.selectToView': '选择
|
|
119
|
+
'messages.empty.selectAid': '← 选择一个 Agent',
|
|
120
|
+
'messages.empty.selectToView': '选择 Agent 查看消息',
|
|
122
121
|
'messages.empty.noMessages': '暂无消息',
|
|
123
122
|
'messages.tag.group': '群聊',
|
|
124
123
|
'messages.tag.encrypted': '🔒密文',
|
|
@@ -345,7 +344,6 @@ const translations = {
|
|
|
345
344
|
'agents.stats.online': 'online',
|
|
346
345
|
'agents.stats.offline': 'offline',
|
|
347
346
|
'agents.stats.messages': 'Messages',
|
|
348
|
-
'agents.stats.traffic': 'Traffic',
|
|
349
347
|
'agents.stats.version': 'Version',
|
|
350
348
|
'agents.stats.pid': 'PID',
|
|
351
349
|
'agents.stats.uptime': 'Uptime',
|
|
@@ -359,9 +357,9 @@ const translations = {
|
|
|
359
357
|
'agents.th.runtime': 'Runtime',
|
|
360
358
|
'agents.th.received': 'Recv',
|
|
361
359
|
'agents.th.sent': 'Sent',
|
|
362
|
-
'agents.th.
|
|
363
|
-
'agents.th.
|
|
364
|
-
'agents.th.
|
|
360
|
+
'agents.th.completed': 'Done',
|
|
361
|
+
'agents.th.errors': 'Err',
|
|
362
|
+
'agents.th.interrupts': 'Int',
|
|
365
363
|
'agents.th.lastActivity': 'Last Activity',
|
|
366
364
|
'agents.th.operations': 'Operations',
|
|
367
365
|
'agents.th.projectPath': 'Project Path',
|
|
@@ -389,11 +387,11 @@ const translations = {
|
|
|
389
387
|
'agents.op.viewAgentMd': 'View agent.md ↗',
|
|
390
388
|
|
|
391
389
|
// Messages view
|
|
392
|
-
'messages.colTitle.aid': '
|
|
393
|
-
'messages.colTitle.peers': '
|
|
390
|
+
'messages.colTitle.aid': 'Agent',
|
|
391
|
+
'messages.colTitle.peers': 'Chats',
|
|
394
392
|
'messages.colTitle.all': 'All',
|
|
395
|
-
'messages.empty.selectAid': '← Select
|
|
396
|
-
'messages.empty.selectToView': 'Select
|
|
393
|
+
'messages.empty.selectAid': '← Select Agent',
|
|
394
|
+
'messages.empty.selectToView': 'Select Agent to view messages',
|
|
397
395
|
'messages.empty.noMessages': 'No messages',
|
|
398
396
|
'messages.tag.group': 'Group',
|
|
399
397
|
'messages.tag.encrypted': '🔒Encrypted',
|
|
@@ -687,20 +685,44 @@ function connect() {
|
|
|
687
685
|
ws.onopen = () => {
|
|
688
686
|
setConnStatus('● ' + t('status.connected'), 'ok');
|
|
689
687
|
reconnectDelay = 1000;
|
|
690
|
-
|
|
688
|
+
// 获取可用的 baseagent
|
|
689
|
+
fetch(`${BASE}api/available-baseagents`)
|
|
690
|
+
.then(r => r.json())
|
|
691
|
+
.then(data => {
|
|
692
|
+
availableBaseagents = data;
|
|
693
|
+
// 如果当前没有选中 baseagent,默认选第一个可用的
|
|
694
|
+
if (!sessSel.baseagent) {
|
|
695
|
+
sessSel.baseagent = data.claude ? 'claude' : (data.codex ? 'codex' : null);
|
|
696
|
+
}
|
|
697
|
+
console.log('[ecweb] Available baseagents:', availableBaseagents, 'Selected:', sessSel.baseagent);
|
|
698
|
+
// 重新订阅当前视图(带上正确的参数)
|
|
699
|
+
if (currentView === 'session') {
|
|
700
|
+
subscribe('session', { sessionId: sessSel.sessionId, project: sessSel.project, baseagent: sessSel.baseagent });
|
|
701
|
+
} else {
|
|
702
|
+
subscribe(currentView, pendingSub || {});
|
|
703
|
+
}
|
|
704
|
+
})
|
|
705
|
+
.catch(err => {
|
|
706
|
+
console.warn('[ecweb] Failed to fetch available-baseagents:', err);
|
|
707
|
+
// 失败时默认使用 claude
|
|
708
|
+
availableBaseagents = { claude: true, codex: false };
|
|
709
|
+
if (!sessSel.baseagent) sessSel.baseagent = 'claude';
|
|
710
|
+
subscribe(currentView, pendingSub || {});
|
|
711
|
+
});
|
|
691
712
|
};
|
|
692
713
|
|
|
693
714
|
ws.onmessage = (ev) => {
|
|
694
715
|
let msg;
|
|
695
716
|
try { msg = JSON.parse(ev.data); } catch { return; }
|
|
696
717
|
if (msg.type === 'pong') return;
|
|
697
|
-
if (msg.type === 'error') { console.warn('
|
|
718
|
+
if (msg.type === 'error') { console.warn('[ecweb] Server error:', msg.message); return; }
|
|
698
719
|
if (msg.type === 'menu.response') {
|
|
699
720
|
const pend = _menuPending[msg.requestId];
|
|
700
721
|
if (pend) { delete _menuPending[msg.requestId]; pend.resolve(msg.data); }
|
|
701
722
|
return;
|
|
702
723
|
}
|
|
703
724
|
if (msg.type === 'snapshot' || msg.type === 'delta') {
|
|
725
|
+
console.log('[ecweb] Received', msg.type, 'for view:', msg.view, 'currentView:', currentView);
|
|
704
726
|
// system 视图保留客户端写入的 check/upgrade,防止 3s 轮询覆盖
|
|
705
727
|
if (msg.view === 'system' && state.system) {
|
|
706
728
|
state.system = {
|
|
@@ -711,7 +733,10 @@ function connect() {
|
|
|
711
733
|
} else {
|
|
712
734
|
state[msg.view] = msg.data;
|
|
713
735
|
}
|
|
714
|
-
if (msg.view === currentView)
|
|
736
|
+
if (msg.view === currentView) {
|
|
737
|
+
console.log('[ecweb] Rendering view:', currentView, 'with data:', msg.data);
|
|
738
|
+
renderView(currentView);
|
|
739
|
+
}
|
|
715
740
|
}
|
|
716
741
|
};
|
|
717
742
|
|
|
@@ -732,7 +757,14 @@ function connect() {
|
|
|
732
757
|
function subscribe(view, params) {
|
|
733
758
|
pendingSub = params;
|
|
734
759
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
|
760
|
+
// session 视图添加 baseagent 参数
|
|
761
|
+
if (view === 'session' && sessSel.baseagent) {
|
|
762
|
+
params = { ...params, baseagent: sessSel.baseagent };
|
|
763
|
+
}
|
|
764
|
+
console.log('[ecweb] Subscribing:', view, params);
|
|
735
765
|
ws.send(JSON.stringify({ type: 'subscribe', view, ...params }));
|
|
766
|
+
} else {
|
|
767
|
+
console.warn('[ecweb] WebSocket not ready, subscription pending');
|
|
736
768
|
}
|
|
737
769
|
}
|
|
738
770
|
|
|
@@ -759,12 +791,13 @@ setInterval(() => {
|
|
|
759
791
|
|
|
760
792
|
// ── Tab 切换 ──
|
|
761
793
|
let msgSel = { aid: null, peer: null };
|
|
762
|
-
let sessSel = { sessionId: null, project: null };
|
|
794
|
+
let sessSel = { sessionId: null, project: null, baseagent: null };
|
|
763
795
|
let trigSel = { agent: null };
|
|
764
796
|
let sessSearch = '';
|
|
765
797
|
let sessFilterNormal = false; // true=只显示有效会话(userMsgs >= 2)
|
|
766
798
|
let sessChatMode = false; // false=完整视图,true=对话视图(折叠处理过程)
|
|
767
799
|
let monRange = '2m'; // Monitor 时间窗口:2m / 10m / 1h
|
|
800
|
+
let availableBaseagents = { claude: false, codex: false }; // 可用的 baseagent
|
|
768
801
|
|
|
769
802
|
function switchView(view) {
|
|
770
803
|
currentView = view;
|
|
@@ -773,7 +806,7 @@ function switchView(view) {
|
|
|
773
806
|
document.querySelectorAll('.view').forEach(v => v.classList.toggle('active', v.id === 'view-' + view));
|
|
774
807
|
// 切换时按当前选择恢复订阅
|
|
775
808
|
if (view === 'msg') subscribe('msg', { aid: msgSel.aid, peer: msgSel.peer });
|
|
776
|
-
else if (view === 'session') subscribe('session', { sessionId: sessSel.sessionId, project: sessSel.project });
|
|
809
|
+
else if (view === 'session') subscribe('session', { sessionId: sessSel.sessionId, project: sessSel.project, baseagent: sessSel.baseagent });
|
|
777
810
|
else if (view === 'cache') subscribe('cache', {});
|
|
778
811
|
else if (view === 'system') subscribe('system', {});
|
|
779
812
|
else if (view === 'triggers') subscribe('triggers', { agent: trigSel.agent });
|
|
@@ -805,6 +838,11 @@ function esc(s) {
|
|
|
805
838
|
return String(s == null ? '' : s).replace(/[&<>"]/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"' }[c]));
|
|
806
839
|
}
|
|
807
840
|
function shortAid(aid) { return String(aid || '').split('.')[0]; }
|
|
841
|
+
function shortId(id) {
|
|
842
|
+
const s = String(id || '');
|
|
843
|
+
if (!s) return 'unknown';
|
|
844
|
+
return s.includes('.') ? shortAid(s) : (s.length > 18 ? s.slice(0, 10) + '…' + s.slice(-5) : s);
|
|
845
|
+
}
|
|
808
846
|
function fmtBytes(b) {
|
|
809
847
|
if (!b) return '0';
|
|
810
848
|
const u = ['B', 'KB', 'MB', 'GB']; let i = Math.min(Math.floor(Math.log(b) / Math.log(1024)), 3);
|
|
@@ -867,7 +905,8 @@ function agentStateBadge(s, agStatus, connStatus) {
|
|
|
867
905
|
if ((s.processing || 0) > 0)
|
|
868
906
|
return `<span class="state-badge working">${t('status.working')}</span>`;
|
|
869
907
|
// 收到过消息 → 永远是 idle,不再回到 connected
|
|
870
|
-
if ((s.
|
|
908
|
+
if ((s.received || 0) > 0 || (s.sent || 0) > 0 || (s.completed || 0) > 0 || (s.errors || 0) > 0 || (s.interrupts || 0) > 0 ||
|
|
909
|
+
(s.messagesReceived || 0) > 0 || (s.messagesSent || 0) > 0)
|
|
871
910
|
return `<span class="state-badge idle">${t('status.idle')}</span>`;
|
|
872
911
|
return `<span class="state-badge connected">${t('status.connected')}</span>`;
|
|
873
912
|
}
|
|
@@ -1005,14 +1044,17 @@ function initMsgTipFloat() {
|
|
|
1005
1044
|
window.addEventListener('scroll', hideNow, true);
|
|
1006
1045
|
}
|
|
1007
1046
|
|
|
1008
|
-
// 顶部统计条:Gateway / AIDs total·connected·offline / Messages
|
|
1009
|
-
function agentsStatsBar(data, aids,
|
|
1047
|
+
// 顶部统计条:Gateway / AIDs total·connected·offline / Messages / Version·PID·Uptime
|
|
1048
|
+
function agentsStatsBar(data, aids, agentStats) {
|
|
1010
1049
|
const connected = aids.filter(a => (a.status || 'connected') === 'connected').length;
|
|
1011
1050
|
const offline = aids.length - connected;
|
|
1012
|
-
let recv = 0, sent = 0,
|
|
1013
|
-
for (const s of
|
|
1014
|
-
recv += s.
|
|
1015
|
-
|
|
1051
|
+
let recv = 0, sent = 0, done = 0, errors = 0, interrupts = 0;
|
|
1052
|
+
for (const s of agentStats) {
|
|
1053
|
+
recv += s.received || 0;
|
|
1054
|
+
sent += s.sent || 0;
|
|
1055
|
+
done += s.completed || 0;
|
|
1056
|
+
errors += s.errors || 0;
|
|
1057
|
+
interrupts += s.interrupts || 0;
|
|
1016
1058
|
}
|
|
1017
1059
|
const gws = [...new Set(aids.filter(a => a.gatewayUrl).map(a => a.gatewayUrl))];
|
|
1018
1060
|
const gw = gws.length ? gws.map(esc).join(', ') : '—';
|
|
@@ -1025,13 +1067,19 @@ function agentsStatsBar(data, aids, stats) {
|
|
|
1025
1067
|
h += `<span class="sg"><span class="sg-k">${t('agents.stats.gateway')}</span><span class="sg-gw">${gw}</span></span>`;
|
|
1026
1068
|
h += `<span class="sg"><span class="sg-k">${t('agents.stats.aids')}</span>${aids.length} ${t('agents.stats.total')} · <span class="num-on">${connected} ${t('agents.stats.online')}</span>` +
|
|
1027
1069
|
`${offline ? ` · <span class="num-off">${offline} ${t('agents.stats.offline')}</span>` : ''}</span>`;
|
|
1028
|
-
h += `<span class="sg"><span class="sg-k">${t('agents.stats.messages')}</span
|
|
1029
|
-
h += `<span class="sg"><span class="sg-k">${t('agents.stats.traffic')}</span><span class="in">↓${fmtBytes(bin)}</span> <span class="out">↑${fmtBytes(bout)}</span></span>`;
|
|
1070
|
+
h += `<span class="sg"><span class="sg-k">${t('agents.stats.messages')}</span>收 ${recv} · 发 ${sent} · 错 ${errors} · 断 ${interrupts} · 完 ${done}</span>`;
|
|
1030
1071
|
h += `<span class="sg"><span class="sg-k">${t('agents.stats.version')}</span>${esc(ver)} · <span class="sg-k">${t('agents.stats.pid')}</span>${pid} · <span class="sg-k">${t('agents.stats.uptime')}</span>${uptime}</span>`;
|
|
1031
1072
|
h += '</div>';
|
|
1032
1073
|
return h;
|
|
1033
1074
|
}
|
|
1034
1075
|
|
|
1076
|
+
function agentQueueHtml(s) {
|
|
1077
|
+
const processing = s.processing || 0;
|
|
1078
|
+
const queued = s.queued || 0;
|
|
1079
|
+
if (processing === 0 && queued === 0) return '<span class="ag-queue-empty">-</span>';
|
|
1080
|
+
return `<span class="ag-queue-num">${processing}/${queued}</span>`;
|
|
1081
|
+
}
|
|
1082
|
+
|
|
1035
1083
|
// 操作列 HTML(启用页):停止/启动 + 清空队列(conditional) + ···(禁用/重载/编辑/md/删除)
|
|
1036
1084
|
function agentOpsHtml(aid, ag, s) {
|
|
1037
1085
|
if (_agentOps.has(aid)) {
|
|
@@ -1064,6 +1112,8 @@ function renderAgents(data) {
|
|
|
1064
1112
|
const aids = data.aids || [];
|
|
1065
1113
|
const statsByAid = {};
|
|
1066
1114
|
for (const s of (data.stats || [])) statsByAid[s.aid] = s;
|
|
1115
|
+
const agentStatsByAid = {};
|
|
1116
|
+
for (const s of (data.agentStats || [])) agentStatsByAid[s.aid] = s;
|
|
1067
1117
|
const aidConnByAid = {};
|
|
1068
1118
|
for (const a of aids) aidConnByAid[a.aid] = a;
|
|
1069
1119
|
|
|
@@ -1107,13 +1157,13 @@ function renderAgents(data) {
|
|
|
1107
1157
|
}
|
|
1108
1158
|
|
|
1109
1159
|
// ── 启用页 ──
|
|
1110
|
-
//
|
|
1111
|
-
const
|
|
1112
|
-
const s =
|
|
1113
|
-
return (s.
|
|
1160
|
+
// 按全渠道任务活动降序排序(活跃的排前面)
|
|
1161
|
+
const totalActivity = (ag) => {
|
|
1162
|
+
const s = agentStatsByAid[ag.aid] || {};
|
|
1163
|
+
return (s.received || 0) + (s.sent || 0) + (s.completed || 0) + (s.errors || 0) + (s.interrupts || 0);
|
|
1114
1164
|
};
|
|
1115
1165
|
const enabledAgents = allAgents.filter(ag => ag.status !== 'disabled')
|
|
1116
|
-
.sort((a, b) =>
|
|
1166
|
+
.sort((a, b) => totalActivity(b) - totalActivity(a));
|
|
1117
1167
|
if (!enabledAgents.length) {
|
|
1118
1168
|
html += `<div class="empty">${t('agents.empty.enabled')}</div>`;
|
|
1119
1169
|
el.innerHTML = html;
|
|
@@ -1122,12 +1172,14 @@ function renderAgents(data) {
|
|
|
1122
1172
|
}
|
|
1123
1173
|
|
|
1124
1174
|
html += '<table><thead><tr>' +
|
|
1125
|
-
`<th>${t('agents.th.aid')}</th><th>${t('agents.th.work')}</th><th>${t('agents.th.queue')}</th><th>${t('agents.th.model')}</th><th>${t('agents.th.runtime')}</th
|
|
1126
|
-
`<th>${t('agents.th.
|
|
1175
|
+
`<th>${t('agents.th.aid')}</th><th>${t('agents.th.work')}</th><th>${t('agents.th.queue')}</th><th>${t('agents.th.model')}</th><th>${t('agents.th.runtime')}</th>` +
|
|
1176
|
+
`<th>${t('agents.th.received')}</th><th>${t('agents.th.sent')}</th><th>${t('agents.th.errors')}</th><th>${t('agents.th.interrupts')}</th><th>${t('agents.th.completed')}</th>` +
|
|
1177
|
+
`<th>${t('agents.th.lastActivity')}</th><th>${t('agents.th.operations')}</th>` +
|
|
1127
1178
|
'</tr></thead><tbody>';
|
|
1128
1179
|
|
|
1129
1180
|
for (const ag of enabledAgents) {
|
|
1130
1181
|
const s = statsByAid[ag.aid] || {};
|
|
1182
|
+
const runStats = agentStatsByAid[ag.aid] || {};
|
|
1131
1183
|
const conn = aidConnByAid[ag.aid] || {};
|
|
1132
1184
|
const connStatus = conn.status || (ag.status === 'running' ? 'connected' : 'disconnected');
|
|
1133
1185
|
const dotCls = connStatus === 'connected' ? 'on' : (connStatus === 'reconnecting' ? 'idle' : 'off');
|
|
@@ -1135,10 +1187,7 @@ function renderAgents(data) {
|
|
|
1135
1187
|
const uptime = (connStatus === 'connected' && conn.lastConnectedAt) ? fmtDur((Date.now() - conn.lastConnectedAt) / 1000) : '—';
|
|
1136
1188
|
const lastTs = Math.max(s.lastReceivedAt || 0, s.lastSentAt || 0, ag.lastActivity || 0);
|
|
1137
1189
|
const preview = agentPreviewHtml(s);
|
|
1138
|
-
|
|
1139
|
-
const rawQueued = s.queued || 0;
|
|
1140
|
-
const queued = rawQueued;
|
|
1141
|
-
const queueCell = queued > 0 ? `<span class="ag-queue-num">${queued}</span>` : '<span style="color:var(--dim)">0</span>';
|
|
1190
|
+
const queueCell = agentQueueHtml(runStats);
|
|
1142
1191
|
const model = ag.model || ag.baseagent || '—';
|
|
1143
1192
|
|
|
1144
1193
|
const idCell = `<div class="ag-id"><span class="dot ${dotCls}" title="${esc(connStatus)}"></span>` +
|
|
@@ -1147,15 +1196,17 @@ function renderAgents(data) {
|
|
|
1147
1196
|
|
|
1148
1197
|
html += `<tr class="ag-main">` +
|
|
1149
1198
|
`<td>${idCell}</td>` +
|
|
1150
|
-
`<td>${agentStateBadge(s, ag.status, connStatus)}</td>` +
|
|
1199
|
+
`<td>${agentStateBadge({ ...s, ...runStats }, ag.status, connStatus)}</td>` +
|
|
1151
1200
|
`<td>${queueCell}</td>` +
|
|
1152
1201
|
`<td style="font-size:11px;color:var(--dim)">${esc(model)}</td>` +
|
|
1153
1202
|
`<td>${uptime}</td>` +
|
|
1154
|
-
`<td>${
|
|
1155
|
-
`<td>${
|
|
1156
|
-
`<td>${
|
|
1203
|
+
`<td>${runStats.received || 0}</td>` +
|
|
1204
|
+
`<td>${runStats.sent || 0}</td>` +
|
|
1205
|
+
`<td>${runStats.errors || 0}</td>` +
|
|
1206
|
+
`<td>${runStats.interrupts || 0}</td>` +
|
|
1207
|
+
`<td>${runStats.completed || 0}</td>` +
|
|
1157
1208
|
`<td>${fmtAgo(lastTs)}</td>` +
|
|
1158
|
-
`<td class="agent-ops-cell">${agentOpsHtml(ag.aid, ag,
|
|
1209
|
+
`<td class="agent-ops-cell">${agentOpsHtml(ag.aid, ag, runStats)}</td>` +
|
|
1159
1210
|
'</tr>';
|
|
1160
1211
|
// 自定义 tooltip(HTML,hover 显示)
|
|
1161
1212
|
const recent = (s.recentMessages || []);
|
|
@@ -1168,7 +1219,7 @@ function renderAgents(data) {
|
|
|
1168
1219
|
}
|
|
1169
1220
|
html += '</tbody></table>';
|
|
1170
1221
|
if (data.daemonRunning) {
|
|
1171
|
-
html += agentsStatsBar(data, aids, data.
|
|
1222
|
+
html += agentsStatsBar(data, aids, data.agentStats || []);
|
|
1172
1223
|
}
|
|
1173
1224
|
el.innerHTML = html;
|
|
1174
1225
|
bindAgentsEvents(el);
|
|
@@ -1301,17 +1352,20 @@ function card(label, value, valCls, sub) {
|
|
|
1301
1352
|
// ── Messages 视图 ──
|
|
1302
1353
|
function renderMsg(data) {
|
|
1303
1354
|
if (!data) return;
|
|
1304
|
-
const aids = data.aids || [];
|
|
1355
|
+
const aids = data.scopes || data.aids || [];
|
|
1305
1356
|
const peers = data.peers || [];
|
|
1306
1357
|
const messages = data.messages || [];
|
|
1358
|
+
if (data.scope && data.scope !== msgSel.aid) msgSel.aid = data.scope;
|
|
1307
1359
|
|
|
1308
1360
|
// 左:AID 列表
|
|
1309
1361
|
let aidsHtml = `<div class="col-title">${t('messages.colTitle.aid')}</div>`;
|
|
1310
1362
|
for (const a of aids) {
|
|
1311
1363
|
const sel = a.aid === msgSel.aid ? ' sel' : '';
|
|
1364
|
+
const name = a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown';
|
|
1365
|
+
const groupBit = a.groupCount ? ` · 群 ${a.groupCount}` : '';
|
|
1312
1366
|
aidsHtml += `<div class="list-item${sel}" data-aid="${esc(a.aid)}">` +
|
|
1313
|
-
`<div class="name">${esc(
|
|
1314
|
-
`<div class="sub">↓${a.totalIn} ↑${a.totalOut} · ${a.peerCount}
|
|
1367
|
+
`<div class="name">${esc(name)}</div>` +
|
|
1368
|
+
`<div class="sub">↓${a.totalIn} ↑${a.totalOut} · ${a.peerCount} chats${groupBit} · ${fmtAgo(a.lastAt)}</div></div>`;
|
|
1315
1369
|
}
|
|
1316
1370
|
$('#msg-aids').innerHTML = aidsHtml;
|
|
1317
1371
|
$('#msg-aids').querySelectorAll('.list-item').forEach(item => {
|
|
@@ -1323,11 +1377,16 @@ function renderMsg(data) {
|
|
|
1323
1377
|
if (msgSel.aid) {
|
|
1324
1378
|
const allSel = msgSel.peer === null ? ' sel' : '';
|
|
1325
1379
|
peersHtml += `<div class="list-item${allSel}" data-peer=""><div class="name">${t('messages.colTitle.all')}</div>` +
|
|
1326
|
-
`<div class="sub">${peers.length}
|
|
1380
|
+
`<div class="sub">${peers.length} chats</div></div>`;
|
|
1327
1381
|
for (const p of peers) {
|
|
1328
1382
|
const sel = p.peerId === msgSel.peer ? ' sel' : '';
|
|
1383
|
+
const displayName = p.chatType === 'group'
|
|
1384
|
+
? (p.groupName || p.peerName || p.groupId || p.peerId)
|
|
1385
|
+
: (p.peerName || p.peerId);
|
|
1386
|
+
const channelLabel = p.channelName && p.channelName !== 'main' ? `${p.channelType}/${p.channelName}` : (p.channelType || '');
|
|
1387
|
+
const typeLabel = p.chatType === 'group' ? `${channelLabel} · ${t('messages.tag.group')}` : channelLabel;
|
|
1329
1388
|
peersHtml += `<div class="list-item${sel}" data-peer="${esc(p.peerId)}">` +
|
|
1330
|
-
`<div class="name">${esc(
|
|
1389
|
+
`<div class="name">${esc(shortId(displayName))} <span class="tag">${esc(typeLabel)}</span></div>` +
|
|
1331
1390
|
`<div class="sub">↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
|
|
1332
1391
|
}
|
|
1333
1392
|
} else {
|
|
@@ -1346,8 +1405,9 @@ function renderMsg(data) {
|
|
|
1346
1405
|
for (const m of messages) {
|
|
1347
1406
|
const cls = m.dir === 'in' ? 'in' : 'out';
|
|
1348
1407
|
const arrow = m.dir === 'in' ? '↓' : '↑';
|
|
1349
|
-
const from =
|
|
1408
|
+
const from = shortId(m.from), to = shortId(m.to);
|
|
1350
1409
|
const tags = [];
|
|
1410
|
+
if (m.channelType) tags.push(m.channelType);
|
|
1351
1411
|
if (m.chatType === 'group') tags.push(t('messages.tag.group'));
|
|
1352
1412
|
// 消息详情流的 kind 来自 jsonl 的 msgType(text/thought/image/file/command),
|
|
1353
1413
|
// 与 agents 页内存态的 MsgKind(send/thought/inject/notify)不是同一套词汇。
|
|
@@ -1388,8 +1448,19 @@ function renderSession(data) {
|
|
|
1388
1448
|
const projOpts = projects.map(p =>
|
|
1389
1449
|
`<option value="${esc(p.encoded)}"${p.encoded === sessSel.project ? ' selected' : ''}>${esc(p.label)} (${p.count})</option>`
|
|
1390
1450
|
).join('');
|
|
1451
|
+
|
|
1452
|
+
// baseagent 下拉选择器(只显示可用的)
|
|
1453
|
+
let baseagentOpts = '';
|
|
1454
|
+
if (availableBaseagents.claude) {
|
|
1455
|
+
baseagentOpts += `<option value="claude"${sessSel.baseagent === 'claude' ? ' selected' : ''}>Claude</option>`;
|
|
1456
|
+
}
|
|
1457
|
+
if (availableBaseagents.codex) {
|
|
1458
|
+
baseagentOpts += `<option value="codex"${sessSel.baseagent === 'codex' ? ' selected' : ''}>Codex</option>`;
|
|
1459
|
+
}
|
|
1460
|
+
|
|
1391
1461
|
const normalCount = transcripts.filter(t => (t.userMsgs || 0) >= 2).length;
|
|
1392
1462
|
let listHtml = '<div class="sess-filter">' +
|
|
1463
|
+
`<select id="sess-baseagent" title="Base Agent">${baseagentOpts}</select>` +
|
|
1393
1464
|
`<select id="sess-project">${projOpts}</select>` +
|
|
1394
1465
|
`<input id="sess-search" type="text" placeholder="搜索标题/首条消息…" value="${esc(sessSearch)}">` +
|
|
1395
1466
|
`<button id="sess-filter-btn" class="ctrl-btn${sessFilterNormal ? ' active' : ''}" title="只显示有效会话(≥2 条用户消息)">有效 ${normalCount}</button>` +
|
|
@@ -1417,11 +1488,19 @@ function renderSession(data) {
|
|
|
1417
1488
|
$('#sess-list').innerHTML = listHtml;
|
|
1418
1489
|
|
|
1419
1490
|
// 绑定交互(注意保持搜索框焦点)
|
|
1491
|
+
const baseagentSel = $('#sess-baseagent');
|
|
1492
|
+
if (baseagentSel) baseagentSel.onchange = () => {
|
|
1493
|
+
console.log('[ecweb] Baseagent changed to:', baseagentSel.value);
|
|
1494
|
+
sessSel = { sessionId: null, project: null, baseagent: baseagentSel.value };
|
|
1495
|
+
sessSearch = '';
|
|
1496
|
+
console.log('[ecweb] Subscribing to session with baseagent:', sessSel.baseagent);
|
|
1497
|
+
subscribe('session', { baseagent: sessSel.baseagent });
|
|
1498
|
+
};
|
|
1420
1499
|
const projSel = $('#sess-project');
|
|
1421
1500
|
if (projSel) projSel.onchange = () => {
|
|
1422
|
-
sessSel = { sessionId: null, project: projSel.value };
|
|
1501
|
+
sessSel = { sessionId: null, project: projSel.value, baseagent: sessSel.baseagent };
|
|
1423
1502
|
sessSearch = '';
|
|
1424
|
-
subscribe('session', { project: sessSel.project });
|
|
1503
|
+
subscribe('session', { project: sessSel.project, baseagent: sessSel.baseagent });
|
|
1425
1504
|
};
|
|
1426
1505
|
const filterBtn = $('#sess-filter-btn');
|
|
1427
1506
|
if (filterBtn) filterBtn.onclick = () => { sessFilterNormal = !sessFilterNormal; renderSession(state.session); };
|
|
@@ -1431,7 +1510,7 @@ function renderSession(data) {
|
|
|
1431
1510
|
if (q) { searchEl.focus(); searchEl.setSelectionRange(searchEl.value.length, searchEl.value.length); }
|
|
1432
1511
|
}
|
|
1433
1512
|
$('#sess-list').querySelectorAll('.list-item').forEach(item => {
|
|
1434
|
-
item.onclick = () => { sessSel = { sessionId: item.dataset.sid, project: sessSel.project }; subscribe('session', sessSel); };
|
|
1513
|
+
item.onclick = () => { sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent }; subscribe('session', sessSel); };
|
|
1435
1514
|
});
|
|
1436
1515
|
|
|
1437
1516
|
// 右:transcript 详情
|
|
@@ -1869,7 +1948,7 @@ function channelHealthRow(c) {
|
|
|
1869
1948
|
if (c.flapCount > 0) meta += ` <span style="color:var(--red)">抖动 ${c.flapCount}</span>`;
|
|
1870
1949
|
const reason = c.kickReason || c.lastError;
|
|
1871
1950
|
if (reason && !c.connected) meta += ` <span style="color:var(--red)" title="${esc(reason)}">"${esc(reason)}"</span>`;
|
|
1872
|
-
return `<div class="ch-row"><span class="dot ${dot}"></span>${esc(c.type)}${c.instName
|
|
1951
|
+
return `<div class="ch-row"><span class="dot ${dot}"></span>${esc(c.type)}${c.instName ? ' ' + esc(c.instName) : ''}${meta}</div>`;
|
|
1873
1952
|
}
|
|
1874
1953
|
|
|
1875
1954
|
function agentHealthCard(ag) {
|
|
@@ -1896,6 +1975,16 @@ function agentHealthCard(ag) {
|
|
|
1896
1975
|
return h;
|
|
1897
1976
|
}
|
|
1898
1977
|
|
|
1978
|
+
function systemBaseagentCards(baseagents) {
|
|
1979
|
+
const list = Array.isArray(baseagents) ? baseagents : [];
|
|
1980
|
+
return list.map(ba => {
|
|
1981
|
+
const ver = ba.version ? `・${ba.version}` : '';
|
|
1982
|
+
const title = `Baseagent・${ba.active ? '✓ ' : ''}${ba.name || 'unknown'}${ver}`;
|
|
1983
|
+
const detail = [ba.model, ba.effort].filter(Boolean).map(esc).join(' · ') || '未指定模型/强度';
|
|
1984
|
+
return `<div class="cache-card"><div class="card-label">${esc(title)}</div><div class="card-val">${detail}</div></div>`;
|
|
1985
|
+
}).join('');
|
|
1986
|
+
}
|
|
1987
|
+
|
|
1899
1988
|
function renderSystem(data) {
|
|
1900
1989
|
const el = $('#view-system');
|
|
1901
1990
|
if (!data) { el.innerHTML = '<div class="empty">加载中…</div>'; return; }
|
|
@@ -1936,27 +2025,36 @@ function renderSystem(data) {
|
|
|
1936
2025
|
|
|
1937
2026
|
// ③ 健康快照
|
|
1938
2027
|
if (chk) {
|
|
2028
|
+
// 从 chk.structured 读取数据(后端返回的数据结构)
|
|
2029
|
+
const s = chk.structured || chk; // 兼容旧版本(如果 chk 本身就是 structured)
|
|
1939
2030
|
html += '<div class="sys-health">';
|
|
1940
2031
|
// 队列 + 近 1 小时(数字卡片同一行)
|
|
1941
2032
|
html += '<div class="cache-cards" style="margin-bottom:8px">';
|
|
1942
|
-
html += `<div class="cache-card"><div class="card-label">队列</div><div class="card-val">${
|
|
1943
|
-
const h =
|
|
2033
|
+
html += `<div class="cache-card"><div class="card-label">队列</div><div class="card-val">${s.queue?.pending ?? 0} 待 · ${s.queue?.processing ?? 0} 处理中</div></div>`;
|
|
2034
|
+
const h = s.lastHour;
|
|
1944
2035
|
if (h) {
|
|
1945
2036
|
const errDetail = h.errors > 0 ? ` (${Object.entries(h.errorsByType || {}).map(([t, c]) => `${t}:${c}`).join(', ')})` : '';
|
|
1946
2037
|
const avg = h.completed > 0 ? ` · 均 ${(h.avgResponseMs / 1000).toFixed(1)}s` : '';
|
|
1947
2038
|
html += `<div class="cache-card"><div class="card-label">近 1 小时</div><div class="card-val">收 ${h.received} · 完 ${h.completed} · 错 ${h.errors}${errDetail} · 断 ${h.interrupts}${avg}</div></div>`;
|
|
1948
2039
|
}
|
|
2040
|
+
html += systemBaseagentCards(sys.baseagents);
|
|
1949
2041
|
html += '</div>';
|
|
1950
2042
|
// 每个 EvolAgent 一张卡片:后端 + 渠道健康 + 负载
|
|
1951
|
-
|
|
2043
|
+
// 排序:启用的(非 disabled)在前,停用的(disabled)在后
|
|
2044
|
+
if (s.evolagents?.length) {
|
|
2045
|
+
const sortedAgents = s.evolagents.slice().sort((a, b) => {
|
|
2046
|
+
const aDisabled = a.status === 'disabled' ? 1 : 0;
|
|
2047
|
+
const bDisabled = b.status === 'disabled' ? 1 : 0;
|
|
2048
|
+
return aDisabled - bDisabled;
|
|
2049
|
+
});
|
|
1952
2050
|
html += '<div class="agent-health-grid">';
|
|
1953
|
-
for (const ag of
|
|
2051
|
+
for (const ag of sortedAgents) html += agentHealthCard(ag);
|
|
1954
2052
|
html += '</div>';
|
|
1955
2053
|
}
|
|
1956
2054
|
// 未归属任何 EvolAgent 的渠道(系统级 / DefaultAgent)
|
|
1957
|
-
if (
|
|
2055
|
+
if (s.unownedChannels?.length) {
|
|
1958
2056
|
html += '<div class="cache-card" style="margin-top:8px"><div class="card-label">未归属渠道</div>';
|
|
1959
|
-
for (const c of
|
|
2057
|
+
for (const c of s.unownedChannels) html += channelHealthRow(c);
|
|
1960
2058
|
html += '</div>';
|
|
1961
2059
|
}
|
|
1962
2060
|
html += '</div>';
|
|
@@ -3534,21 +3632,21 @@ function renderMonitor(data) {
|
|
|
3534
3632
|
$('#mon-agent-table-wrap').innerHTML =
|
|
3535
3633
|
'<div class="mon-section-title">各 Agent 运行状态</div>' +
|
|
3536
3634
|
'<table class="usage-table"><thead><tr>' +
|
|
3537
|
-
'<th>Agent</th><th>状态</th><th>收</th><th>发</th><th
|
|
3635
|
+
'<th>Agent</th><th>状态</th><th>收</th><th>发</th><th>错</th><th>断</th><th>完</th><th>队列</th><th>处理中</th>' +
|
|
3538
3636
|
'</tr></thead><tbody>' +
|
|
3539
3637
|
(agents.length ? agents.map(function (a) {
|
|
3540
|
-
var st = a.
|
|
3638
|
+
var st = a.runtimeStats || {};
|
|
3541
3639
|
var dot = dotMap[a.status] || 'off';
|
|
3542
3640
|
return '<tr>' +
|
|
3543
3641
|
'<td title="' + esc(a.aid) + '">' + esc(a.agentName || shortAid(a.aid)) + '</td>' +
|
|
3544
3642
|
'<td><span class="dot ' + dot + '"></span>' + esc(a.status) + '</td>' +
|
|
3545
|
-
'<td>' + (st.
|
|
3546
|
-
'<td>' + (st.
|
|
3547
|
-
'<td>' +
|
|
3548
|
-
'<td>' +
|
|
3549
|
-
'<td>' + (st.
|
|
3643
|
+
'<td>' + (st.received || 0) + '</td>' +
|
|
3644
|
+
'<td>' + (st.sent || 0) + '</td>' +
|
|
3645
|
+
'<td>' + (st.errors || 0) + '</td>' +
|
|
3646
|
+
'<td>' + (st.interrupts || 0) + '</td>' +
|
|
3647
|
+
'<td>' + (st.completed || 0) + '</td>' +
|
|
3550
3648
|
'<td>' + (st.queued || 0) + '</td>' +
|
|
3551
|
-
'<td>' + (st.processing
|
|
3649
|
+
'<td>' + (st.processing || 0) + '</td>' +
|
|
3552
3650
|
'</tr>';
|
|
3553
3651
|
}).join('') : '<tr><td colspan="9" style="text-align:center;color:var(--dim)">暂无 Agent</td></tr>') +
|
|
3554
3652
|
'</tbody></table>';
|
package/dist/static/index.html
CHANGED
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
|
|
38
38
|
</nav>
|
|
39
39
|
<span style="flex:1"></span>
|
|
40
|
-
2026-06-
|
|
40
|
+
2026-06-29 10:59
|
|
41
41
|
<span id="conn-status" class="conn-status" data-i18n="status.connecting">连接中…</span>
|
|
42
42
|
<button id="lang-btn" class="theme-btn" title="Switch Language / 切换语言">🌐</button>
|
|
43
43
|
<button id="theme-btn" class="theme-btn" title="Toggle Theme / 切换主题">🌗</button>
|
package/dist/static/style.css
CHANGED
|
@@ -680,6 +680,7 @@ tr.ag-disabled .ag-name { color: var(--dim); }
|
|
|
680
680
|
|
|
681
681
|
/* 队列数列 */
|
|
682
682
|
.ag-queue-num { color: var(--orange); font-weight: 600; font-variant-numeric: tabular-nums; }
|
|
683
|
+
.ag-queue-empty { color: var(--dim); }
|
|
683
684
|
|
|
684
685
|
/* 子标签栏 */
|
|
685
686
|
.ag-subtabs { display: inline-flex; gap: 2px; }
|