myagent-ai 1.32.4 → 1.32.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.32.4",
3
+ "version": "1.32.5",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -1,11 +1,16 @@
1
1
  // ========== Platforms ==========
2
+ // [v1.32.4] Bug修复: 1) 修复inline onclick中平台ID特殊字符导致的XSS风险
3
+ // 2) 使用data-action事件委托替代inline onclick
4
+ // 3) 模态框使用存储ID模式避免特殊字符注入
5
+ var _editingPlatformId='';
6
+
2
7
  async function renderPlatforms(){
3
8
  const ps=await api('/api/platforms');
4
9
  if(ps.error){$('content').innerHTML='<div class="empty" style="color:var(--danger)">加载失败: '+escHtml(ps.error)+'</div>';return}
5
10
  const icons={telegram:'📱',discord:'🎮',feishu:'🐦',qq:'🐧',wechat:'💚',whatsapp:'💬'};
6
11
  let html=`<div class="flex justify-between items-center mb-16">
7
12
  <div style="color:var(--text2);font-size:13px">共 ${ps.length} 个平台实例</div>
8
- <button class="btn btn-primary" onclick="showAddPlatformModal()">+ 添加平台</button></div>`;
13
+ <button class="btn btn-primary" data-action="addPlatform">+ 添加平台</button></div>`;
9
14
  if(!ps.length){html+='<div class="empty">暂无聊天平台配置,点击上方按钮添加</div>';$('content').innerHTML=html;return}
10
15
  html+='<div class="grid">';
11
16
  for(const p of ps){
@@ -17,7 +22,6 @@ async function renderPlatforms(){
17
22
  const _btnClass=_isEnabled?'btn-danger':'btn-success';
18
23
  const _btnText=_isEnabled?'停用':'启用';
19
24
  const _newState=!_isEnabled;
20
- // [v1.23.56] 使用 data 属性避免 onclick 中特殊字符问题
21
25
  html+=`<div class="card"><div class="flex justify-between items-center">
22
26
  <h3 style="color:var(--text)">${icons[p.platform]||'📡'} ${escHtml(p.display_name||p.platform)}</h3>
23
27
  <span class="badge ${p.enabled?'badge-green':'badge-red'}">${p.enabled?'已启用':'未启用'}</span>
@@ -25,8 +29,8 @@ async function renderPlatforms(){
25
29
  ${bindInfo?'<p style="font-size:12px;color:var(--text3);margin-top:4px">'+bindInfo+'</p>':''}
26
30
  <div class="mt-8 flex gap-8">
27
31
  <button class="btn btn-sm ${_btnClass}" data-toggle-platform="${escHtml(pid)}" data-toggle-state="${_newState}">${_btnText}</button>
28
- <button class="btn btn-sm btn-ghost" onclick="showEditPlatformModal('${escHtml(pid)}')">配置</button>
29
- ${_supportsQR?'<button class="btn btn-sm btn-primary" onclick="showPlatformQRModal(\''+escHtml(pid)+'\')">📱 QR绑定</button>':''}
32
+ <button class="btn btn-sm btn-ghost" data-action="editPlatform" data-pid="${escHtml(pid)}">配置</button>
33
+ ${_supportsQR?'<button class="btn btn-sm btn-primary" data-action="showQR" data-pid="'+escHtml(pid)+'">📱 QR绑定</button>':''}
30
34
  </div></div>`;
31
35
  }
32
36
  html+='</div>';$('content').innerHTML=html;
@@ -69,12 +73,12 @@ function showAddPlatformModal(){
69
73
  <div class="form-group"><label>绑定 Agent</label>
70
74
  <div class="flex gap-8">
71
75
  <input id="apBindAgent" placeholder="手输 Agent ID (可选)" style="flex:1">
72
- <button class="btn btn-sm btn-ghost" onclick="showAgentSelectorForPlatform('apBindAgent','apBindAgents')">选择 Agent</button>
76
+ <button class="btn btn-sm btn-ghost" data-action="selectAgent" data-input="apBindAgent" data-container="apBindAgents">选择 Agent</button>
73
77
  </div>
74
78
  <div id="apBindAgents" style="font-size:11px;color:var(--text3);margin-top:4px"></div>
75
79
  <div class="hint">手输 Agent ID 或点击"选择 Agent"从部门中选择。支持绑定多个 Agent。</div>
76
80
  </div>
77
- <div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="doAddPlatform()">添加</button><button class="btn btn-ghost" onclick="closeModal()">取消</button></div>
81
+ <div class="flex gap-8 mt-16"><button class="btn btn-primary" data-action="doAddPlatform">添加</button><button class="btn btn-ghost" onclick="closeModal()">取消</button></div>
78
82
  </div></div>`;
79
83
  }
80
84
  function onPlatformTypeChange(){
@@ -95,9 +99,12 @@ async function doAddPlatform(){
95
99
  if(r.error){showToast(r.error,'danger');return}closeModal();showToast('已添加: '+(r.display_name||r.platform),'success');renderPlatforms();
96
100
  }
97
101
  async function showEditPlatformModal(id){
98
- const p=await api(`/api/platforms/${id}`);
102
+ _editingPlatformId=id;
103
+ const p=await api(`/api/platforms/${encodeURIComponent(id)}`);
99
104
  if(p.error){showToast(p.error,'danger');return}
100
105
  const pid=p.id||id;
106
+ // 更新存储的 ID(可能因 API 返回而不同)
107
+ _editingPlatformId=pid;
101
108
  var bindAgentVal=p.bind_agent||'';
102
109
  var bindAgentsArr=p.bind_agents||[];
103
110
  $('modalContainer').innerHTML=`<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()" style="max-width:520px">
@@ -111,27 +118,29 @@ async function showEditPlatformModal(id){
111
118
  <div class="form-group"><label>绑定 Agent</label>
112
119
  <div class="flex gap-8">
113
120
  <input id="epBindAgent" value="${escHtml(bindAgentVal)}" placeholder="手输 Agent ID (可选)" style="flex:1">
114
- <button class="btn btn-sm btn-ghost" onclick="showAgentSelectorForPlatform('epBindAgent','epBindAgents')">选择 Agent</button>
121
+ <button class="btn btn-sm btn-ghost" data-action="selectAgent" data-input="epBindAgent" data-container="epBindAgents">选择 Agent</button>
115
122
  </div>
116
123
  <div id="epBindAgents" style="font-size:11px;color:var(--text3);margin-top:4px">${bindAgentsArr.map(function(a){return '<span class="selected-agent-tag" data-agent="'+escHtml(a)+'" style="display:inline-block;background:var(--bg);border:1px solid var(--border);border-radius:4px;padding:1px 6px;margin:2px;font-size:11px;cursor:pointer" onclick="this.remove()" title="点击移除">'+escHtml(a)+' ×</span>';}).join('')}</div>
117
124
  <div class="hint">手输 Agent ID 或点击"选择 Agent"从部门中选择。一个平台可以绑定多个 Agent。</div>
118
125
  </div>
119
- <div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="doSavePlatform('${escHtml(pid)}')">保存</button><button class="btn btn-danger" onclick="doDeletePlatform('${escHtml(pid)}')">删除平台</button><button class="btn btn-ghost" onclick="closeModal()">关闭</button></div>
126
+ <div class="flex gap-8 mt-16"><button class="btn btn-primary" data-action="doSavePlatform">保存</button><button class="btn btn-danger" data-action="doDeletePlatform">删除平台</button><button class="btn btn-ghost" onclick="closeModal()">关闭</button></div>
120
127
  </div></div>`;
121
128
  }
122
- async function doSavePlatform(id){
129
+ async function doSavePlatform(){
130
+ var id=_editingPlatformId;if(!id){showToast('平台ID丢失','danger');return}
123
131
  var bindAgent=$('epBindAgent').value.trim();
124
132
  var bindAgents=[];
125
133
  var selectedEls=document.querySelectorAll('#epBindAgents .selected-agent-tag');
126
134
  selectedEls.forEach(function(el){var v=el.getAttribute('data-agent');if(v)bindAgents.push(v);});
127
135
  if(bindAgent)bindAgents.unshift(bindAgent);
128
136
  bindAgents=[...new Set(bindAgents)];
129
- const r=await api(`/api/platforms/${id}`,{method:'PUT',body:JSON.stringify({display_name:$('epDisplayName').value,token:$('epToken').value,app_id:$('epAppId').value,app_secret:$('epSecret').value||undefined,webhook_url:$('epWebhook').value,allowed_users:$('epUsers').value.split(',').map(s=>s.trim()).filter(Boolean),bind_agent:bindAgents[0]||'',bind_agents:bindAgents})});
137
+ const r=await api(`/api/platforms/${encodeURIComponent(id)}`,{method:'PUT',body:JSON.stringify({display_name:$('epDisplayName').value,token:$('epToken').value,app_id:$('epAppId').value,app_secret:$('epSecret').value||undefined,webhook_url:$('epWebhook').value,allowed_users:$('epUsers').value.split(',').map(s=>s.trim()).filter(Boolean),bind_agent:bindAgents[0]||'',bind_agents:bindAgents})});
130
138
  if(r.error){showToast(r.error,'danger');return}closeModal();showToast('已保存','success');renderPlatforms();
131
139
  }
132
- async function doDeletePlatform(id){
140
+ async function doDeletePlatform(){
141
+ var id=_editingPlatformId;if(!id){showToast('平台ID丢失','danger');return}
133
142
  showConfirm('删除平台','确认删除该平台实例吗?',async()=>{
134
- const r=await api(`/api/platforms/${id}`,{method:'DELETE'});if(r.error){showToast(r.error,'danger');closeModal();return}closeModal();showToast('已删除','success');renderPlatforms();
143
+ const r=await api(`/api/platforms/${encodeURIComponent(id)}`,{method:'DELETE'});if(r.error){showToast(r.error,'danger');closeModal();return}closeModal();showToast('已删除','success');renderPlatforms();
135
144
  });
136
145
  }
137
146
 
@@ -139,7 +148,9 @@ async function doDeletePlatform(id){
139
148
  let _qrPollTimer=null;
140
149
  async function showPlatformQRModal(platformId){
141
150
  if(_qrPollTimer){clearInterval(_qrPollTimer);_qrPollTimer=null}
142
- $('modalContainer').innerHTML='<div class="modal-overlay" onclick="closeModal();if(_qrPollTimer){clearInterval(_qrPollTimer);_qrPollTimer=null}"><div class="modal" onclick="event.stopPropagation()" style="max-width:420px;text-align:center"><h3>📱 QR 码绑定</h3><p style="font-size:13px;color:var(--text2);margin:8px 0">使用手机扫描下方二维码以绑定账号</p><div id="qrStatus" style="margin:12px 0;padding:12px;border-radius:var(--radius-sm);background:var(--bg3);font-size:13px;color:var(--text2)">正在请求 QR 码...</div><div id="qrCodeContainer" style="display:flex;justify-content:center;padding:16px 0"><div id="qrCodeImg" style="width:256px;height:256px;border:1px solid var(--bg4);border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:13px">加载中...</div></div><div id="qrActions" style="display:none;margin-top:12px"><button class="btn btn-sm btn-ghost" onclick="requestNewQR(\''+escHtml(platformId)+'\')">🔄 刷新 QR 码</button></div><div class="flex gap-8 mt-16"><button class="btn btn-ghost" onclick="closeModal();if(_qrPollTimer){clearInterval(_qrPollTimer);_qrPollTimer=null}">关闭</button></div></div></div>';
151
+ $('modalContainer').innerHTML='<div class="modal-overlay" onclick="closeModal();if(_qrPollTimer){clearInterval(_qrPollTimer);_qrPollTimer=null}"><div class="modal" onclick="event.stopPropagation()" style="max-width:420px;text-align:center"><h3>📱 QR 码绑定</h3><p style="font-size:13px;color:var(--text2);margin:8px 0">使用手机扫描下方二维码以绑定账号</p><div id="qrStatus" style="margin:12px 0;padding:12px;border-radius:var(--radius-sm);background:var(--bg3);font-size:13px;color:var(--text2)">正在请求 QR 码...</div><div id="qrCodeContainer" style="display:flex;justify-content:center;padding:16px 0"><div id="qrCodeImg" style="width:256px;height:256px;border:1px solid var(--bg4);border-radius:8px;display:flex;align-items:center;justify-content:center;color:var(--text3);font-size:13px">加载中...</div></div><div id="qrActions" style="display:none;margin-top:12px"><button class="btn btn-sm btn-ghost" data-action="refreshQR">🔄 刷新 QR 码</button></div><div class="flex gap-8 mt-16"><button class="btn btn-ghost" onclick="closeModal();if(_qrPollTimer){clearInterval(_qrPollTimer);_qrPollTimer=null}">关闭</button></div></div></div>';
152
+ // 存储当前 QR 平台 ID
153
+ _editingPlatformId=platformId;
143
154
  var r=await api('/api/platforms/'+encodeURIComponent(platformId)+'/qr',{method:'POST'});
144
155
  if(r.error){$('qrStatus').textContent='错误: '+escHtml(r.error);return}
145
156
  if(r.qr_code){
@@ -184,10 +195,14 @@ async function requestNewQR(platformId){
184
195
  // Agent 选择器(从部门树选择 Agent)
185
196
  // [v1.23.56] 修复: 不再覆盖 modalContainer,而是保存/恢复编辑表单 HTML
186
197
  var _savedEditModalHtml='';
198
+ var _agentSelectorInputId='';
199
+ var _agentSelectorContainerId='';
187
200
  async function showAgentSelectorForPlatform(inputId,containerId){
188
201
  try{
189
202
  // 保存当前编辑表单 HTML
190
203
  _savedEditModalHtml=$('modalContainer').innerHTML;
204
+ _agentSelectorInputId=inputId;
205
+ _agentSelectorContainerId=containerId;
191
206
  var agents=await api('/api/agents');
192
207
  if(!Array.isArray(agents)){showToast('加载 Agent 列表失败','danger');return}
193
208
  var agentList=agents.filter(function(a){return!a.system&&a.path!=='default'});
@@ -214,18 +229,18 @@ async function showAgentSelectorForPlatform(inputId,containerId){
214
229
  });
215
230
  html+='</div>';
216
231
  }
217
- html+='<div class="flex gap-8 mt-16"><button class="btn btn-primary" onclick="confirmAgentSelection(\''+inputId+'\',\''+containerId+'\')">确定</button><button class="btn btn-ghost" onclick="cancelAgentSelection()">取消</button></div></div></div>';
232
+ html+='<div class="flex gap-8 mt-16"><button class="btn btn-primary" data-action="confirmAgentSelection">确定</button><button class="btn btn-ghost" data-action="cancelAgentSelection">取消</button></div></div></div>';
218
233
  $('modalContainer').innerHTML=html;
219
234
  }catch(e){showToast('加载失败: '+e.message,'danger')}
220
235
  }
221
- function confirmAgentSelection(inputId,containerId){
236
+ function confirmAgentSelection(){
222
237
  // 收集选中的 agent 列表
223
238
  var checked=[];
224
239
  document.querySelectorAll('.modal input[type="checkbox"]:checked').forEach(function(cb){checked.push(cb.value);});
225
240
  // 恢复编辑表单
226
241
  if(_savedEditModalHtml){$('modalContainer').innerHTML=_savedEditModalHtml;_savedEditModalHtml='';}
227
242
  // 更新编辑表单中的 agent 标签
228
- var container=document.getElementById(containerId);
243
+ var container=document.getElementById(_agentSelectorContainerId);
229
244
  if(container){
230
245
  container.innerHTML='';
231
246
  checked.forEach(function(agentPath){
@@ -240,7 +255,7 @@ function confirmAgentSelection(inputId,containerId){
240
255
  });
241
256
  }
242
257
  // 同步第一个到输入框
243
- var input=document.getElementById(inputId);
258
+ var input=document.getElementById(_agentSelectorInputId);
244
259
  if(input){input.value=checked[0]||'';}
245
260
  }
246
261
  function cancelAgentSelection(){
@@ -254,3 +269,37 @@ function toggleAgentSelection(cb,inputId,containerId){
254
269
 
255
270
  if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
256
271
  window._adminRenderers['platforms'] = renderPlatforms;
272
+
273
+ // [v1.32.4] 事件委托: 处理平台管理页面的 data-action 按钮(防止 XSS 注入)
274
+ document.addEventListener('click', function(e) {
275
+ var btn = e.target.closest('[data-action]');
276
+ if (!btn) return;
277
+ var action = btn.getAttribute('data-action');
278
+
279
+ // 主内容区按钮(data-page=platforms)
280
+ if (btn.closest('#content')?.getAttribute('data-page') === 'platforms') {
281
+ switch (action) {
282
+ case 'addPlatform': showAddPlatformModal(); break;
283
+ case 'editPlatform': showEditPlatformModal(btn.getAttribute('data-pid') || ''); break;
284
+ case 'showQR': showPlatformQRModal(btn.getAttribute('data-pid') || ''); break;
285
+ }
286
+ }
287
+
288
+ // 模态框内的按钮(不在 #content 内,需要单独处理)
289
+ if (btn.closest('#modalContainer')) {
290
+ switch (action) {
291
+ case 'doAddPlatform': doAddPlatform(); break;
292
+ case 'doSavePlatform': doSavePlatform(); break;
293
+ case 'doDeletePlatform': doDeletePlatform(); break;
294
+ case 'selectAgent':
295
+ showAgentSelectorForPlatform(
296
+ btn.getAttribute('data-input') || '',
297
+ btn.getAttribute('data-container') || ''
298
+ );
299
+ break;
300
+ case 'confirmAgentSelection': confirmAgentSelection(); break;
301
+ case 'cancelAgentSelection': cancelAgentSelection(); break;
302
+ case 'refreshQR': requestNewQR(_editingPlatformId); break;
303
+ }
304
+ }
305
+ });
@@ -1,4 +1,8 @@
1
1
  // ========== Sessions ==========
2
+ // [v1.32.4] Bug修复: 1) 修复硬编码agentDbId=1导致切入会话跳转错误Agent
3
+ // 2) 修复inline onclick的XSS风险(改用data-action事件委托)
4
+ // 3) 修复"返回查看"导航错误(应返回消息视图而非会话列表)
5
+ // 4) 修复"清除"操作无确认提示
2
6
  async function renderSessions(){
3
7
  const ss=await api('/api/sessions');
4
8
  if(!ss||!ss.length){
@@ -22,19 +26,22 @@ async function renderSessions(){
22
26
  const displayName=s.display_name||s.id;
23
27
  const dbId=s.agent_db_id||1;
24
28
  html+=`<tr><td title="${escHtml(s.id)}">${escHtml(displayName.length>30?displayName.slice(0,30)+'...':displayName)}</td><td>${escHtml(agentName)}</td><td>${s.messages}</td><td>${s.last?.slice(0,19)||''}</td>
25
- <td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}',${dbId})">切入</button>
26
- <button class="btn btn-sm btn-ghost" onclick="viewSession('${escHtml(s.id)}')">查看</button>
27
- <button class="btn btn-sm btn-danger" onclick="clearSession('${escHtml(s.id)}')">清除</button></td></tr>`;}
29
+ <td><button class="btn btn-sm" style="background:var(--success);color:#fff" data-action="enterSession" data-sid="${escHtml(s.id)}" data-aid="${dbId}">切入</button>
30
+ <button class="btn btn-sm btn-ghost" data-action="viewSession" data-sid="${escHtml(s.id)}" data-aid="${dbId}">查看</button>
31
+ <button class="btn btn-sm btn-danger" data-action="clearSession" data-sid="${escHtml(s.id)}">清除</button></td></tr>`;}
28
32
  html+='</table></div>';
29
33
  $('content').innerHTML=html;
30
34
  }
31
- async function viewSession(sid){
32
- window._viewSessionSid=sid;window._viewSessionOffset=0;
35
+ async function viewSession(sid, agentDbId){
36
+ window._viewSessionSid=sid;
37
+ window._viewSessionAgentDbId=agentDbId||1;
38
+ window._viewSessionOffset=0;
33
39
  _navSubState='view:'+sid;
34
40
  navigateTo('sessions','view:'+sid,_loadSessionMessages);
35
41
  }
36
42
  async function _loadSessionMessages(){
37
43
  const sid=window._viewSessionSid;if(!sid)return;
44
+ const agentDbId=window._viewSessionAgentDbId||1;
38
45
  const offset=window._viewSessionOffset||0;
39
46
  const msgs=await api(`/api/session/messages?sid=${encodeURIComponent(sid)}&limit=100&offset=${offset}`);
40
47
  if(!Array.isArray(msgs)){showToast('加载失败','danger');return}
@@ -79,15 +86,15 @@ async function _loadSessionMessages(){
79
86
  html+=`<div style="flex-shrink:0;font-size:16px;margin-top:2px">${avatar}</div>`;
80
87
  html+=`<div class="msg-bubble-wrap" style="min-width:0"><div style="font-size:11px;color:var(--text3);margin-bottom:2px;${isUser?'text-align:right':''}">${isUser?'用户':'助手'} <span style="margin-left:4px">${time}</span></div>`;
81
88
  html+=`<div style="background:${bg};color:${fg};padding:10px 14px;border-radius:var(--radius);font-size:13px;line-height:1.6;white-space:pre-wrap;word-break:break-word">${escHtml(displayContent)}</div>`;
82
- if(long)html+=`<button class="btn btn-sm btn-ghost" style="margin-top:4px;font-size:11px" onclick="this.previousElementSibling.textContent=this.dataset.full;this.remove()" data-full="${escHtml(content).replace(/"/g,'&quot;')}">展开全部 (${content.length}字符)</button>`;
89
+ if(long)html+=`<button class="btn btn-sm btn-ghost" style="margin-top:4px;font-size:11px" data-action="expandMsg" data-full="${escHtml(content).replace(/"/g,'&quot;')}">展开全部 (${content.length}字符)</button>`;
83
90
  html+=`</div></div>`;
84
91
  }
85
92
  }
86
93
  html+='</div>';
87
- if(hasMore)html+=`<button class="btn btn-ghost mt-8" onclick="window._viewSessionOffset=${offset+100};_loadSessionMessages()">加载更多...</button>`;
88
- html+='<div class="flex gap-8 mt-8"><button class="btn btn-ghost" onclick="goBack()">返回</button>';
89
- html+=`<button class="btn" style="background:var(--accent);color:#fff" onclick="viewSessionRaw('${escHtml(sid)}')">Raw 原始消息</button>`;
90
- html+=`<button class="btn btn-primary" onclick="enterSession('${escHtml(sid)}',1)">在聊天中查看完整记录</button></div>`;
94
+ if(hasMore)html+=`<button class="btn btn-ghost mt-8" data-action="loadMore" data-offset="${offset+100}">加载更多...</button>`;
95
+ html+='<div class="flex gap-8 mt-8"><button class="btn btn-ghost" data-action="backToSessions">返回</button>';
96
+ html+=`<button class="btn" style="background:var(--accent);color:#fff" data-action="viewRaw" data-sid="${escHtml(sid)}">Raw 原始消息</button>`;
97
+ html+=`<button class="btn btn-primary" data-action="enterSessionChat" data-sid="${escHtml(sid)}" data-aid="${agentDbId}">在聊天中查看完整记录</button></div>`;
91
98
  $('content').innerHTML=html;
92
99
  }
93
100
  // ========== Raw 原始消息查看 ==========
@@ -96,7 +103,7 @@ var _rawMsgsCache=[];
96
103
  async function viewSessionRaw(sid){
97
104
  window._viewSessionSid=sid;
98
105
  _navSubState='raw:'+sid;
99
- _navHistory.push({page:currentPage,sub:window._navSubState});
106
+ _navHistory.push({page:currentPage,sub:'view:'+sid});
100
107
  if(_navHistory.length>6)_navHistory.shift();
101
108
  var hash='sessions'+'~raw:'+sid;
102
109
  history.pushState({page:'sessions',sub:'raw:'+sid},'','#'+hash);
@@ -158,8 +165,8 @@ function _renderRawView(msgs,sid){
158
165
  html+=`</div></div>`;
159
166
  }
160
167
  html+=`</div>`;
161
- html+=`<div class="flex gap-8 mt-8"><button class="btn btn-ghost" onclick="goBack()">返回查看</button>`;
162
- html+=`<button class="btn btn-ghost" onclick="goBack()">返回会话列表</button></div>`;
168
+ html+=`<div class="flex gap-8 mt-8"><button class="btn btn-ghost" data-action="backToMsgView">返回查看</button>`;
169
+ html+=`<button class="btn btn-ghost" data-action="backToSessions">返回会话列表</button></div>`;
163
170
  $('content').innerHTML=html;
164
171
  _buildTimeNav(msgs);
165
172
  }
@@ -232,7 +239,14 @@ function _buildTimeNav(msgs){
232
239
  }
233
240
  nav.innerHTML=navHtml;
234
241
  }
235
- async function clearSession(sid){await api(`/api/session?sid=${encodeURIComponent(sid)}`,{method:'DELETE'});renderSessions()}
242
+ async function clearSession(sid){
243
+ showConfirm('清除会话', '确定要清除该会话的所有消息吗?此操作不可恢复。', async function(){
244
+ var r = await api(`/api/session?sid=${encodeURIComponent(sid)}`,{method:'DELETE'});
245
+ if(r.error){showToast('清除失败: '+r.error,'danger');return}
246
+ showToast('会话已清除','success');
247
+ renderSessions();
248
+ });
249
+ }
236
250
  // 切入会话: 打开聊天页面并自动加载指定会话
237
251
  // [v1.27.2] 使用 aid(数字 agent ID)+ s(编码 session ID),不再暴露明文 agent 名字
238
252
  function enterSession(sid,agentDbId){
@@ -241,3 +255,44 @@ function enterSession(sid,agentDbId){
241
255
 
242
256
  if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
243
257
  window._adminRenderers['sessions'] = renderSessions;
258
+
259
+ // [v1.32.4] 事件委托: 处理会话管理页面的 data-action 按钮(防止 XSS 注入)
260
+ document.addEventListener('click', function(e) {
261
+ var btn = e.target.closest('[data-action]');
262
+ if (!btn) return;
263
+ var action = btn.getAttribute('data-action');
264
+
265
+ // 会话列表页按钮(data-page=sessions)
266
+ if (btn.closest('#content')?.getAttribute('data-page') === 'sessions') {
267
+ var sid = btn.getAttribute('data-sid') || '';
268
+ var aid = parseInt(btn.getAttribute('data-aid')) || 1;
269
+ switch (action) {
270
+ case 'enterSession': enterSession(sid, aid); break;
271
+ case 'viewSession': viewSession(sid, aid); break;
272
+ case 'clearSession': clearSession(sid); break;
273
+ case 'viewRaw': viewSessionRaw(sid); break;
274
+ case 'enterSessionChat': enterSession(sid, aid); break;
275
+ case 'loadMore':
276
+ window._viewSessionOffset = parseInt(btn.getAttribute('data-offset')) || 0;
277
+ _loadSessionMessages();
278
+ break;
279
+ case 'backToSessions':
280
+ _navSubState = null;
281
+ renderSessions();
282
+ break;
283
+ case 'backToMsgView':
284
+ // 返回消息视图(不回到列表)
285
+ _navSubState = 'view:' + (window._viewSessionSid || '');
286
+ _loadSessionMessages();
287
+ break;
288
+ case 'expandMsg':
289
+ // 展开长消息
290
+ var prev = btn.previousElementSibling;
291
+ if (prev && btn.dataset.full) {
292
+ prev.textContent = btn.dataset.full;
293
+ btn.remove();
294
+ }
295
+ break;
296
+ }
297
+ }
298
+ });
@@ -1,5 +1,7 @@
1
1
  // ========== Site Management ==========
2
2
  // [v1.31.1] 网站注册/浏览器Profile统一管理
3
+ // [v1.32.4] Bug修复: 1) 修复模态框inline onclick中网站名称特殊字符导致的XSS风险
4
+ // 2) 使用data-action事件委托+存储ID模式替代inline onclick
3
5
 
4
6
  const SITE_CAT_ICONS = {
5
7
  email: '\u{1F4E7}', social: '\u{1F4AC}', cloud: '\u2601\uFE0F',
@@ -12,6 +14,8 @@ const SITE_CAT_LABELS = {
12
14
 
13
15
  let _sitesFilter = '';
14
16
  let _sitesProfileView = false;
17
+ // 存储当前正在操作/编辑的网站名称(避免在onclick中传递,防XSS)
18
+ var _editingSiteName = '';
15
19
 
16
20
  async function renderSites() {
17
21
  _sitesProfileView = false;
@@ -44,8 +48,8 @@ async function renderSites() {
44
48
  '</div>' +
45
49
  '<div class="flex gap-8">' +
46
50
  '<input id="siteSearch" placeholder="搜索网站..." style="width:200px" oninput="filterSites()">' +
47
- '<button class="btn btn-primary btn-sm" onclick="showAddSiteModal()">+ 添加网站</button>' +
48
- '<button class="btn btn-ghost btn-sm" onclick="initAllProfiles()">初始化所有 Profile</button>' +
51
+ '<button class="btn btn-primary btn-sm" data-action="addSite">+ 添加网站</button>' +
52
+ '<button class="btn btn-ghost btn-sm" data-action="initAllProfiles">初始化所有 Profile</button>' +
49
53
  '</div></div>';
50
54
 
51
55
  // Render each category group
@@ -121,6 +125,7 @@ function filterSites() {
121
125
 
122
126
  // ── View Site Detail ──
123
127
  async function viewSiteDetail(name) {
128
+ _editingSiteName = name;
124
129
  var s = await api('/api/sites/' + encodeURIComponent(name));
125
130
  if (s.error) { showToast(s.error, 'danger'); return; }
126
131
 
@@ -166,8 +171,8 @@ async function viewSiteDetail(name) {
166
171
  urlsHtml + tipsHtml + profileInfo +
167
172
  '<div class="flex gap-8 mt-16">' +
168
173
  '<button class="btn btn-ghost" onclick="closeModal()">关闭</button>' +
169
- (!s.is_builtin ? '<button class="btn btn-primary" onclick="closeModal();editSiteModal(\'' + escHtml(s.name) + '\')">编辑</button>' : '') +
170
- '<button class="btn btn-ghost" onclick="initSiteProfile(\'' + escHtml(s.name) + '\');closeModal()">初始化 Profile</button>' +
174
+ (!s.is_builtin ? '<button class="btn btn-primary" data-action="editSiteFromModal">编辑</button>' : '') +
175
+ '<button class="btn btn-ghost" data-action="initProfileFromModal">初始化 Profile</button>' +
171
176
  '</div>';
172
177
 
173
178
  $('modalContainer').innerHTML = '<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()" style="max-width:600px">' + html + '</div></div>';
@@ -197,7 +202,7 @@ function showAddSiteModal() {
197
202
  '<div class="form-group"><label>登录检测 URL</label>' +
198
203
  '<input id="siteFormDetectUrl" placeholder="https://example.com/dashboard"></div>' +
199
204
  '<div class="flex gap-8 mt-16">' +
200
- '<button class="btn btn-primary" onclick="doAddSite()">添加</button>' +
205
+ '<button class="btn btn-primary" data-action="doAddSite">添加</button>' +
201
206
  '<button class="btn btn-ghost" onclick="closeModal()">取消</button>' +
202
207
  '</div>';
203
208
 
@@ -227,6 +232,7 @@ async function doAddSite() {
227
232
 
228
233
  // ── Edit Site Modal ──
229
234
  async function editSiteModal(name) {
235
+ _editingSiteName = name;
230
236
  var s = await api('/api/sites/' + encodeURIComponent(name));
231
237
  if (s.error) { showToast(s.error, 'danger'); return; }
232
238
 
@@ -252,14 +258,16 @@ async function editSiteModal(name) {
252
258
  '<div class="form-group"><label>登录检测 URL</label>' +
253
259
  '<input id="editSiteDetectUrl" value="' + escHtml(s.detect_url || '') + '"></div>' +
254
260
  '<div class="flex gap-8 mt-16">' +
255
- '<button class="btn btn-primary" onclick="doUpdateSite(\'' + escHtml(s.name) + '\')">保存</button>' +
261
+ '<button class="btn btn-primary" data-action="doUpdateSite">保存</button>' +
256
262
  '<button class="btn btn-ghost" onclick="closeModal()">取消</button>' +
257
263
  '</div>';
258
264
 
259
265
  $('modalContainer').innerHTML = '<div class="modal-overlay" onclick="closeModal()"><div class="modal" onclick="event.stopPropagation()" style="max-width:500px">' + html + '</div></div>';
260
266
  }
261
267
 
262
- async function doUpdateSite(name) {
268
+ async function doUpdateSite() {
269
+ var name = _editingSiteName;
270
+ if (!name) { showToast('网站名称丢失', 'danger'); return; }
263
271
  var data = {
264
272
  display_name: $('editSiteDisplay')?.value || '',
265
273
  category: $('editSiteCategory')?.value || 'custom',
@@ -342,18 +350,41 @@ if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
342
350
  window._adminRenderers['sites'] = renderSites;
343
351
 
344
352
  // [v1.31.4] 事件委托: 处理网站管理页面的 data-action 按钮(防止 XSS 注入)
353
+ // [v1.32.4] 扩展事件委托:同时处理内容区和模态框内的按钮
345
354
  document.addEventListener('click', function(e) {
346
355
  var btn = e.target.closest('[data-action]');
347
- if (!btn || btn.closest('#content')?.getAttribute('data-page') !== 'sites') return;
356
+ if (!btn) return;
348
357
  var action = btn.getAttribute('data-action');
349
358
  var name = btn.getAttribute('data-name') || '';
350
- switch (action) {
351
- case 'viewSite': viewSiteDetail(name); break;
352
- case 'editSite': editSiteModal(name); break;
353
- case 'removeSite': removeSite(name); break;
354
- case 'openBrowser': openSiteBrowser(name); break;
355
- case 'closeBrowser': closeSiteBrowser(name); break;
356
- case 'deleteProfile': deleteSiteProfile(name); break;
357
- case 'initProfile': initSiteProfile(name); break;
359
+
360
+ // 主内容区按钮(data-page=sites)
361
+ if (btn.closest('#content')?.getAttribute('data-page') === 'sites') {
362
+ switch (action) {
363
+ case 'viewSite': viewSiteDetail(name); break;
364
+ case 'editSite': editSiteModal(name); break;
365
+ case 'removeSite': removeSite(name); break;
366
+ case 'openBrowser': openSiteBrowser(name); break;
367
+ case 'closeBrowser': closeSiteBrowser(name); break;
368
+ case 'deleteProfile': deleteSiteProfile(name); break;
369
+ case 'initProfile': initSiteProfile(name); break;
370
+ case 'addSite': showAddSiteModal(); break;
371
+ case 'initAllProfiles': initAllProfiles(); break;
372
+ }
373
+ }
374
+
375
+ // 模态框内的按钮
376
+ if (btn.closest('#modalContainer')) {
377
+ switch (action) {
378
+ case 'doAddSite': doAddSite(); break;
379
+ case 'doUpdateSite': doUpdateSite(); break;
380
+ case 'editSiteFromModal':
381
+ closeModal();
382
+ if (_editingSiteName) editSiteModal(_editingSiteName);
383
+ break;
384
+ case 'initProfileFromModal':
385
+ if (_editingSiteName) initSiteProfile(_editingSiteName);
386
+ closeModal();
387
+ break;
388
+ }
358
389
  }
359
390
  });
@@ -148,7 +148,7 @@ function guideRowHtml(s){
148
148
  const isDisabled=s.disabled;
149
149
  const refTag=(s.references||[]).length>0?`<span class="tag">${s.references.length} 参考资料</span>`:'';
150
150
  const tplTag=s.has_templates?'<span class="tag">含模板</span>':'';
151
- const scriptTag=s.has_scripts?'<span class="tag">含脚本</span>`:'';
151
+ const scriptTag=s.has_scripts?'<span class="tag">含脚本</span>':'';
152
152
  const catBadge=s.category?`<span class="badge badge-purple">${escHtml(s.category)}</span>`:'';
153
153
  return `<div class="skill-row" data-name="${escHtml(s.name||'')}" data-desc="${escHtml(s.description||'')}">
154
154
  <div style="flex-shrink:0;width:36px;text-align:center;font-size:18px">📝</div>