ec-web 0.0.27 → 0.0.29-test.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.
@@ -1,7 +1,6 @@
1
1
  /* EC Web — 前端 WS 客户端 + 多视图渲染 */
2
2
 
3
3
  const $ = (sel) => document.querySelector(sel);
4
- const TOKEN_KEY = 'ecWatchToken';
5
4
  const LANG_KEY = 'ecWatchLang';
6
5
  const VIEW_KEY = 'ecWatchCurrentView';
7
6
  const ROLE_SUBVIEW_KEY = 'ecWatchRoleSubview';
@@ -84,6 +83,12 @@ const translations = {
84
83
  'common.loadingSuffix': '加载中',
85
84
  'common.noChanges': '没有需要保存的改动',
86
85
  'common.close': '关闭',
86
+ 'common.back': '返回',
87
+ 'common.openDetail': '打开详情',
88
+ 'navigation.open': '打开导航',
89
+ 'navigation.close': '关闭导航',
90
+ 'navigation.pages': '页面导航',
91
+ 'navigation.moreFilters': '更多筛选',
87
92
  'common.rawJsonPreview': '原始 JSON 预览',
88
93
  'common.inherited': '继承',
89
94
  'common.noLimit': '无限制',
@@ -117,6 +122,7 @@ const translations = {
117
122
  'theme.light': '浅色模式',
118
123
  'theme.dark': '深色模式',
119
124
  'theme.system': '跟随系统',
125
+ 'theme.drawerLabel': '主题:{mode}',
120
126
  'theme.buttonTitle': '主题:{mode};点击切换',
121
127
  'language.buttonTitle': '切换语言',
122
128
 
@@ -134,6 +140,7 @@ const translations = {
134
140
  'logs.lines.1000': '1000 行',
135
141
  'logs.lines.2000': '2000 行',
136
142
  'logs.follow': '跟随尾部',
143
+ 'logs.moreFilters': '更多筛选',
137
144
  'logs.live': '自动刷新',
138
145
  'logs.meta': '{files} 个文件 · {size} · 更新于 {time}',
139
146
  'logs.filtered': '筛选结果 {visible} 行(已加载 {total} 行)',
@@ -278,10 +285,13 @@ const translations = {
278
285
  // Messages view
279
286
  'messages.colTitle.aid': 'Agent',
280
287
  'messages.colTitle.peers': 'Chats',
288
+ 'messages.mobile.selection': '消息选择',
289
+ 'messages.mobile.aid': 'Agent',
290
+ 'messages.mobile.peer': 'Chats',
281
291
  'messages.empty.selectAid': '← 选择一个 Agent',
282
292
  'messages.empty.selectToView': '选择 Agent 查看消息',
283
293
  'messages.empty.noAgents': '暂无 Agent',
284
- 'messages.empty.noPeers': '暂无聊天对端',
294
+ 'messages.empty.noPeers': '暂无 Chats',
285
295
  'messages.empty.noMessages': '暂无消息',
286
296
  'messages.loadOlder': '加载更早消息',
287
297
  'messages.tag.group': '群聊',
@@ -305,7 +315,7 @@ const translations = {
305
315
  'messages.msgType.command': '命令',
306
316
  'messages.groupCount': '群 {count}',
307
317
  'messages.privateCount': '单 {count}',
308
- 'messages.session.field.sessionId': 'EvolCore 会话 ID',
318
+ 'messages.session.field.sessionId': 'EC会话ID',
309
319
  'messages.session.field.baseagent': 'Baseagent',
310
320
  'messages.session.field.chatMode': '模式',
311
321
  'messages.session.field.permissionMode': '权限',
@@ -316,6 +326,8 @@ const translations = {
316
326
  'messages.session.status.idle': '空闲',
317
327
  'messages.session.mode.interactive': '响应',
318
328
  'messages.session.mode.proactive': '自主',
329
+ 'messages.session.details.expand': '展开会话状态',
330
+ 'messages.session.details.collapse': '收起会话状态',
319
331
 
320
332
  // Sessions view
321
333
  'sessions.filter.normal': '🔍 仅有效',
@@ -359,6 +371,8 @@ const translations = {
359
371
  'sessions.fullView': '📜 完整视图',
360
372
  'sessions.chatHint': '只看用户与 Agent 的对话,处理过程已折叠',
361
373
  'sessions.fullHint': '显示全部消息',
374
+ 'sessions.details.expand': '展开会话信息',
375
+ 'sessions.details.collapse': '收起会话信息',
362
376
  'sessions.procSummary': '⋯ {count} 条处理过程(思考·工具·结果)',
363
377
  'sessions.empty.noDialogue': '该会话没有用户对话消息',
364
378
  'sessions.turnType.userInput': '用户输入',
@@ -750,10 +764,13 @@ const translations = {
750
764
  'triggers.lastUnrecorded': '未记录',
751
765
  'triggers.collapse.expand': '展开 Agent 列表',
752
766
  'triggers.collapse.collapse': '收起 Agent 列表',
767
+ 'triggers.agent.daemon': 'DAEMON',
753
768
  'triggers.empty.noAgents': '暂无 Agent',
754
769
  'triggers.empty.noTriggers': '暂无触发器',
755
770
  'triggers.empty.noAgentTriggers': '该 Agent 暂无触发器',
756
771
  'triggers.th.status': '状态',
772
+ 'triggers.th.agent': 'Agent',
773
+ 'triggers.th.id': 'ID',
757
774
  'triggers.th.subscription': '订阅',
758
775
  'triggers.th.name': '名称',
759
776
  'triggers.th.type': '类型',
@@ -1024,6 +1041,12 @@ const translations = {
1024
1041
  'common.loadingSuffix': 'loading',
1025
1042
  'common.noChanges': 'No changes to save',
1026
1043
  'common.close': 'Close',
1044
+ 'common.back': 'Back',
1045
+ 'common.openDetail': 'Open details',
1046
+ 'navigation.open': 'Open navigation',
1047
+ 'navigation.close': 'Close navigation',
1048
+ 'navigation.pages': 'Page navigation',
1049
+ 'navigation.moreFilters': 'More filters',
1027
1050
  'common.rawJsonPreview': 'Raw JSON Preview',
1028
1051
  'common.inherited': 'Inherited',
1029
1052
  'common.noLimit': 'No limit',
@@ -1057,6 +1080,7 @@ const translations = {
1057
1080
  'theme.light': 'Light',
1058
1081
  'theme.dark': 'Dark',
1059
1082
  'theme.system': 'System',
1083
+ 'theme.drawerLabel': 'Theme: {mode}',
1060
1084
  'theme.buttonTitle': 'Theme: {mode}; click to switch',
1061
1085
  'language.buttonTitle': 'Switch language',
1062
1086
 
@@ -1074,6 +1098,7 @@ const translations = {
1074
1098
  'logs.lines.1000': '1000 Lines',
1075
1099
  'logs.lines.2000': '2000 Lines',
1076
1100
  'logs.follow': 'Follow tail',
1101
+ 'logs.moreFilters': 'More filters',
1077
1102
  'logs.live': 'Auto refresh',
1078
1103
  'logs.meta': '{files} files · {size} · updated {time}',
1079
1104
  'logs.filtered': '{visible} filtered ({total} loaded)',
@@ -1218,6 +1243,9 @@ const translations = {
1218
1243
  // Messages view
1219
1244
  'messages.colTitle.aid': 'Agent',
1220
1245
  'messages.colTitle.peers': 'Chats',
1246
+ 'messages.mobile.selection': 'Message selection',
1247
+ 'messages.mobile.aid': 'Agent',
1248
+ 'messages.mobile.peer': 'Chats',
1221
1249
  'messages.empty.selectAid': '← Select Agent',
1222
1250
  'messages.empty.selectToView': 'Select Agent to view messages',
1223
1251
  'messages.empty.noAgents': 'No agents',
@@ -1245,7 +1273,7 @@ const translations = {
1245
1273
  'messages.msgType.command': 'Command',
1246
1274
  'messages.groupCount': '{count} groups',
1247
1275
  'messages.privateCount': '{count} private',
1248
- 'messages.session.field.sessionId': 'EvolCore Session ID',
1276
+ 'messages.session.field.sessionId': 'EC Session ID',
1249
1277
  'messages.session.field.baseagent': 'Baseagent',
1250
1278
  'messages.session.field.chatMode': 'Mode',
1251
1279
  'messages.session.field.permissionMode': 'Perm',
@@ -1256,6 +1284,8 @@ const translations = {
1256
1284
  'messages.session.status.idle': 'Idle',
1257
1285
  'messages.session.mode.interactive': 'Interactive',
1258
1286
  'messages.session.mode.proactive': 'Proactive',
1287
+ 'messages.session.details.expand': 'Expand session status',
1288
+ 'messages.session.details.collapse': 'Collapse session status',
1259
1289
 
1260
1290
  // Sessions view
1261
1291
  'sessions.filter.normal': '🔍 Valid Only',
@@ -1299,6 +1329,8 @@ const translations = {
1299
1329
  'sessions.fullView': '📜 Full View',
1300
1330
  'sessions.chatHint': 'Only user and Agent messages; processing is collapsed',
1301
1331
  'sessions.fullHint': 'Show all messages',
1332
+ 'sessions.details.expand': 'Expand session details',
1333
+ 'sessions.details.collapse': 'Collapse session details',
1302
1334
  'sessions.procSummary': '⋯ {count} processing steps (thinking · tools · results)',
1303
1335
  'sessions.empty.noDialogue': 'No user dialogue messages in this session',
1304
1336
  'sessions.turnType.userInput': 'User Input',
@@ -1690,10 +1722,13 @@ const translations = {
1690
1722
  'triggers.lastUnrecorded': 'Not recorded',
1691
1723
  'triggers.collapse.expand': 'Expand Agent List',
1692
1724
  'triggers.collapse.collapse': 'Collapse Agent List',
1725
+ 'triggers.agent.daemon': 'DAEMON',
1693
1726
  'triggers.empty.noAgents': 'No agents',
1694
1727
  'triggers.empty.noTriggers': 'No triggers',
1695
1728
  'triggers.empty.noAgentTriggers': 'No triggers for this agent',
1696
1729
  'triggers.th.status': 'Status',
1730
+ 'triggers.th.agent': 'Agent',
1731
+ 'triggers.th.id': 'ID',
1697
1732
  'triggers.th.subscription': 'Subscription',
1698
1733
  'triggers.th.name': 'Name',
1699
1734
  'triggers.th.type': 'Type',
@@ -1922,9 +1957,15 @@ function updateI18n() {
1922
1957
  const key = el.getAttribute('data-i18n-title');
1923
1958
  el.title = t(key);
1924
1959
  });
1960
+ document.querySelectorAll('[data-i18n-aria-label]').forEach(el => {
1961
+ const key = el.getAttribute('data-i18n-aria-label');
1962
+ el.setAttribute('aria-label', t(key));
1963
+ });
1925
1964
  // 更新 html lang 属性
1926
1965
  document.documentElement.lang = currentLang;
1927
1966
  updateThemeButton();
1967
+ updateMobilePageTitle();
1968
+ renderMobileNavigation();
1928
1969
  window.refreshBasicSettingsI18n?.();
1929
1970
  }
1930
1971
 
@@ -1939,7 +1980,7 @@ function toggleLang() {
1939
1980
  }
1940
1981
 
1941
1982
  // ── 基础路径 ──
1942
- // 本地直连时页面在 "/",经 AUN Service Proxy 时页面在 "/ecweb/"。
1983
+ // 本地直连时页面在 "/",经 AUN Service Proxy 时页面在 "/<host>/ecweb/"。
1943
1984
  // 取当前页面所在目录(含尾斜杠)作为所有 API/WS 的前缀,使绝对路径在两种
1944
1985
  // 部署下都正确(proxy-server 用首段路径选服务,前缀不能丢)。
1945
1986
  const BASE = (() => {
@@ -1952,6 +1993,9 @@ const BASE = (() => {
1952
1993
  }
1953
1994
  return location.pathname.replace(/[^/]*$/, '');
1954
1995
  })();
1996
+ // 同源的不同主机共享 localStorage,token 必须按完整代理路径隔离。
1997
+ // 旧 ecWatchToken 无法确定所属主机,不迁移;远程访问需重新配对一次。
1998
+ const TOKEN_KEY = `ecWatchToken:${BASE}`;
1955
1999
  const apiUrl = (p) => BASE + p.replace(/^\/+/, '');
1956
2000
  const authApiUrl = (p) => {
1957
2001
  const token = localStorage.getItem(TOKEN_KEY);
@@ -2280,7 +2324,13 @@ setInterval(() => {
2280
2324
 
2281
2325
  // ── Tab 切换 ──
2282
2326
  let msgSel = { aid: null, peer: null, before: null };
2327
+ let msgCollapsedAids = new Set();
2328
+ // 消息详情扩展信息只在当前页面生命周期内记忆,不写入 sessionStorage。
2329
+ // 以 Agent/Chats/EC 会话 ID 区分,切换会话时仍默认展开。
2330
+ const msgSessionDetailsExpanded = new Map();
2283
2331
  let sessSel = { sessionId: null, project: null, baseagent: null, turnBefore: null };
2332
+ // 会话详情顶部信息只在当前页面生命周期内记忆展开/收起状态。
2333
+ const sessHeaderExpanded = new Map();
2284
2334
  let trigSel = { agent: null };
2285
2335
  let trigAgentsCollapsed = false;
2286
2336
  const _trigOps = new Map(); // Map<triggerId, string>
@@ -2290,9 +2340,98 @@ let _triggerTest = null;
2290
2340
  let sessSearch = '';
2291
2341
  let sessShowAll = false; // false=只显示有效会话;true=显示全部
2292
2342
  let sessSourceFilter = 'all'; // all / trigger / agent / cli / user / unknown
2343
+ let sessListScroll = { layout: 0, items: 0 };
2293
2344
  let sessChatMode = false; // false=完整视图,true=对话视图(折叠处理过程)
2294
2345
  let monRange = '2m'; // Monitor 时间窗口:2m / 10m / 1h
2295
2346
  let logSel = { source: null, level: 'all', module: 'all', search: '', limit: 500, follow: true };
2347
+ const SESSION_UI_KEY = 'ecWebUiState';
2348
+ let mobileNavOpen = false;
2349
+ let detailViewState = { msg: false, session: false };
2350
+ let historySyncing = false;
2351
+ let sessScrollSaveFrame = null;
2352
+
2353
+ function readSessionUiState() {
2354
+ try {
2355
+ const raw = sessionStorage.getItem(SESSION_UI_KEY);
2356
+ if (!raw) return;
2357
+ const saved = JSON.parse(raw);
2358
+ if (saved.msg && typeof saved.msg === 'object') msgSel = { ...msgSel, ...saved.msg, before: null };
2359
+ if (saved.session && typeof saved.session === 'object') sessSel = { ...sessSel, ...saved.session, turnBefore: null };
2360
+ if (saved.trigger && typeof saved.trigger === 'object') trigSel = { ...trigSel, ...saved.trigger };
2361
+ if (typeof saved.sessShowAll === 'boolean') sessShowAll = saved.sessShowAll;
2362
+ if (typeof saved.sessSourceFilter === 'string') sessSourceFilter = saved.sessSourceFilter;
2363
+ if (saved.sessionListScroll && typeof saved.sessionListScroll === 'object') {
2364
+ sessListScroll = {
2365
+ layout: Math.max(0, Number(saved.sessionListScroll.layout) || 0),
2366
+ items: Math.max(0, Number(saved.sessionListScroll.items) || 0),
2367
+ };
2368
+ }
2369
+ if (saved.log && typeof saved.log === 'object') logSel = { ...logSel, ...saved.log };
2370
+ if (saved.detail && typeof saved.detail === 'object') detailViewState = { ...detailViewState, ...saved.detail };
2371
+ } catch {
2372
+ // sessionStorage may be unavailable in private or embedded contexts.
2373
+ }
2374
+ }
2375
+
2376
+ function saveSessionUiState() {
2377
+ try {
2378
+ captureSessionListScroll();
2379
+ sessionStorage.setItem(SESSION_UI_KEY, JSON.stringify({
2380
+ msg: { aid: msgSel.aid, peer: msgSel.peer },
2381
+ session: { ...sessSel, turnBefore: null },
2382
+ trigger: { agent: trigSel.agent },
2383
+ sessShowAll,
2384
+ sessSourceFilter,
2385
+ sessionListScroll: sessListScroll,
2386
+ log: { ...logSel },
2387
+ detail: { ...detailViewState },
2388
+ }));
2389
+ } catch {
2390
+ // Keep the in-memory state when storage is unavailable.
2391
+ }
2392
+ }
2393
+
2394
+ function captureSessionListScroll() {
2395
+ const layout = document.querySelector('.sess-layout');
2396
+ const items = document.querySelector('#sess-list .sess-items');
2397
+ if (layout) sessListScroll.layout = Math.max(0, layout.scrollTop || 0);
2398
+ if (items) sessListScroll.items = Math.max(0, items.scrollTop || 0);
2399
+ }
2400
+
2401
+ function restoreSessionListScroll() {
2402
+ const layout = document.querySelector('.sess-layout');
2403
+ const items = document.querySelector('#sess-list .sess-items');
2404
+ if (layout) layout.scrollTop = sessListScroll.layout;
2405
+ if (items) items.scrollTop = sessListScroll.items;
2406
+ }
2407
+
2408
+ function bindSessionListScroll() {
2409
+ const layout = document.querySelector('.sess-layout');
2410
+ if (layout && layout.dataset.scrollBound !== '1') {
2411
+ layout.dataset.scrollBound = '1';
2412
+ layout.addEventListener('scroll', () => {
2413
+ captureSessionListScroll();
2414
+ scheduleSessionListStateSave();
2415
+ }, { passive: true });
2416
+ }
2417
+ const items = document.querySelector('#sess-list .sess-items');
2418
+ if (items) items.addEventListener('scroll', () => {
2419
+ captureSessionListScroll();
2420
+ scheduleSessionListStateSave();
2421
+ }, { passive: true });
2422
+ }
2423
+
2424
+ function scheduleSessionListStateSave() {
2425
+ if (sessScrollSaveFrame != null) return;
2426
+ const flush = () => {
2427
+ sessScrollSaveFrame = null;
2428
+ saveSessionUiState();
2429
+ };
2430
+ if (typeof window.requestAnimationFrame === 'function') sessScrollSaveFrame = window.requestAnimationFrame(flush);
2431
+ else sessScrollSaveFrame = window.setTimeout(flush, 0);
2432
+ }
2433
+
2434
+ readSessionUiState();
2296
2435
  // ECWeb 界面的 base agent 展示名统一走这里的 i18n 键,而不是父包别名表:
2297
2436
  // 别名表面向 CLI/日志(claude → "Claude Code"、ecagent → "ecagent"),
2298
2437
  // 面板里应保持简洁一致(Claude / Codex / Gemini / EC Agent)。
@@ -2369,7 +2508,133 @@ function setRoleManagementSubview(subview, options = {}) {
2369
2508
  if (options.render !== false) renderRoleManagementSubview();
2370
2509
  }
2371
2510
 
2372
- function switchView(view) {
2511
+ const VIEW_LABEL_KEYS = {
2512
+ agents: 'tab.agents', msg: 'tab.messages', session: 'tab.sessions', triggers: 'tab.triggers',
2513
+ cache: 'tab.cache', system: 'tab.system', logs: 'tab.logs', usage: 'tab.usage', roleManagement: 'tab.roleManagement',
2514
+ };
2515
+
2516
+ function normalizedView(view) {
2517
+ if (view === 'monitor') return 'system';
2518
+ if (view === 'roles' || view === 'roleDefinitions') return 'roleManagement';
2519
+ return ['agents', 'msg', 'session', 'triggers', 'cache', 'system', 'logs', 'usage', 'roleManagement'].includes(view)
2520
+ ? view
2521
+ : 'agents';
2522
+ }
2523
+
2524
+ function historySnapshot() {
2525
+ return {
2526
+ ecweb: true,
2527
+ view: currentView,
2528
+ detail: { ...detailViewState },
2529
+ roleManagementSubview,
2530
+ };
2531
+ }
2532
+
2533
+ function updateMobilePageTitle() {
2534
+ const title = $('#mobile-page-title');
2535
+ if (title) title.textContent = t(VIEW_LABEL_KEYS[currentView] || 'tab.agents');
2536
+ }
2537
+
2538
+ function applyDetailClasses() {
2539
+ ['msg', 'session'].forEach(view => {
2540
+ const el = $('#view-' + view);
2541
+ if (!el) return;
2542
+ el.classList.toggle('detail-fullscreen', currentView === view && detailViewState[view] === true);
2543
+ });
2544
+ saveSessionUiState();
2545
+ }
2546
+
2547
+ function enterDetail(view, options = {}) {
2548
+ if (!['msg', 'session'].includes(view) || currentView !== view) return;
2549
+ if (detailViewState[view]) return;
2550
+ detailViewState[view] = true;
2551
+ applyDetailClasses();
2552
+ if (options.history !== false && !historySyncing) history.pushState(historySnapshot(), '', location.href);
2553
+ renderView(view);
2554
+ }
2555
+
2556
+ function leaveDetail(view, options = {}) {
2557
+ if (!['msg', 'session'].includes(view) || !detailViewState[view]) return;
2558
+ if (options.history !== false && !historySyncing && history.state?.ecweb && history.state.detail?.[view]) {
2559
+ history.back();
2560
+ return;
2561
+ }
2562
+ detailViewState[view] = false;
2563
+ applyDetailClasses();
2564
+ renderView(view);
2565
+ }
2566
+
2567
+ function restoreHistoryState(snapshot) {
2568
+ const next = snapshot && snapshot.ecweb ? snapshot : { ecweb: true, view: currentView, detail: {} };
2569
+ historySyncing = true;
2570
+ if (next.roleManagementSubview) roleManagementSubview = next.roleManagementSubview;
2571
+ detailViewState = { msg: false, session: false };
2572
+ switchView(next.view || 'agents', { history: false });
2573
+ detailViewState = { msg: false, session: false, ...(next.detail || {}) };
2574
+ historySyncing = false;
2575
+ applyDetailClasses();
2576
+ if (currentView === 'msg' || currentView === 'session') renderView(currentView);
2577
+ }
2578
+
2579
+ function initHistory() {
2580
+ const initial = history.state && history.state.ecweb ? history.state : historySnapshot();
2581
+ history.replaceState(initial, '', location.href);
2582
+ window.addEventListener('popstate', event => restoreHistoryState(event.state));
2583
+ }
2584
+
2585
+ function renderMobileNavigation() {
2586
+ const list = $('.mobile-nav-list');
2587
+ if (!list) return;
2588
+ list.innerHTML = [...document.querySelectorAll('.tabs .tab')].map(tab => {
2589
+ const view = tab.dataset.view;
2590
+ const label = t(tab.dataset.i18n || VIEW_LABEL_KEYS[view] || view);
2591
+ return `<button type="button" class="mobile-nav-item${view === currentView ? ' active' : ''}" data-mobile-view="${esc(view)}">${esc(label)}</button>`;
2592
+ }).join('');
2593
+ list.querySelectorAll('[data-mobile-view]').forEach(button => {
2594
+ button.onclick = () => {
2595
+ closeMobileNavigation();
2596
+ switchView(button.dataset.mobileView);
2597
+ };
2598
+ });
2599
+ }
2600
+
2601
+ function setMobileNavigation(open) {
2602
+ mobileNavOpen = Boolean(open);
2603
+ const drawer = $('#mobile-nav-drawer');
2604
+ const backdrop = $('#mobile-nav-backdrop');
2605
+ const menu = $('#mobile-menu-btn');
2606
+ if (drawer) {
2607
+ drawer.classList.toggle('open', mobileNavOpen);
2608
+ drawer.setAttribute('aria-hidden', String(!mobileNavOpen));
2609
+ }
2610
+ if (backdrop) {
2611
+ backdrop.hidden = !mobileNavOpen;
2612
+ backdrop.classList.toggle('open', mobileNavOpen);
2613
+ }
2614
+ if (menu) menu.setAttribute('aria-expanded', String(mobileNavOpen));
2615
+ document.body.classList.toggle('mobile-nav-open', mobileNavOpen);
2616
+ }
2617
+
2618
+ function closeMobileNavigation() { setMobileNavigation(false); }
2619
+
2620
+ function initMobileNavigation() {
2621
+ renderMobileNavigation();
2622
+ $('#mobile-menu-btn')?.addEventListener('click', () => setMobileNavigation(!mobileNavOpen));
2623
+ $('#mobile-nav-close')?.addEventListener('click', closeMobileNavigation);
2624
+ $('#mobile-nav-backdrop')?.addEventListener('click', closeMobileNavigation);
2625
+ document.querySelectorAll('[data-mobile-tool]').forEach(button => {
2626
+ button.addEventListener('click', () => {
2627
+ const tool = button.dataset.mobileTool;
2628
+ closeMobileNavigation();
2629
+ if (tool === 'lang') toggleLang();
2630
+ else if (tool === 'theme') $('#theme-btn')?.click();
2631
+ else if (tool === 'settings') $('#basic-settings-btn')?.click();
2632
+ else if (tool === 'logout') $('#logout-btn')?.click();
2633
+ });
2634
+ });
2635
+ }
2636
+
2637
+ function switchView(view, options = {}) {
2373
2638
  if (view === 'monitor') {
2374
2639
  systemContentMode = 'monitor';
2375
2640
  view = 'system';
@@ -2378,10 +2643,19 @@ function switchView(view) {
2378
2643
  roleManagementSubview = view === 'roles' ? 'assignments' : 'definitions';
2379
2644
  view = 'roleManagement';
2380
2645
  }
2646
+ view = normalizedView(view);
2647
+ if (currentView !== view) {
2648
+ detailViewState = { msg: false, session: false };
2649
+ }
2381
2650
  currentView = view;
2382
2651
  localStorage.setItem(VIEW_KEY, view);
2383
2652
  document.querySelectorAll('.tab').forEach(t => t.classList.toggle('active', t.dataset.view === view));
2384
2653
  document.querySelectorAll('.view').forEach(v => v.classList.toggle('active', v.id === 'view-' + view));
2654
+ updateMobilePageTitle();
2655
+ renderMobileNavigation();
2656
+ applyDetailClasses();
2657
+ if (options.replaceHistory) history.replaceState(historySnapshot(), '', location.href);
2658
+ else if (options.history !== false && !historySyncing && options.fromHistory !== true) history.pushState(historySnapshot(), '', location.href);
2385
2659
  // 切换时按当前选择恢复订阅
2386
2660
  if (view === 'msg') subscribe('msg', msgSel);
2387
2661
  else if (view === 'session') subscribe('session', sessSel);
@@ -2413,6 +2687,7 @@ function initTabs() {
2413
2687
  document.querySelector(`[data-role-subview="${next}"]`)?.focus();
2414
2688
  };
2415
2689
  });
2690
+ initMobileNavigation();
2416
2691
  }
2417
2692
 
2418
2693
  function renderView(view) {
@@ -2516,37 +2791,49 @@ function bindLogControls() {
2516
2791
  const search = $('#log-search');
2517
2792
  const limit = $('#log-limit');
2518
2793
  const follow = $('#log-follow');
2794
+ const moreFilters = $('#log-more-filters');
2519
2795
  if (!source || source.dataset.bound === '1') return;
2520
2796
  source.dataset.bound = '1';
2521
2797
  source.addEventListener('change', () => {
2522
2798
  logSel.source = source.value || null;
2799
+ saveSessionUiState();
2523
2800
  $('#log-stream').innerHTML = `<div class="empty">${t('common.loading')}</div>`;
2524
2801
  subscribe('logs', { source: logSel.source, limit: logSel.limit });
2525
2802
  });
2526
2803
  level.addEventListener('change', () => {
2527
2804
  logSel.level = level.value || 'all';
2805
+ saveSessionUiState();
2528
2806
  renderLogs(state.logs);
2529
2807
  });
2530
2808
  module.addEventListener('change', () => {
2531
2809
  logSel.module = module.value || 'all';
2810
+ saveSessionUiState();
2532
2811
  renderLogs(state.logs);
2533
2812
  });
2534
2813
  search.addEventListener('input', () => {
2535
2814
  logSel.search = search.value || '';
2815
+ saveSessionUiState();
2536
2816
  renderLogs(state.logs);
2537
2817
  });
2538
2818
  limit.addEventListener('change', () => {
2539
2819
  logSel.limit = Number(limit.value) || 500;
2820
+ saveSessionUiState();
2540
2821
  $('#log-stream').innerHTML = `<div class="empty">${t('common.loading')}</div>`;
2541
2822
  subscribe('logs', { source: logSel.source, limit: logSel.limit });
2542
2823
  });
2543
2824
  follow.addEventListener('change', () => {
2544
2825
  logSel.follow = follow.checked;
2826
+ saveSessionUiState();
2545
2827
  if (logSel.follow) {
2546
2828
  const stream = $('#log-stream');
2547
2829
  stream.scrollTop = stream.scrollHeight;
2548
2830
  }
2549
2831
  });
2832
+ if (moreFilters) moreFilters.addEventListener('click', () => {
2833
+ const toolbar = moreFilters.closest('.log-toolbar');
2834
+ const expanded = toolbar?.classList.toggle('filters-expanded') || false;
2835
+ moreFilters.setAttribute('aria-expanded', String(expanded));
2836
+ });
2550
2837
  }
2551
2838
 
2552
2839
  function renderLogs(data) {
@@ -2556,6 +2843,7 @@ function renderLogs(data) {
2556
2843
  const search = $('#log-search');
2557
2844
  const limit = $('#log-limit');
2558
2845
  const follow = $('#log-follow');
2846
+ const moreFilters = $('#log-more-filters');
2559
2847
  const meta = $('#log-meta-info');
2560
2848
  const filterResult = $('#log-filter-result');
2561
2849
  const stream = $('#log-stream');
@@ -2566,6 +2854,7 @@ function renderLogs(data) {
2566
2854
  search.value = logSel.search;
2567
2855
  limit.value = String(logSel.limit);
2568
2856
  follow.checked = logSel.follow;
2857
+ if (moreFilters) moreFilters.textContent = t('logs.moreFilters');
2569
2858
  if (!data) {
2570
2859
  stream.innerHTML = `<div class="empty">${t('common.loading')}</div>`;
2571
2860
  return;
@@ -2889,6 +3178,13 @@ function initMsgTipFloat() {
2889
3178
  if (to && (wrap.contains(to) || (floatEl && floatEl.contains(to)))) return;
2890
3179
  scheduleHide();
2891
3180
  });
3181
+ document.addEventListener('click', (e) => {
3182
+ if (!floatEl || !floatEl.classList.contains('show')) return;
3183
+ if (floatEl.contains(e.target)) return;
3184
+ const wrap = e.target.closest && e.target.closest('.ag-msg-wrap');
3185
+ if (wrap && wrap === curWrap) return;
3186
+ hideNow();
3187
+ }, true);
2892
3188
  // 滚动时隐藏,避免浮层与行脱节
2893
3189
  window.addEventListener('scroll', hideNow, true);
2894
3190
  }
@@ -3138,7 +3434,9 @@ function renderAgents(data) {
3138
3434
  if (!disabledAgents.length) {
3139
3435
  html += `<div class="empty">${t('agents.empty.disabled')}</div>`;
3140
3436
  } else {
3141
- html += `<table><thead><tr><th>${t('agents.th.agent')}</th><th>${t('agents.th.projectPath')}</th><th>${t('agents.th.operations')}</th></tr></thead><tbody>`;
3437
+ html += '<table class="agents-table agents-table-disabled"><thead><tr>' +
3438
+ `<th>${t('agents.th.agent')}</th><th>${t('agents.th.operations')}</th>` +
3439
+ '</tr></thead><tbody>';
3142
3440
  for (const ag of disabledAgents) {
3143
3441
  const busy = _agentOps.has(ag.aid);
3144
3442
  const ops = busy
@@ -3146,7 +3444,6 @@ function renderAgents(data) {
3146
3444
  : agentDisabledOpsHtml(ag.aid);
3147
3445
  html += `<tr class="ag-main">` +
3148
3446
  `<td><div class="ag-id"><span class="dot off"></span><span class="ag-id-text"><span class="ag-name">${esc(ag.displayName || shortAid(ag.aid))}${agentLifecycleBadge(ag.lifecycle)}</span><span class="ag-aid">${esc(ag.aid)}</span></span></div></td>` +
3149
- `<td style="font-size:11px;font-family:monospace">${esc(ag.projectPath || '—')}</td>` +
3150
3447
  `<td class="agent-ops-cell">${ops}</td></tr>`;
3151
3448
  }
3152
3449
  html += '</tbody></table>';
@@ -3171,7 +3468,7 @@ function renderAgents(data) {
3171
3468
  return;
3172
3469
  }
3173
3470
 
3174
- html += '<table><thead><tr>' +
3471
+ html += '<table class="agents-table agents-table-enabled"><thead><tr>' +
3175
3472
  `<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>` +
3176
3473
  `<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>` +
3177
3474
  `<th>${t('agents.th.lastActivity')}</th><th>${t('agents.th.operations')}</th>` +
@@ -3212,10 +3509,11 @@ function renderAgents(data) {
3212
3509
  const recent = (s.recentMessages || []);
3213
3510
  const tipHtml = recentMsgTooltipHtml(recent);
3214
3511
 
3215
- html += `<tr class="ag-sub"><td colspan="12"><div class="ag-info">` +
3216
- (ag.projectPath ? `<div class="ag-path">${esc(ag.projectPath)}</div>` : '') +
3217
- (preview ? `<div class="ag-msg-wrap">${tipHtml}<div class="ag-msg">${preview}</div></div>` : '') +
3218
- '</div></td></tr>';
3512
+ if (preview) {
3513
+ html += `<tr class="ag-sub"><td colspan="12"><div class="ag-info">` +
3514
+ `<div class="ag-msg-wrap">${tipHtml}<div class="ag-msg">${preview}</div></div>` +
3515
+ '</div></td></tr>';
3516
+ }
3219
3517
  }
3220
3518
  html += '</tbody></table>';
3221
3519
  if (data.daemonRunning) {
@@ -3358,6 +3656,83 @@ function msgChannelTone(channelType) {
3358
3656
  : 'other';
3359
3657
  }
3360
3658
 
3659
+ function renderMsgMobileControls(aids, peers, scopeMatches) {
3660
+ const el = $('#msg-mobile-controls');
3661
+ if (!el) return;
3662
+ const aidOptions = aids.map(item => {
3663
+ const name = item.displayName || (item.selfAID && item.selfAID !== 'unknown' ? shortAid(item.selfAID) : 'unknown');
3664
+ return `<option value="${esc(item.aid)}"${item.aid === msgSel.aid ? ' selected' : ''}>${esc(name)}</option>`;
3665
+ }).join('');
3666
+ const peerOptions = peers.map(item => {
3667
+ const name = item.chatType === 'group'
3668
+ ? (item.groupName || item.peerName || item.groupId || item.peerId)
3669
+ : (item.peerName || item.peerId);
3670
+ return `<option value="${esc(item.peerId)}"${item.peerId === msgSel.peer ? ' selected' : ''}>${esc(shortId(name))}</option>`;
3671
+ }).join('');
3672
+ el.innerHTML = `<label><span>${esc(t('messages.mobile.aid'))}</span>` +
3673
+ `<select id="msg-mobile-aid"${aids.length ? '' : ' disabled'}>${aidOptions || '<option value="">—</option>'}</select>` +
3674
+ `</label><label><span>${esc(t('messages.mobile.peer'))}</span>` +
3675
+ `<select id="msg-mobile-peer"${scopeMatches && peers.length ? '' : ' disabled'}>${peerOptions || '<option value="">—</option>'}</select>` +
3676
+ '</label>';
3677
+ const aidSelect = $('#msg-mobile-aid');
3678
+ if (aidSelect) aidSelect.onchange = () => {
3679
+ msgSel = { aid: aidSelect.value || null, peer: null, before: null };
3680
+ detailViewState.msg = false;
3681
+ saveSessionUiState();
3682
+ subscribe('msg', msgSel);
3683
+ };
3684
+ const peerSelect = $('#msg-mobile-peer');
3685
+ if (peerSelect) peerSelect.onchange = () => {
3686
+ msgSel = { aid: msgSel.aid, peer: peerSelect.value || null, before: null };
3687
+ saveSessionUiState();
3688
+ subscribe('msg', msgSel);
3689
+ };
3690
+ }
3691
+
3692
+ function msgAgentIsExpanded(aid) {
3693
+ return aid === msgSel.aid && !msgCollapsedAids.has(aid);
3694
+ }
3695
+
3696
+ function renderMsgPeerItem(peer, extraClass = '') {
3697
+ const sel = peer.peerId === msgSel.peer ? ' sel' : '';
3698
+ const isGroup = peer.chatType === 'group';
3699
+ const displayName = peer.chatType === 'group'
3700
+ ? (peer.groupName || peer.peerName || peer.groupId || peer.peerId)
3701
+ : (peer.peerName || peer.peerId);
3702
+ const chatTitle = peer.partition === 'thread'
3703
+ ? (peer.threadName || peer.threadId || displayName)
3704
+ : peer.partition === 'legacy'
3705
+ ? t('messages.legacy.title')
3706
+ : displayName;
3707
+ const channelLabel = peer.channelName && peer.channelName !== 'main'
3708
+ ? `${peer.channelType}/${peer.channelName}`
3709
+ : (peer.channelType || '');
3710
+ const groupMark = isGroup ? `<span class="msg-group-mark">${esc(t('messages.tag.groupShort'))}</span>` : '';
3711
+ const partitionLabel = peer.partition === 'thread'
3712
+ ? t('messages.tag.topic')
3713
+ : peer.partition === 'legacy' ? t('messages.tag.legacy') : t('messages.tag.main');
3714
+ const partitionMark = `<span class="msg-partition-mark msg-partition-${esc(peer.partition || 'main')}">${esc(partitionLabel)}</span>`;
3715
+ const channelTag = channelLabel
3716
+ ? `<span class="msg-tag msg-channel msg-channel-${msgChannelTone(peer.channelType)}">${esc(channelLabel)}</span>`
3717
+ : '';
3718
+ const topicStatus = ({ pending: '待讨论', in_progress: '进行中', completed: '已完成', archived: '已归档' })[peer.topicStatus] || '待讨论';
3719
+ const sub = peer.partition === 'thread' ? `${esc(topicStatus)} · ` : '';
3720
+ const label = peer.label ? `${esc(peer.label)} · ` : '';
3721
+ const threadPeer = peer.partition === 'thread' ? `${esc(shortId(displayName))} · ` : '';
3722
+ return `<div class="list-item msg-peer-item ${extraClass}${sel}" data-peer="${esc(peer.peerId)}">` +
3723
+ `<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
3724
+ `<div class="sub">${sub}${label}${threadPeer}↓${peer.inbound} ↑${peer.outbound} · ${fmtAgo(peer.lastAt)}</div></div>`;
3725
+ }
3726
+
3727
+ function bindDetailPreview(container, view) {
3728
+ if (!container) return;
3729
+ container.onclick = event => {
3730
+ if (event.target.closest('button, a, input, select, textarea, summary, details')) return;
3731
+ if (detailViewState[view]) leaveDetail(view);
3732
+ else enterDetail(view);
3733
+ };
3734
+ }
3735
+
3361
3736
  function renderMsg(data) {
3362
3737
  if (!data) return;
3363
3738
  const aids = data.scopes || data.aids || [];
@@ -3369,6 +3744,7 @@ function renderMsg(data) {
3369
3744
  const nextAid = aids[0]?.aid || null;
3370
3745
  const selectionChanged = msgSel.aid !== nextAid || msgSel.peer !== null;
3371
3746
  msgSel = { aid: nextAid, peer: null, before: null };
3747
+ saveSessionUiState();
3372
3748
  if (selectionChanged && nextAid) subscribe('msg', msgSel);
3373
3749
  }
3374
3750
  const scopeMatches = data.scope === msgSel.aid;
@@ -3379,6 +3755,7 @@ function renderMsg(data) {
3379
3755
  const nextPeer = peers[0]?.peerId || null;
3380
3756
  const selectionChanged = msgSel.peer !== nextPeer;
3381
3757
  msgSel = { aid: msgSel.aid, peer: nextPeer, before: null };
3758
+ saveSessionUiState();
3382
3759
  if (selectionChanged && nextPeer) subscribe('msg', msgSel);
3383
3760
  }
3384
3761
  }
@@ -3386,24 +3763,78 @@ function renderMsg(data) {
3386
3763
  const selectedAid = aids.find(item => item.aid === msgSel.aid);
3387
3764
  const peerTotal = scopeMatches ? peers.length : (selectedAid?.peerCount || 0);
3388
3765
 
3389
- // 左:AID 列表
3390
- let aidsHtml = `<div class="col-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
3766
+ // 左:桌面端 AID 列表;双列布局使用同一栏里的 Agent → Chats 树。
3767
+ let aidListHtml = `<div class="col-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
3391
3768
  for (const a of aids) {
3392
3769
  const sel = a.aid === msgSel.aid ? ' sel' : '';
3393
3770
  const name = a.displayName || (a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown');
3394
3771
  const privateCount = Math.max(0, (a.peerCount || 0) - (a.groupCount || 0));
3395
3772
  const privateBit = tf('messages.privateCount', { count: privateCount });
3396
3773
  const groupBit = a.groupCount ? ` · ${tf('messages.groupCount', { count: a.groupCount })}` : '';
3397
- aidsHtml += `<div class="list-item${sel}" data-aid="${esc(a.aid)}">` +
3774
+ aidListHtml += `<div class="list-item msg-aid-item${sel}" data-aid="${esc(a.aid)}">` +
3398
3775
  `<div class="name">${esc(name)}</div>` +
3399
3776
  `<div class="sub">↓${a.totalIn} ↑${a.totalOut} · ${privateBit}${groupBit} · ${fmtAgo(a.lastAt)}</div></div>`;
3400
3777
  }
3401
- if (!aids.length) aidsHtml += `<div class="empty">${t('messages.empty.noAgents')}</div>`;
3402
- $('#msg-aids').innerHTML = aidsHtml;
3403
- $('#msg-aids').querySelectorAll('.list-item').forEach(item => {
3778
+ if (!aids.length) aidListHtml += `<div class="empty">${t('messages.empty.noAgents')}</div>`;
3779
+
3780
+ let aidTreeHtml = `<div class="msg-tree-title"><span>${t('messages.colTitle.aid')}</span><span class="col-count">${aids.length}</span></div>`;
3781
+ for (const a of aids) {
3782
+ const selected = a.aid === msgSel.aid;
3783
+ const expanded = msgAgentIsExpanded(a.aid);
3784
+ const name = a.displayName || (a.selfAID && a.selfAID !== 'unknown' ? shortAid(a.selfAID) : 'unknown');
3785
+ const peerCount = a.peerCount || 0;
3786
+ const groupBit = a.groupCount ? ` · ${tf('messages.groupCount', { count: a.groupCount })}` : '';
3787
+ let chatsHtml = '';
3788
+ if (expanded) {
3789
+ if (!scopeMatches) chatsHtml = `<div class="msg-tree-empty">${t('common.loading')}</div>`;
3790
+ else if (!peers.length) chatsHtml = `<div class="msg-tree-empty">${t('messages.empty.noPeers')}</div>`;
3791
+ else chatsHtml = peers.map(peer => renderMsgPeerItem(peer, 'msg-tree-chat')).join('');
3792
+ }
3793
+ aidTreeHtml += `<div class="msg-tree-agent${selected ? ' sel' : ''}${expanded ? ' expanded' : ''}">` +
3794
+ `<button type="button" class="msg-tree-agent-toggle" data-aid="${esc(a.aid)}" aria-expanded="${expanded ? 'true' : 'false'}">` +
3795
+ `<span class="msg-tree-chevron" aria-hidden="true">${expanded ? '▾' : '▸'}</span>` +
3796
+ `<span class="msg-tree-agent-main"><span class="name">${esc(name)}</span>` +
3797
+ `<span class="sub">${t('messages.colTitle.peers')} ${peerCount} · ↓${a.totalIn} ↑${a.totalOut}${groupBit ? `${groupBit}` : ''} · ${fmtAgo(a.lastAt)}</span>` +
3798
+ `</span>` +
3799
+ `</button>` +
3800
+ `<div class="msg-tree-chats"${expanded ? '' : ' hidden'}>${chatsHtml}</div>` +
3801
+ `</div>`;
3802
+ }
3803
+ if (!aids.length) aidTreeHtml += `<div class="empty">${t('messages.empty.noAgents')}</div>`;
3804
+ $('#msg-aids').innerHTML = `<div class="msg-aids-list">${aidListHtml}</div><div class="msg-tree-view">${aidTreeHtml}</div>`;
3805
+ $('#msg-aids').querySelectorAll('.msg-aid-item').forEach(item => {
3404
3806
  item.onclick = () => {
3405
3807
  if (item.dataset.aid === msgSel.aid) return;
3808
+ msgCollapsedAids.delete(item.dataset.aid);
3406
3809
  msgSel = { aid: item.dataset.aid, peer: null, before: null };
3810
+ detailViewState.msg = false;
3811
+ saveSessionUiState();
3812
+ subscribe('msg', msgSel);
3813
+ };
3814
+ });
3815
+
3816
+ $('#msg-aids').querySelectorAll('.msg-tree-agent-toggle').forEach(item => {
3817
+ item.onclick = () => {
3818
+ const aid = item.dataset.aid;
3819
+ if (aid !== msgSel.aid) {
3820
+ msgCollapsedAids.delete(aid);
3821
+ msgSel = { aid, peer: null, before: null };
3822
+ detailViewState.msg = false;
3823
+ saveSessionUiState();
3824
+ subscribe('msg', msgSel);
3825
+ return;
3826
+ }
3827
+ if (msgCollapsedAids.has(aid)) msgCollapsedAids.delete(aid);
3828
+ else msgCollapsedAids.add(aid);
3829
+ renderMsg(state.msg);
3830
+ };
3831
+ });
3832
+ $('#msg-aids').querySelectorAll('.msg-tree-chat').forEach(item => {
3833
+ item.onclick = event => {
3834
+ event.stopPropagation();
3835
+ if (item.dataset.peer === msgSel.peer) return;
3836
+ msgSel = { aid: msgSel.aid, peer: item.dataset.peer, before: null };
3837
+ saveSessionUiState();
3407
3838
  subscribe('msg', msgSel);
3408
3839
  };
3409
3840
  });
@@ -3417,40 +3848,20 @@ function renderMsg(data) {
3417
3848
  } else if (!peers.length) {
3418
3849
  peersHtml += `<div class="empty">${t('messages.empty.noPeers')}</div>`;
3419
3850
  } else {
3420
- for (const p of peers) {
3421
- const sel = p.peerId === msgSel.peer ? ' sel' : '';
3422
- const isGroup = p.chatType === 'group';
3423
- const displayName = p.chatType === 'group'
3424
- ? (p.groupName || p.peerName || p.groupId || p.peerId)
3425
- : (p.peerName || p.peerId);
3426
- const chatTitle = p.partition === 'thread'
3427
- ? (p.threadName || p.threadId || displayName)
3428
- : p.partition === 'legacy'
3429
- ? t('messages.legacy.title')
3430
- : displayName;
3431
- const channelLabel = p.channelName && p.channelName !== 'main' ? `${p.channelType}/${p.channelName}` : (p.channelType || '');
3432
- const groupMark = isGroup ? `<span class="msg-group-mark">${esc(t('messages.tag.groupShort'))}</span>` : '';
3433
- const partitionLabel = p.partition === 'thread'
3434
- ? t('messages.tag.topic')
3435
- : p.partition === 'legacy' ? t('messages.tag.legacy') : t('messages.tag.main');
3436
- const partitionMark = `<span class="msg-partition-mark msg-partition-${esc(p.partition || 'main')}">${esc(partitionLabel)}</span>`;
3437
- const channelTag = channelLabel
3438
- ? `<span class="msg-tag msg-channel msg-channel-${msgChannelTone(p.channelType)}">${esc(channelLabel)}</span>`
3439
- : '';
3440
- peersHtml += `<div class="list-item${sel}" data-peer="${esc(p.peerId)}">` +
3441
- `<div class="name">${groupMark}${partitionMark}<span class="msg-peer-title" title="${esc(chatTitle)}">${esc(shortId(chatTitle))}</span>${channelTag}</div>` +
3442
- `<div class="sub">${p.lable ? `${esc(p.lable)} · ` : ''}${p.partition === 'thread' ? `${esc(shortId(displayName))} · ` : ''}↓${p.inbound} ↑${p.outbound} · ${fmtAgo(p.lastAt)}</div></div>`;
3443
- }
3851
+ for (const p of peers) peersHtml += renderMsgPeerItem(p);
3444
3852
  }
3445
3853
  $('#msg-peers').innerHTML = peersHtml;
3446
- $('#msg-peers').querySelectorAll('.list-item').forEach(item => {
3854
+ $('#msg-peers').querySelectorAll('.msg-peer-item').forEach(item => {
3447
3855
  item.onclick = () => {
3448
3856
  if (item.dataset.peer === msgSel.peer) return;
3449
3857
  msgSel = { aid: msgSel.aid, peer: item.dataset.peer, before: null };
3858
+ saveSessionUiState();
3450
3859
  subscribe('msg', msgSel);
3451
3860
  };
3452
3861
  });
3453
3862
 
3863
+ renderMsgMobileControls(aids, peers, scopeMatches);
3864
+
3454
3865
  // 右:消息流
3455
3866
  const stream = $('#msg-stream');
3456
3867
  if (!msgSel.aid) { stream.innerHTML = `<div class="empty">${t('messages.empty.selectToView')}</div>`; return; }
@@ -3489,16 +3900,83 @@ function renderMsg(data) {
3489
3900
  }
3490
3901
  msgHtml += messages.length ? '</div>' : `<div class="empty">${t('messages.empty.noMessages')}</div></div>`;
3491
3902
  stream.innerHTML = renderMsgSessionHeader(sessionContext) + msgHtml;
3903
+ bindDetailPreview(stream, 'msg');
3904
+ const sessionDetailsRow = stream.querySelector('.msg-session-id-row');
3905
+ const sessionDetails = stream.querySelector('.msg-session-grid');
3906
+ if (sessionDetailsRow && sessionDetails) {
3907
+ const detailsKey = [msgSel.aid || '', msgSel.peer || '', sessionContext?.sessionId || ''].join('\u001f');
3908
+ const expanded = msgSessionDetailsExpanded.has(detailsKey)
3909
+ ? msgSessionDetailsExpanded.get(detailsKey) !== false
3910
+ : true;
3911
+ sessionDetails.hidden = !expanded;
3912
+ sessionDetailsRow.setAttribute('aria-expanded', String(expanded));
3913
+ sessionDetails.setAttribute('aria-expanded', String(expanded));
3914
+ const initialLabel = t(`messages.session.details.${expanded ? 'collapse' : 'expand'}`);
3915
+ sessionDetailsRow.setAttribute('aria-label', initialLabel);
3916
+ sessionDetailsRow.title = initialLabel;
3917
+ sessionDetails.setAttribute('aria-label', initialLabel);
3918
+ const initialIcon = sessionDetailsRow.querySelector('.msg-session-toggle-icon');
3919
+ if (initialIcon) initialIcon.textContent = expanded ? '▾' : '▸';
3920
+ const setSessionDetailsExpanded = () => {
3921
+ const nextExpanded = sessionDetails.hidden;
3922
+ sessionDetails.hidden = !nextExpanded;
3923
+ msgSessionDetailsExpanded.set(detailsKey, nextExpanded);
3924
+ sessionDetailsRow.setAttribute('aria-expanded', String(nextExpanded));
3925
+ sessionDetails.setAttribute('aria-expanded', String(nextExpanded));
3926
+ const label = t(`messages.session.details.${nextExpanded ? 'collapse' : 'expand'}`);
3927
+ sessionDetailsRow.setAttribute('aria-label', label);
3928
+ sessionDetailsRow.title = label;
3929
+ sessionDetails.setAttribute('aria-label', label);
3930
+ const icon = sessionDetailsRow.querySelector('.msg-session-toggle-icon');
3931
+ if (icon) icon.textContent = nextExpanded ? '▾' : '▸';
3932
+ };
3933
+ sessionDetailsRow.onclick = event => {
3934
+ event.stopPropagation();
3935
+ setSessionDetailsExpanded();
3936
+ };
3937
+ sessionDetailsRow.onkeydown = event => {
3938
+ if (event.key !== 'Enter' && event.key !== ' ') return;
3939
+ event.preventDefault();
3940
+ event.stopPropagation();
3941
+ setSessionDetailsExpanded();
3942
+ };
3943
+ sessionDetails.onclick = event => {
3944
+ event.stopPropagation();
3945
+ setSessionDetailsExpanded();
3946
+ };
3947
+ sessionDetails.onkeydown = event => {
3948
+ if (event.key !== 'Enter' && event.key !== ' ') return;
3949
+ event.preventDefault();
3950
+ event.stopPropagation();
3951
+ setSessionDetailsExpanded();
3952
+ };
3953
+ }
3492
3954
  const loadMore = stream.querySelector('.msg-load-more');
3493
3955
  if (loadMore) loadMore.onclick = () => {
3494
3956
  msgSel = { aid: msgSel.aid, peer: msgSel.peer, before: Number(loadMore.dataset.before) };
3957
+ saveSessionUiState();
3495
3958
  subscribe('msg', msgSel);
3496
3959
  };
3497
3960
  if (atBottom) stream.scrollTop = stream.scrollHeight;
3498
3961
  }
3499
3962
 
3963
+ function renderDetailToolbar(view, title = '') {
3964
+ return title ? `<div class="detail-toolbar"><span class="detail-toolbar-title">${esc(title)}</span></div>` : '';
3965
+ }
3966
+
3967
+ function renderSessionDetailToolbar() {
3968
+ return '<div class="detail-toolbar sess-detail-toolbar">' +
3969
+ `<span class="detail-toolbar-title">${esc(t('tab.sessions'))}</span>` +
3970
+ '<div class="sess-view-control">' +
3971
+ `<button class="view-toggle${sessChatMode ? ' active' : ''}" id="chat-toggle">` +
3972
+ `${sessChatMode ? t('sessions.chatView') : t('sessions.fullView')}</button>` +
3973
+ `<span class="toolbar-hint">${sessChatMode ? t('sessions.chatHint') : t('sessions.fullHint')}</span>` +
3974
+ '</div>' +
3975
+ '</div>';
3976
+ }
3977
+
3500
3978
  function renderMsgSessionHeader(context) {
3501
- if (!context) return '';
3979
+ if (!context) return renderDetailToolbar('msg');
3502
3980
  const status = context.status === 'processing' ? 'processing' : 'idle';
3503
3981
  const statusLabel = t(`messages.session.status.${status}`);
3504
3982
  const modeKey = context.chatMode === 'proactive' ? 'proactive' : (context.chatMode === 'interactive' ? 'interactive' : '');
@@ -3519,13 +3997,16 @@ function renderMsgSessionHeader(context) {
3519
3997
  `${esc(value)}</span>` +
3520
3998
  '</span>'
3521
3999
  ).join('');
3522
- return '<div class="sess-header msg-session-header">' +
4000
+ const detailsLabel = t('messages.session.details.collapse');
4001
+ return renderDetailToolbar('msg') +
4002
+ '<div class="sess-header msg-session-header">' +
3523
4003
  '<div class="msg-session-context-row">' +
3524
- '<div class="msg-session-id-row">' +
4004
+ `<div class="msg-session-id-row" role="button" tabindex="0" aria-expanded="false" aria-label="${esc(detailsLabel)}" title="${esc(detailsLabel)}">` +
3525
4005
  `<span class="msg-session-label">${t('messages.session.field.sessionId')}</span>` +
3526
4006
  `<span class="msg-session-id" title="${esc(context.sessionId || '—')}">${esc(context.sessionId || '—')}</span>` +
4007
+ '<span class="msg-session-toggle" aria-hidden="true"><span class="msg-session-toggle-icon">▸</span></span>' +
3527
4008
  '</div>' +
3528
- `<div class="msg-session-grid">${stats}</div>` +
4009
+ `<div class="msg-session-grid" role="button" tabindex="0" hidden>${stats}</div>` +
3529
4010
  '</div>' +
3530
4011
  '</div>';
3531
4012
  }
@@ -3663,7 +4144,7 @@ function renderSession(data) {
3663
4144
  // 有效筛选
3664
4145
  const filtered = filterSessionTranscripts(transcripts, sessShowAll, sessSourceFilter);
3665
4146
 
3666
- // 左栏:过滤条 + 列表
4147
+ // 会话列表顶部:过滤条 + 标题/计数;详情保持右侧第二列。
3667
4148
  const projOpts = projects.map(p =>
3668
4149
  `<option value="${esc(p.encoded)}"${p.encoded === sessSel.project ? ' selected' : ''}>${esc(p.label)} (${p.count})</option>`
3669
4150
  ).join('');
@@ -3678,7 +4159,7 @@ function renderSession(data) {
3678
4159
 
3679
4160
  const filterLabelKey = sessShowAll ? 'sessions.filter.all' : 'sessions.filter.valid';
3680
4161
  const filterTitleKey = sessShowAll ? 'sessions.filter.allTitle' : 'sessions.filter.validTitle';
3681
- let listHtml = '<div class="sess-filter">' +
4162
+ const contextHtml = '<div class="sess-filter">' +
3682
4163
  '<div class="sess-select-row">' +
3683
4164
  `<select id="sess-baseagent" title="Base Agent">${baseagentOpts}</select>` +
3684
4165
  `<select id="sess-project" title="${t('sessions.header.project')}">${projOpts}</select>` +
@@ -3691,8 +4172,9 @@ function renderSession(data) {
3691
4172
  `<input id="sess-filter-toggle" type="checkbox"${sessShowAll ? ' checked' : ''}>` +
3692
4173
  '<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>' +
3693
4174
  '</label>' +
3694
- '</div></div>' +
3695
- '<div class="sess-items">';
4175
+ '</div></div>';
4176
+
4177
+ let listHtml = contextHtml + '<div class="sess-items">';
3696
4178
 
3697
4179
  if (!filtered.length) {
3698
4180
  const emptyText = data.noConfiguredBaseagent
@@ -3717,47 +4199,72 @@ function renderSession(data) {
3717
4199
  }
3718
4200
  listHtml += '</div>';
3719
4201
  $('#sess-list').innerHTML = listHtml;
4202
+ bindSessionListScroll();
4203
+ restoreSessionListScroll();
4204
+ window.requestAnimationFrame?.(restoreSessionListScroll);
3720
4205
 
3721
4206
  // 绑定交互
3722
4207
  const baseagentSel = $('#sess-baseagent');
3723
4208
  if (baseagentSel) baseagentSel.onchange = () => {
3724
4209
  console.log('[ecweb] Baseagent changed to:', baseagentSel.value);
3725
4210
  sessSel = { sessionId: null, project: null, baseagent: baseagentSel.value, turnBefore: null };
4211
+ detailViewState.session = false;
4212
+ saveSessionUiState();
3726
4213
  console.log('[ecweb] Subscribing to session with baseagent:', sessSel.baseagent);
3727
4214
  subscribe('session', { baseagent: sessSel.baseagent });
3728
4215
  };
3729
4216
  const projSel = $('#sess-project');
3730
4217
  if (projSel) projSel.onchange = () => {
3731
4218
  sessSel = { sessionId: null, project: projSel.value, baseagent: sessSel.baseagent, turnBefore: null };
4219
+ detailViewState.session = false;
4220
+ saveSessionUiState();
3732
4221
  subscribe('session', { project: sessSel.project, baseagent: sessSel.baseagent });
3733
4222
  };
3734
4223
  const filterToggle = $('#sess-filter-toggle');
3735
- if (filterToggle) filterToggle.onchange = () => { sessShowAll = filterToggle.checked; renderSession(state.session); };
4224
+ if (filterToggle) filterToggle.onchange = () => { sessShowAll = filterToggle.checked; saveSessionUiState(); renderSession(state.session); };
3736
4225
  const sourceTypeSelect = $('#sess-source-type');
3737
4226
  if (sourceTypeSelect) sourceTypeSelect.onchange = () => {
3738
4227
  sessSourceFilter = sourceTypeSelect.value;
4228
+ saveSessionUiState();
3739
4229
  renderSession(state.session);
3740
4230
  };
3741
4231
  $('#sess-list').querySelectorAll('.list-item').forEach(item => {
3742
- item.onclick = () => { sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent, turnBefore: null }; subscribe('session', sessSel); };
4232
+ item.onclick = () => {
4233
+ sessSel = { sessionId: item.dataset.sid, project: sessSel.project, baseagent: sessSel.baseagent, turnBefore: null };
4234
+ saveSessionUiState();
4235
+ if (window.matchMedia?.('(max-width: 479px)').matches) enterDetail('session');
4236
+ subscribe('session', sessSel);
4237
+ };
3743
4238
  });
3744
4239
 
3745
4240
  // 右:transcript 详情
3746
4241
  const detail = $('#sess-detail');
3747
- if (!sessSel.sessionId) { detail.innerHTML = `<div class="empty">${t('sessions.selectLog')}</div>`; return; }
3748
- if (!turns.length) { detail.innerHTML = `<div class="empty">${t('sessions.empty.noContent')}</div>`; return; }
4242
+ if (!sessSel.sessionId) {
4243
+ detail.innerHTML = `<div class="detail-toolbar"><span class="detail-placeholder">${t('sessions.selectLog')}</span></div><div class="empty">${t('sessions.selectLog')}</div>`;
4244
+ bindDetailPreview(detail, 'session');
4245
+ return;
4246
+ }
4247
+ if (!turns.length) {
4248
+ detail.innerHTML = renderDetailToolbar('session') + `<div class="empty">${t('sessions.empty.noContent')}</div>`;
4249
+ bindDetailPreview(detail, 'session');
4250
+ return;
4251
+ }
3749
4252
  const h = data.header || {};
3750
4253
  const atBottom = detail.scrollHeight - detail.scrollTop - detail.clientHeight < 60;
3751
- let html = renderSessHeader(h, data.baseagent || sessSel.baseagent);
4254
+ let html = renderSessionDetailToolbar();
4255
+ html += renderSessHeader(h, data.baseagent || sessSel.baseagent);
3752
4256
  if (data.turnPage?.hasMore && data.turnPage.nextBefore != null) {
3753
4257
  html += `<button class="ctrl-btn sess-load-more" type="button" data-before="${data.turnPage.nextBefore}">${t('sessions.loadOlder') || '加载更早内容'}</button>`;
3754
4258
  }
3755
4259
  html += '<div class="turn-list">' + (sessChatMode ? renderChatView(turns) : renderFullView(turns)) + '</div>';
3756
4260
  detail.innerHTML = html;
4261
+ bindDetailPreview(detail, 'session');
4262
+ bindSessionHeaderToggle(detail, [sessSel.project || '', data.baseagent || sessSel.baseagent || '', h.sessionId || ''].join('\u001f'));
3757
4263
 
3758
4264
  const loadMore = detail.querySelector('.sess-load-more');
3759
4265
  if (loadMore) loadMore.onclick = () => {
3760
4266
  sessSel = { ...sessSel, turnBefore: Number(loadMore.dataset.before) };
4267
+ saveSessionUiState();
3761
4268
  subscribe('session', sessSel);
3762
4269
  };
3763
4270
 
@@ -3768,6 +4275,41 @@ function renderSession(data) {
3768
4275
  if (atBottom) detail.scrollTop = detail.scrollHeight;
3769
4276
  }
3770
4277
 
4278
+ function bindSessionHeaderToggle(container, key) {
4279
+ const toggleRow = container?.querySelector('.sess-header-toggle-row');
4280
+ const details = container?.querySelector('.sess-header-details');
4281
+ if (!toggleRow || !details) return;
4282
+ const remembered = sessHeaderExpanded.get(key);
4283
+ const initialExpanded = remembered == null ? true : remembered !== false;
4284
+ const apply = expanded => {
4285
+ details.hidden = !expanded;
4286
+ const label = t(`sessions.details.${expanded ? 'collapse' : 'expand'}`);
4287
+ toggleRow.setAttribute('aria-expanded', String(expanded));
4288
+ toggleRow.setAttribute('aria-label', label);
4289
+ toggleRow.title = label;
4290
+ details.setAttribute('aria-expanded', String(expanded));
4291
+ details.setAttribute('aria-label', label);
4292
+ const icon = toggleRow.querySelector('.sess-header-toggle-icon');
4293
+ if (icon) icon.textContent = expanded ? '▾' : '▸';
4294
+ };
4295
+ const toggle = event => {
4296
+ event.preventDefault();
4297
+ event.stopPropagation();
4298
+ const expanded = details.hidden;
4299
+ sessHeaderExpanded.set(key, expanded);
4300
+ apply(expanded);
4301
+ };
4302
+ toggleRow.onclick = toggle;
4303
+ details.onclick = toggle;
4304
+ toggleRow.onkeydown = event => {
4305
+ if (event.key === 'Enter' || event.key === ' ') toggle(event);
4306
+ };
4307
+ details.onkeydown = event => {
4308
+ if (event.key === 'Enter' || event.key === ' ') toggle(event);
4309
+ };
4310
+ apply(initialExpanded);
4311
+ }
4312
+
3771
4313
  // 完整视图:所有轮次按 4 类渲染
3772
4314
  function renderFullView(turns) {
3773
4315
  let html = '';
@@ -3897,24 +4439,23 @@ function renderSessHeader(h, baseagent) {
3897
4439
  const dot = h.online ? `<span class="dot on"></span>${t('common.online')}` : `<span class="dot idle"></span>${t('common.offline')}`;
3898
4440
  bind = `<span class="sh-stat">🔗 ${esc(h.boundChannel || '')} · ${esc(shortAid(h.boundSelf || ''))} ${dot}</span>`;
3899
4441
  }
3900
- return '<div class="sess-header">' +
3901
- '<div class="sh-title-row">' +
4442
+ const detailsLabel = t('sessions.details.collapse');
4443
+ return '<div class="sess-header sess-header-collapsible">' +
4444
+ `<div class="sess-header-toggle-row" role="button" tabindex="0" aria-expanded="true" aria-label="${esc(detailsLabel)}" title="${esc(detailsLabel)}">` +
3902
4445
  `<div class="sh-title" title="${esc(title)}">${esc(title)}</div>` +
4446
+ '<span class="sess-header-toggle" aria-hidden="true"><span class="sess-header-toggle-icon">▾</span></span>' +
3903
4447
  '</div>' +
3904
- '<div class="sh-stats">' +
3905
- `<span class="sh-stat" title="${tf('sessions.messageCountTitle', { user: h.userMsgs || 0, total: h.totalMsgs || 0 })}">💬 ${h.userMsgs || 0}/${h.totalMsgs || 0} ${t('sessions.msgUnit')}</span>` +
3906
- (h.model ? `<span class="sh-stat">🤖 ${esc(h.model)}</span>` : '') +
3907
- tok + ctx + cost +
3908
- (h.gitBranch ? `<span class="sh-stat">🌿 ${esc(h.gitBranch)}</span>` : '') +
3909
- (h.version ? `<span class="sh-stat">cc ${esc(h.version)}</span>` : '') +
3910
- bind +
3911
- '</div>' +
3912
- '<div class="sh-footer-row">' +
3913
- renderCatBar(h.counts) +
3914
- '<div class="sess-view-control">' +
3915
- `<button class="view-toggle${sessChatMode ? ' active' : ''}" id="chat-toggle">` +
3916
- `${sessChatMode ? t('sessions.chatView') : t('sessions.fullView')}</button>` +
3917
- `<span class="toolbar-hint">${sessChatMode ? t('sessions.chatHint') : t('sessions.fullHint')}</span>` +
4448
+ '<div class="sess-header-details" role="button" tabindex="0" aria-expanded="true" aria-label="' + esc(detailsLabel) + '">' +
4449
+ '<div class="sh-stats">' +
4450
+ `<span class="sh-stat" title="${tf('sessions.messageCountTitle', { user: h.userMsgs || 0, total: h.totalMsgs || 0 })}">💬 ${h.userMsgs || 0}/${h.totalMsgs || 0} ${t('sessions.msgUnit')}</span>` +
4451
+ (h.model ? `<span class="sh-stat">🤖 ${esc(h.model)}</span>` : '') +
4452
+ tok + ctx + cost +
4453
+ (h.gitBranch ? `<span class="sh-stat">🌿 ${esc(h.gitBranch)}</span>` : '') +
4454
+ (h.version ? `<span class="sh-stat">cc ${esc(h.version)}</span>` : '') +
4455
+ bind +
4456
+ '</div>' +
4457
+ '<div class="sh-footer-row">' +
4458
+ renderCatBar(h.counts) +
3918
4459
  '</div>' +
3919
4460
  '</div>' +
3920
4461
  '</div>';
@@ -5344,7 +5885,7 @@ function openAgentConfigDrawer(aid, data) {
5344
5885
  const fullaccessState = enabled => currentLang === 'zh-CN'
5345
5886
  ? (enabled ? '开启' : '关闭')
5346
5887
  : (enabled ? 'Enabled' : 'Disabled');
5347
- const fullaccessSection = fullaccessStatus.hostEnabled ?
5888
+ const fullaccessSection =
5348
5889
  `<section class="ag-edit-section">` +
5349
5890
  `<div class="ag-edit-fullaccess-row">` +
5350
5891
  `<span class="ag-edit-field-label">Owner Fullaccess<span class="basic-settings-tip" tabindex="0" role="img" aria-label="${esc(fullaccessHelpLabel)}" data-tooltip="${esc(fullaccessHelp)}">?</span></span>` +
@@ -5354,7 +5895,7 @@ function openAgentConfigDrawer(aid, data) {
5354
5895
  `<span class="sess-switch-track" aria-hidden="true"><span class="sess-switch-thumb"></span></span>` +
5355
5896
  `</label>` +
5356
5897
  `</div>` +
5357
- `</section>` : '';
5898
+ `</section>`;
5358
5899
 
5359
5900
  const html = `<div class="ag-edit-backdrop" id="ag-edit-backdrop">` +
5360
5901
  `<aside class="ag-edit-drawer" role="dialog" aria-modal="true" aria-labelledby="ag-edit-title">` +
@@ -5437,7 +5978,6 @@ function openAgentConfigDrawer(aid, data) {
5437
5978
  document.addEventListener('keydown', ctx.onKeydown);
5438
5979
  syncAgentBaseagentFields(ctx);
5439
5980
  syncAgentBaseagentOptions(ctx);
5440
- setTimeout(() => backdrop.querySelector('#ag-edit-owners')?.focus(), 0);
5441
5981
  }
5442
5982
 
5443
5983
  async function agentOpEdit(aid) {
@@ -5787,8 +6327,7 @@ const TRIGGER_EFFORTS = ['low', 'medium', 'high', 'xhigh', 'max'];
5787
6327
  const TRIGGER_PERMISSION_MODES = ['readonly', 'auto', 'request', 'bypass'];
5788
6328
  function triggerPermissionModes(currentMode = '') {
5789
6329
  const modes = [...TRIGGER_PERMISSION_MODES];
5790
- if ((state.triggers?.fullaccessEnabled === true
5791
- && state.triggers?.canApprovePersistentFullAccess === true)
6330
+ if (state.triggers?.canApprovePersistentFullAccess === true
5792
6331
  || currentMode === 'fullaccess') modes.push('fullaccess');
5793
6332
  return modes;
5794
6333
  }
@@ -6330,6 +6869,18 @@ function triggerAgentInfo(agentAid) {
6330
6869
  return (state.triggers?.agents || []).find(item => item.aid === agentAid || item.value === agentAid) || null;
6331
6870
  }
6332
6871
 
6872
+ function isDaemonTriggerAgent(aid, label, channels) {
6873
+ const normalizedAid = String(aid || '').trim().toLowerCase();
6874
+ const normalizedLabel = String(label || '').trim().toLowerCase();
6875
+ return normalizedAid === 'daemon'
6876
+ || normalizedAid === '__daemon__'
6877
+ || normalizedAid.endsWith('#daemon')
6878
+ || normalizedAid.endsWith('.daemon')
6879
+ || normalizedLabel === 'daemon'
6880
+ || (Array.isArray(channels) && channels.some(channel => String(channel).trim().toLowerCase() === 'daemon'))
6881
+ || /\(daemon\)$/i.test(normalizedLabel);
6882
+ }
6883
+
6333
6884
  function triggerChannelOptions(agentAid, current) {
6334
6885
  const channels = [...(triggerAgentInfo(agentAid)?.channels || [])].map(String).filter(Boolean);
6335
6886
  if (current && !channels.includes(current)) channels.push(current);
@@ -6466,31 +7017,22 @@ function triggerRunJson(value) {
6466
7017
  try { return JSON.stringify(value, null, 2); } catch { return String(value); }
6467
7018
  }
6468
7019
 
6469
- function triggerRunPreview(run) {
6470
- return String(
6471
- run?.error?.message
6472
- || run?.reply?.textPreview
6473
- || run?.script?.result?.text
6474
- || run?.script?.stderrPreview
6475
- || run?.script?.stdoutPreview
6476
- || run?.reason
6477
- || '',
6478
- );
6479
- }
6480
-
6481
- function triggerHistoryListHtml(runs, selectedRunId) {
7020
+ function triggerHistoryListHtml(runs, selectedRunId, inlineDetails, renderDetail) {
6482
7021
  if (!runs.length) return `<div class="trig-history-empty">${t('triggers.history.empty')}</div>`;
6483
7022
  return runs.map(run => {
6484
7023
  const runId = String(run.runId || '');
7024
+ const selected = runId === selectedRunId;
7025
+ const expanded = inlineDetails && selected;
6485
7026
  const duration = Number(run.finishedAt || 0) - Number(run.startedAt || 0);
6486
7027
  const source = run.source?.eventName || run.source?.type || '—';
6487
7028
  const execution = run.processing?.mode || run.definition?.name || '—';
6488
- const preview = triggerRunPreview(run);
6489
- return `<button type="button" class="trig-history-item${runId === selectedRunId ? ' selected' : ''}" data-runid="${esc(runId)}">` +
6490
- `<div class="trig-history-item-head"><span>${run.startedAt ? fmtTime(run.startedAt) : '—'}</span>${triggerRunStatusBadge(run.status)}</div>` +
7029
+ return `<article class="trig-history-entry${expanded ? ' expanded' : ''}">` +
7030
+ `<button type="button" class="trig-history-item${selected ? ' selected' : ''}" data-runid="${esc(runId)}" aria-expanded="${expanded}">` +
7031
+ `<div class="trig-history-item-head"><span>${run.startedAt ? fmtTime(run.startedAt) : '—'}</span>${triggerRunStatusBadge(run.status)}<span class="trig-history-chevron" aria-hidden="true">›</span></div>` +
6491
7032
  `<div class="trig-history-item-meta"><span>${esc(source)}</span><span>${esc(execution)}</span><span>${esc(triggerRunDurationLabel(duration))}</span></div>` +
6492
- (preview ? `<div class="trig-history-item-preview">${esc(preview)}</div>` : '') +
6493
- `</button>`;
7033
+ `</button>` +
7034
+ (expanded ? `<div class="trig-history-inline-detail">${renderDetail(run)}</div>` : '') +
7035
+ `</article>`;
6494
7036
  }).join('');
6495
7037
  }
6496
7038
 
@@ -6841,29 +7383,38 @@ function triggerHistoryDetailHtml(run, definition) {
6841
7383
  function renderTriggerHistoryDrawer(ctx) {
6842
7384
  if (_triggerHistory !== ctx) return;
6843
7385
  const list = ctx.backdrop.querySelector('#trig-history-list');
6844
- const detail = ctx.backdrop.querySelector('#trig-history-detail');
6845
7386
  const count = ctx.backdrop.querySelector('#trig-history-count');
7387
+ const detail = ctx.backdrop.querySelector('#trig-history-detail');
7388
+ const inlineDetails = ctx.layoutQuery.matches;
6846
7389
  if (count) count.textContent = tf('triggers.history.runCount', { count: ctx.runs.length });
7390
+ const renderDetail = run => {
7391
+ const revision = String(run?.definition?.revision || '');
7392
+ const historicalDefinition = ctx.definitionsByRevision.get(revision)
7393
+ || (revision && revision === String(ctx.currentRevision || '') ? ctx.currentDefinition : undefined);
7394
+ return triggerHistoryDetailHtml(run, historicalDefinition);
7395
+ };
6847
7396
  if (list) {
6848
- list.innerHTML = triggerHistoryListHtml(ctx.runs, ctx.selectedRunId);
7397
+ list.innerHTML = triggerHistoryListHtml(ctx.runs, ctx.selectedRunId, inlineDetails, renderDetail);
6849
7398
  list.querySelectorAll('.trig-history-item').forEach(item => {
6850
7399
  item.addEventListener('click', () => {
6851
- ctx.selectedRunId = item.dataset.runid || '';
7400
+ const runId = item.dataset.runid || '';
7401
+ ctx.selectedRunId = inlineDetails && ctx.selectedRunId === runId ? '' : runId;
6852
7402
  renderTriggerHistoryDrawer(ctx);
6853
7403
  });
6854
7404
  });
6855
7405
  }
6856
- const selected = ctx.runs.find(run => String(run.runId || '') === ctx.selectedRunId) || null;
6857
- const revision = String(selected?.definition?.revision || '');
6858
- const historicalDefinition = ctx.definitionsByRevision.get(revision)
6859
- || (revision && revision === String(ctx.currentRevision || '') ? ctx.currentDefinition : undefined);
6860
- if (detail) detail.innerHTML = triggerHistoryDetailHtml(selected, historicalDefinition);
7406
+ const selectedRun = ctx.runs.find(run => String(run.runId || '') === ctx.selectedRunId) || null;
7407
+ if (detail) detail.innerHTML = !inlineDetails && selectedRun
7408
+ ? `<div class="trig-history-detail">${renderDetail(selectedRun)}</div>`
7409
+ : `<div class="trig-history-detail-empty">${t('triggers.history.selectRun')}</div>`;
6861
7410
  }
6862
7411
 
6863
7412
  function closeTriggerHistoryDrawer() {
6864
7413
  const ctx = _triggerHistory;
6865
7414
  if (!ctx) return;
6866
7415
  document.removeEventListener('keydown', ctx.onKeydown);
7416
+ if (ctx.layoutQuery?.removeEventListener) ctx.layoutQuery.removeEventListener('change', ctx.onLayoutChange);
7417
+ else if (ctx.layoutQuery?.removeListener) ctx.layoutQuery.removeListener(ctx.onLayoutChange);
6867
7418
  try { ctx.backdrop.remove(); } catch {}
6868
7419
  _triggerHistory = null;
6869
7420
  }
@@ -6882,10 +7433,10 @@ async function openTriggerHistoryDrawer(id, agent) {
6882
7433
  `<button class="ag-edit-close" id="trig-history-close" type="button" aria-label="${t('common.close')}">×</button>` +
6883
7434
  `</div>` +
6884
7435
  `<div class="trig-history-content">` +
6885
- `<div class="trig-history-list-pane"><div id="trig-history-count" class="trig-history-pane-title">${t('triggers.history.loading')}</div><div id="trig-history-list" class="trig-history-list"><div class="trig-history-empty">${t('triggers.history.loading')}</div></div></div>` +
6886
- `<div id="trig-history-detail" class="trig-history-detail"><div class="trig-history-detail-empty">${t('triggers.history.selectRun')}</div></div>` +
7436
+ `<div class="trig-history-list-pane"><div id="trig-history-list" class="trig-history-list"><div class="trig-history-empty">${t('triggers.history.loading')}</div></div></div>` +
7437
+ `<div id="trig-history-detail" class="trig-history-detail-pane"><div class="trig-history-detail-empty">${t('triggers.history.selectRun')}</div></div>` +
6887
7438
  `</div>` +
6888
- `<div class="ag-edit-actions"><button class="ctrl-btn" id="trig-history-done" type="button">${t('common.close')}</button></div>` +
7439
+ `<div class="ag-edit-actions trig-history-actions"><span id="trig-history-count" class="trig-history-count" aria-live="polite">${t('triggers.history.loading')}</span><button class="ctrl-btn" id="trig-history-done" type="button">${t('common.close')}</button></div>` +
6889
7440
  `</aside>` +
6890
7441
  `</div>`;
6891
7442
  const wrap = document.createElement('div');
@@ -6896,16 +7447,20 @@ async function openTriggerHistoryDrawer(id, agent) {
6896
7447
  backdrop,
6897
7448
  runs: [],
6898
7449
  selectedRunId: '',
7450
+ layoutQuery: window.matchMedia('(orientation: portrait)'),
6899
7451
  currentDefinition: item.definition,
6900
7452
  currentRevision: item.revision,
6901
7453
  definitionsByRevision: new Map(),
6902
7454
  onKeydown: e => { if (e.key === 'Escape') closeTriggerHistoryDrawer(); },
7455
+ onLayoutChange: () => renderTriggerHistoryDrawer(ctx),
6903
7456
  };
6904
7457
  _triggerHistory = ctx;
6905
7458
  backdrop.addEventListener('click', e => { if (e.target === backdrop) closeTriggerHistoryDrawer(); });
6906
7459
  backdrop.querySelector('#trig-history-close').addEventListener('click', closeTriggerHistoryDrawer);
6907
7460
  backdrop.querySelector('#trig-history-done').addEventListener('click', closeTriggerHistoryDrawer);
6908
7461
  document.addEventListener('keydown', ctx.onKeydown);
7462
+ if (ctx.layoutQuery.addEventListener) ctx.layoutQuery.addEventListener('change', ctx.onLayoutChange);
7463
+ else if (ctx.layoutQuery.addListener) ctx.layoutQuery.addListener(ctx.onLayoutChange);
6909
7464
 
6910
7465
  try {
6911
7466
  const response = await ipcSend({ type: 'trigger.history', agentAid: agent, triggerId: id, limit: 500 }, 10000);
@@ -7758,7 +8313,6 @@ function openTriggerEditDrawer(id, agent) {
7758
8313
  if (_triggerEdit === ctx) toast(e.message || String(e), true);
7759
8314
  });
7760
8315
  if (triggerUsesModelOptions(ctx)) syncTriggerOptionFields(ctx, original);
7761
- setTimeout(() => backdrop.querySelector('#trig-edit-name')?.focus(), 0);
7762
8316
  }
7763
8317
 
7764
8318
  function renderTriggers(data) {
@@ -7769,7 +8323,7 @@ function renderTriggers(data) {
7769
8323
 
7770
8324
  // 左列:agent 列表(仿 msg list-item 风格)
7771
8325
  let aHtml = '<div class="col-title trig-title">' +
7772
- '<span>Agent</span>' +
8326
+ `<span>${t('triggers.th.agent')}</span>` +
7773
8327
  `<button id="trig-agents-toggle" class="trig-collapse-btn" title="${trigAgentsCollapsed ? t('triggers.collapse.expand') : t('triggers.collapse.collapse')}">${trigAgentsCollapsed ? '›' : '‹'}</button>` +
7774
8328
  '</div>';
7775
8329
  if (!trigAgentsCollapsed) {
@@ -7777,9 +8331,12 @@ function renderTriggers(data) {
7777
8331
  for (const ag of agents) {
7778
8332
  const aid = ag.aid || ag.value || '';
7779
8333
  const sel = aid === selAid ? ' sel' : '';
8334
+ const isDaemonAgent = isDaemonTriggerAgent(aid, ag.label, ag.channels);
8335
+ const label = isDaemonAgent ? t('triggers.agent.daemon') : ag.label;
7780
8336
  aHtml += `<div class="list-item${sel}" data-aid="${esc(aid)}">` +
7781
- `<div class="name">${esc(ag.label)}</div>` +
7782
- `<div class="sub">${esc(aid)}</div></div>`;
8337
+ `<div class="name">${esc(label)}</div>` +
8338
+ (isDaemonAgent ? '' : `<div class="sub">${esc(aid)}</div>`) +
8339
+ '</div>';
7783
8340
  }
7784
8341
  }
7785
8342
  const agentCol = $('#trig-agents');
@@ -7797,11 +8354,13 @@ function renderTriggers(data) {
7797
8354
  if (trigAgentsCollapsed) return;
7798
8355
  if (!trigSel.agent) return;
7799
8356
  trigSel.agent = null;
8357
+ saveSessionUiState();
7800
8358
  subscribe('triggers', {});
7801
8359
  };
7802
8360
  agentCol.querySelectorAll('.list-item').forEach(item => {
7803
8361
  item.onclick = () => {
7804
8362
  trigSel.agent = item.dataset.aid;
8363
+ saveSessionUiState();
7805
8364
  subscribe('triggers', { agent: trigSel.agent });
7806
8365
  };
7807
8366
  });
@@ -7809,14 +8368,14 @@ function renderTriggers(data) {
7809
8368
  // 右列:table,每字段一列
7810
8369
  const el = $('#trig-table');
7811
8370
  if (!triggers.length) {
7812
- el.innerHTML = `<div class="empty" style="padding:16px">${selAid ? t('triggers.empty.noAgentTriggers') : t('triggers.empty.noTriggers')}</div>`;
8371
+ el.innerHTML = `<div class="trig-table-empty empty" style="padding:16px">${selAid ? t('triggers.empty.noAgentTriggers') : t('triggers.empty.noTriggers')}</div>`;
7813
8372
  return;
7814
8373
  }
7815
8374
 
7816
8375
  let html = '<table><thead><tr>' +
7817
- `<th>Agent</th><th>${t('triggers.th.status')}</th><th>${t('triggers.th.subscription')}</th><th>${t('triggers.th.name')}</th><th>ID</th><th>${t('triggers.th.type')}</th><th>${t('triggers.th.expression')}</th>` +
8376
+ `<th class="trig-agent-col">${t('triggers.th.agent')}</th><th>${t('triggers.th.status')}</th><th class="trig-subscription-col">${t('triggers.th.subscription')}</th><th>${t('triggers.th.name')}</th><th class="trig-id-col">${t('triggers.th.id')}</th><th>${t('triggers.th.type')}</th><th>${t('triggers.th.expression')}</th>` +
7818
8377
  `<th>${t('triggers.th.lastFire')}</th><th>${t('triggers.th.nextFire')}</th><th>${t('triggers.th.fireCount')}</th><th>${t('triggers.th.failCount')}</th><th>${t('triggers.th.lastResult')}</th><th>${t('triggers.field.executionType')}</th>` +
7819
- `<th>${t('triggers.th.targetChannel')}</th><th>${t('triggers.field.channelId')}</th><th>${t('triggers.th.channelType')}</th>` +
8378
+ `<th class="trig-target-channel-col">${t('triggers.th.targetChannel')}</th><th class="trig-channel-id-col">${t('triggers.field.channelId')}</th><th class="trig-channel-type-col">${t('triggers.th.channelType')}</th>` +
7820
8379
  `<th>${t('triggers.th.createdBy')}</th><th>${t('triggers.th.createdChannel')}</th><th>${t('triggers.th.createdAt')}</th><th class="trig-ops-col">${t('triggers.th.operations')}</th>` +
7821
8380
  '</tr></thead><tbody>';
7822
8381
  for (const trigger of triggers) {
@@ -7825,42 +8384,62 @@ function renderTriggers(data) {
7825
8384
  const lastFireTime = trigger.lastFiredAt || trigger.lastScheduledAt;
7826
8385
  const lastResult = trigger.lastResult || (trigger.lastScheduledAt ? t('triggers.lastUnrecorded') : '');
7827
8386
  const agentAid = trigger.agentAid || trigger.schedulerAid || selAid || '';
7828
- const agentLabel = trigger.agentLabel || (agentAid ? shortAid(agentAid) : '');
8387
+ const agentInfo = triggerAgentInfo(agentAid);
8388
+ const agentLabel = isDaemonTriggerAgent(agentAid, trigger.agentLabel, agentInfo?.channels)
8389
+ ? t('triggers.agent.daemon')
8390
+ : (trigger.agentLabel || (agentAid ? shortAid(agentAid) : ''));
7829
8391
  const sourceType = triggerSourceType(trigger);
7830
- html += `<tr class="${active ? '' : 'trig-disabled'}">` +
7831
- `<td title="${esc(agentAid)}">${esc(agentLabel)}</td>` +
7832
- `<td>${trigStatusBadge(status)}</td>` +
7833
- `<td>${trigSubscriptionBadge(trigger.subscription, sourceType)}</td>` +
7834
- `<td>${esc(trigger.name ?? trigger.label ?? '')}</td>` +
7835
- `<td>${esc(trigger.id ?? trigger.value ?? '')}</td>` +
7836
- `<td>${esc(triggerScheduleLabel(sourceType || trigger.scheduleType || ''))}</td>` +
7837
- `<td>${esc(triggerScheduleValueLabel(trigger.scheduleType, trigger.scheduleValue))}</td>` +
7838
- `<td>${lastFireTime ? fmtTime(lastFireTime) : '—'}</td>` +
7839
- `<td>${trigger.nextFireAt ? fmtTime(trigger.nextFireAt) : '—'}</td>` +
7840
- `<td>${trigger.fireCount ?? 0}</td>` +
7841
- `<td>${trigger.failCount ? `<span style="color:var(--red)">${trigger.failCount}</span>` : '0'}</td>` +
7842
- `<td>${lastResult ? esc(lastResult) : '—'}</td>` +
7843
- `<td>${esc(triggerExecutionTypeLabel(trigger.definition?.execution?.type ?? trigger.executionType ?? trigger.execution?.type))}</td>` +
7844
- `<td>${esc(trigger.targetChannel ?? '')}</td>` +
7845
- `<td>${esc(trigger.targetChannelId ?? '')}</td>` +
7846
- `<td>${esc(trigger.targetChannelType ?? '')}</td>` +
7847
- `<td>${esc(trigger.createdByPeerId ?? '')}</td>` +
7848
- `<td>${esc(trigger.createdByChannel ?? '')}</td>` +
7849
- `<td>${trigger.createdAt ? fmtTime(trigger.createdAt) : '—'}</td>` +
7850
- `<td class="trig-ops-col">${triggerOpsHtml(trigger)}</td>` +
8392
+ const mobileLabel = key => ` data-mobile-label="${esc(t(key))}"`;
8393
+ html += `<tr class="trig-row${active ? '' : ' trig-disabled'}" tabindex="0" aria-expanded="false">` +
8394
+ `<td class="trig-agent-col"${mobileLabel('triggers.th.agent')} title="${esc(agentAid)}">${esc(agentLabel)}</td>` +
8395
+ `<td${mobileLabel('triggers.th.status')}>${trigStatusBadge(status)}</td>` +
8396
+ `<td class="trig-subscription-col"${mobileLabel('triggers.th.subscription')}>${trigSubscriptionBadge(trigger.subscription, sourceType)}</td>` +
8397
+ `<td${mobileLabel('triggers.th.name')}>${esc(trigger.name ?? trigger.label ?? '')}</td>` +
8398
+ `<td class="trig-id-col"${mobileLabel('triggers.th.id')}>${esc(trigger.id ?? trigger.value ?? '')}</td>` +
8399
+ `<td${mobileLabel('triggers.th.type')}>${esc(triggerScheduleLabel(sourceType || trigger.scheduleType || ''))}</td>` +
8400
+ `<td${mobileLabel('triggers.th.expression')}>${esc(triggerScheduleValueLabel(trigger.scheduleType, trigger.scheduleValue))}</td>` +
8401
+ `<td${mobileLabel('triggers.th.lastFire')}>${lastFireTime ? fmtTime(lastFireTime) : '—'}</td>` +
8402
+ `<td${mobileLabel('triggers.th.nextFire')}>${trigger.nextFireAt ? fmtTime(trigger.nextFireAt) : '—'}</td>` +
8403
+ `<td${mobileLabel('triggers.th.fireCount')}>${trigger.fireCount ?? 0}</td>` +
8404
+ `<td${mobileLabel('triggers.th.failCount')}>${trigger.failCount ? `<span style="color:var(--red)">${trigger.failCount}</span>` : '0'}</td>` +
8405
+ `<td${mobileLabel('triggers.th.lastResult')}>${lastResult ? esc(lastResult) : '—'}</td>` +
8406
+ `<td${mobileLabel('triggers.field.executionType')}>${esc(triggerExecutionTypeLabel(trigger.definition?.execution?.type ?? trigger.executionType ?? trigger.execution?.type))}</td>` +
8407
+ `<td class="trig-target-channel-col"${mobileLabel('triggers.th.targetChannel')}>${esc(trigger.targetChannel ?? '')}</td>` +
8408
+ `<td class="trig-channel-id-col"${mobileLabel('triggers.field.channelId')}>${esc(trigger.targetChannelId ?? '')}</td>` +
8409
+ `<td class="trig-channel-type-col"${mobileLabel('triggers.th.channelType')}>${esc(trigger.targetChannelType ?? '')}</td>` +
8410
+ `<td${mobileLabel('triggers.th.createdBy')}>${esc(trigger.createdByPeerId ?? '')}</td>` +
8411
+ `<td${mobileLabel('triggers.th.createdChannel')}>${esc(trigger.createdByChannel ?? '')}</td>` +
8412
+ `<td${mobileLabel('triggers.th.createdAt')}>${trigger.createdAt ? fmtTime(trigger.createdAt) : '—'}</td>` +
8413
+ `<td class="trig-ops-col"${mobileLabel('triggers.th.operations')}>${triggerOpsHtml(trigger)}</td>` +
7851
8414
  '</tr>';
7852
8415
  }
7853
8416
  html += '</tbody></table>';
7854
- el.innerHTML = html;
8417
+ el.innerHTML = `<div class="trig-table-wide">${html}</div>`;
7855
8418
  ensureOpsOutsideClose();
8419
+ bindTriggerOps(el);
8420
+ el.querySelectorAll('.trig-row').forEach(row => {
8421
+ const toggle = event => {
8422
+ if (event.target.closest('button, a, input, select, textarea, summary, details')) return;
8423
+ const expanded = row.classList.toggle('trig-expanded');
8424
+ row.setAttribute('aria-expanded', String(expanded));
8425
+ };
8426
+ row.addEventListener('click', toggle);
8427
+ row.addEventListener('keydown', event => {
8428
+ if (event.key !== 'Enter' && event.key !== ' ') return;
8429
+ event.preventDefault();
8430
+ toggle(event);
8431
+ });
8432
+ });
8433
+ }
7856
8434
 
7857
- el.querySelectorAll('.trig-ops button[data-trig-op]').forEach(btn => {
8435
+ function bindTriggerOps(container) {
8436
+ container.querySelectorAll('.trig-ops button[data-trig-op]').forEach(btn => {
7858
8437
  btn.addEventListener('click', async (e) => {
7859
8438
  const op = btn.dataset.trigOp;
7860
8439
  if (op === 'more') {
7861
8440
  const more = btn.closest('.ops-more');
7862
8441
  const wasOpen = more?.classList.contains('open');
7863
- document.querySelectorAll('.ops-more.open').forEach(m => m.classList.remove('open'));
8442
+ document.querySelectorAll('.ops-more.open').forEach(item => item.classList.remove('open'));
7864
8443
  if (more && !wasOpen) more.classList.add('open');
7865
8444
  e.stopPropagation();
7866
8445
  return;
@@ -7881,8 +8460,9 @@ function startApp() {
7881
8460
  initTabs();
7882
8461
  initRolesTab();
7883
8462
  initRoleDefinitionsTab();
8463
+ initHistory();
7884
8464
  // 恢复保存的 tab 视图
7885
- switchView(currentView);
8465
+ switchView(currentView, { history: false });
7886
8466
  connect();
7887
8467
  $('#logout-btn').onclick = () => {
7888
8468
  localStorage.removeItem(TOKEN_KEY);
@@ -7910,13 +8490,17 @@ function resolveTheme(preference) {
7910
8490
 
7911
8491
  function updateThemeButton() {
7912
8492
  const btn = $('#theme-btn');
7913
- if (!btn) return;
7914
8493
  const icons = { light: '☀️', dark: '🌙', system: '🖥️' };
7915
- const label = t('theme.buttonTitle').replace('{mode}', t('theme.' + themePreference));
7916
- btn.textContent = icons[themePreference] || icons.light;
7917
- btn.title = label;
7918
- btn.setAttribute('aria-label', label);
7919
- btn.dataset.themeMode = themePreference;
8494
+ const modeLabel = t('theme.' + themePreference);
8495
+ const label = tf('theme.buttonTitle', { mode: modeLabel });
8496
+ if (btn) {
8497
+ btn.textContent = icons[themePreference] || icons.light;
8498
+ btn.title = label;
8499
+ btn.setAttribute('aria-label', label);
8500
+ btn.dataset.themeMode = themePreference;
8501
+ }
8502
+ const mobileLabel = $('[data-mobile-theme-label]');
8503
+ if (mobileLabel) mobileLabel.textContent = tf('theme.drawerLabel', { mode: modeLabel });
7920
8504
  }
7921
8505
 
7922
8506
  function disposeWindowChart(key) {
@@ -10037,6 +10621,7 @@ const COMMAND_PERMISSION_CATEGORY_ORDER = ['read', 'diagnose', 'write-own', 'wri
10037
10621
  const COMMAND_PERMISSION_BOOLEAN_CONSTRAINTS = [
10038
10622
  'ownPeerOnly',
10039
10623
  'ownAgentOnly',
10624
+ 'currentRelationOnly',
10040
10625
  'privateOnly',
10041
10626
  'groupOnly',
10042
10627
  'requireDaemonOwner',
@@ -10104,6 +10689,7 @@ function commandConstraintLabel(key) {
10104
10689
  const labels = {
10105
10690
  ownPeerOnly: currentLang === 'zh-CN' ? '仅本人 Peer' : 'Own peer only',
10106
10691
  ownAgentOnly: currentLang === 'zh-CN' ? '仅当前 Agent' : 'Own agent only',
10692
+ currentRelationOnly: currentLang === 'zh-CN' ? '仅当前关系' : 'Current relation only',
10107
10693
  privateOnly: currentLang === 'zh-CN' ? '仅私聊' : 'Private only',
10108
10694
  groupOnly: currentLang === 'zh-CN' ? '仅群聊' : 'Group only',
10109
10695
  requireDaemonOwner: currentLang === 'zh-CN' ? '需要 daemon owner' : 'Require daemon owner',