myagent-ai 1.32.4 → 1.32.6
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 +1 -1
- package/web/api_server.py +4 -2
- package/web/ui/admin/admin-platforms.js +67 -18
- package/web/ui/admin/admin-sessions.js +69 -14
- package/web/ui/admin/admin-sites.js +47 -16
- package/web/ui/admin/admin-skills.js +1 -1
- package/web/ui/chat/chat_main.js +65 -39
- package/web/ui/chat/flow_engine.js +2 -2
- package/web/ui/chat/groupchat.js +2 -2
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -4331,7 +4331,8 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4331
4331
|
if agent:
|
|
4332
4332
|
# [v1.27.2] 先将 agent 路径转为数字 ID,用数字 ID 精确过滤
|
|
4333
4333
|
target_aid = self.core.memory.get_agent_id(agent) if agent else 0
|
|
4334
|
-
|
|
4334
|
+
# [v1.32.4] 兼容旧路径 "default" 和新路径 "1",两者都指全权Agent
|
|
4335
|
+
if agent == "1" or agent == "default":
|
|
4335
4336
|
# default agent: agent_id=0(未分配)或 agent_id=1(default 的数字 ID)或 旧格式
|
|
4336
4337
|
rows = conn.execute(
|
|
4337
4338
|
f"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last,
|
|
@@ -4400,7 +4401,8 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4400
4401
|
return web.json_response({"agent": name, "sessions": []})
|
|
4401
4402
|
# [v1.27.2] 使用数字 agent_id 精确过滤,同时兼容旧数据
|
|
4402
4403
|
target_aid = self.core.memory.get_agent_id(name)
|
|
4403
|
-
|
|
4404
|
+
# [v1.32.4] 兼容旧路径 "default" 和新路径 "1"
|
|
4405
|
+
if name == "1" or name == "default":
|
|
4404
4406
|
# default agent: agent_id=0 或 agent_id=1 或 旧格式
|
|
4405
4407
|
rows = self.core.memory._get_conn().execute(
|
|
4406
4408
|
"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories
|
|
@@ -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"
|
|
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"
|
|
29
|
-
${_supportsQR?'<button class="btn btn-sm btn-primary"
|
|
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"
|
|
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"
|
|
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
|
-
|
|
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"
|
|
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"
|
|
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(
|
|
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(
|
|
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"
|
|
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"
|
|
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(
|
|
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(
|
|
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(
|
|
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"
|
|
26
|
-
<button class="btn btn-sm btn-ghost"
|
|
27
|
-
<button class="btn btn-sm btn-danger"
|
|
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;
|
|
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"
|
|
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,'"')}">展开全部 (${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"
|
|
88
|
-
html+='<div class="flex gap-8 mt-8"><button class="btn btn-ghost"
|
|
89
|
-
html+=`<button class="btn" style="background:var(--accent);color:#fff"
|
|
90
|
-
html+=`<button class="btn btn-primary"
|
|
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:
|
|
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"
|
|
162
|
-
html+=`<button class="btn btn-ghost"
|
|
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){
|
|
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"
|
|
48
|
-
'<button class="btn btn-ghost btn-sm"
|
|
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"
|
|
170
|
-
'<button class="btn btn-ghost"
|
|
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"
|
|
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"
|
|
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(
|
|
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
|
|
356
|
+
if (!btn) return;
|
|
348
357
|
var action = btn.getAttribute('data-action');
|
|
349
358
|
var name = btn.getAttribute('data-name') || '';
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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>
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -150,7 +150,7 @@ function popoutChat() {
|
|
|
150
150
|
const h = Math.min(720, screen.height - 100);
|
|
151
151
|
const left = Math.round((screen.width - w) / 2);
|
|
152
152
|
const top = Math.round((screen.height - h) / 2);
|
|
153
|
-
window.open(popoutUrl, 'myagent_popout_' + (state.activeAgent || '
|
|
153
|
+
window.open(popoutUrl, 'myagent_popout_' + (state.activeAgent || '1'), 'width=' + w + ',height=' + h + ',left=' + left + ',top=' + top + ',menubar=no,toolbar=no,location=no,status=no,scrollbars=yes,resizable=yes');
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
// Override toggleAgentPanel for mobile (deferred since function is defined later)
|
|
@@ -227,11 +227,11 @@ const state = {
|
|
|
227
227
|
// ── Agent state (层级体系) ──
|
|
228
228
|
agentTree: [], // 树形结构 [{path, name, children: [...], ...}]
|
|
229
229
|
agentsFlat: [], // 扁平列表 [{path, name, parent, depth, ...}]
|
|
230
|
-
activeAgent: '
|
|
230
|
+
activeAgent: '1',
|
|
231
231
|
agentPanelOpen: true,
|
|
232
232
|
agentSessions: {}, // {agentPath: [{id, name, messages, last}]}
|
|
233
233
|
agentDetails: {}, // {agentPath: {soul, identity, model, ...}}
|
|
234
|
-
expandedNodes: new Set(['
|
|
234
|
+
expandedNodes: new Set(['1']), // 树形展开状态
|
|
235
235
|
modelsLibrary: [], // 模型库 [{id, name, provider, model, base_url, enabled, has_api_key, ...}]
|
|
236
236
|
// ── Message queue during generation ──
|
|
237
237
|
pendingMessages: [], // 待发送的消息队列 [{text, time}]
|
|
@@ -330,7 +330,7 @@ const StatePersistence = {
|
|
|
330
330
|
// 持久化当前活跃会话ID,用于页面刷新后恢复
|
|
331
331
|
if (state.activeSessionId && state.activeSessionId !== '__new__') {
|
|
332
332
|
StatePersistence.save('activeSessionId', state.activeSessionId);
|
|
333
|
-
StatePersistence.save('activeSessionAgent', state.activeAgent || '
|
|
333
|
+
StatePersistence.save('activeSessionAgent', state.activeAgent || '1');
|
|
334
334
|
}
|
|
335
335
|
// 持久化当前会话的滚动位置(立即保存,不防抖)
|
|
336
336
|
if (typeof saveScrollPosition === 'function') {
|
|
@@ -344,12 +344,12 @@ const StatePersistence = {
|
|
|
344
344
|
},
|
|
345
345
|
/** 恢复 UI 状态 */
|
|
346
346
|
restoreUIState() {
|
|
347
|
-
state.activeAgent = StatePersistence.load('activeAgent', '
|
|
347
|
+
state.activeAgent = StatePersistence.load('activeAgent', '1');
|
|
348
348
|
state.chatMode = StatePersistence.load('chatMode', 'exec');
|
|
349
349
|
state.agentPanelOpen = StatePersistence.load('agentPanelOpen', true);
|
|
350
350
|
var savedRpSections = StatePersistence.load('rpSections');
|
|
351
351
|
if (savedRpSections) { Object.assign(rpSections, savedRpSections); }
|
|
352
|
-
const savedNodes = StatePersistence.load('expandedNodes', ['
|
|
352
|
+
const savedNodes = StatePersistence.load('expandedNodes', ['1']);
|
|
353
353
|
state.expandedNodes = new Set(savedNodes);
|
|
354
354
|
// 恢复上次活跃的会话 ID,供 loadSessions() 使用
|
|
355
355
|
var savedSessionId = StatePersistence.load('activeSessionId', null);
|
|
@@ -529,23 +529,23 @@ async function initChat() {
|
|
|
529
529
|
} else if (urlAgent) {
|
|
530
530
|
var resolved = urlAgent;
|
|
531
531
|
if (!findAgentByPath(resolved)) {
|
|
532
|
-
console.warn('URL agent not found, fallback to
|
|
533
|
-
resolved =
|
|
532
|
+
console.warn('URL agent not found, fallback to master:', resolved);
|
|
533
|
+
resolved = findMasterAgentPath();
|
|
534
534
|
}
|
|
535
535
|
if (resolved !== state.activeAgent) {
|
|
536
536
|
selectAgent(resolved);
|
|
537
537
|
}
|
|
538
538
|
} else if (urlSession) {
|
|
539
539
|
// 只有 session 没有 agent,尝试从 session ID 推断 agent(兼容旧格式 agent_web_xxx)
|
|
540
|
-
var targetAgent =
|
|
540
|
+
var targetAgent = findMasterAgentPath();
|
|
541
541
|
if (urlSession.indexOf('_web_') >= 0) {
|
|
542
|
-
targetAgent = urlSession.split('_web_')[0] ||
|
|
542
|
+
targetAgent = urlSession.split('_web_')[0] || findMasterAgentPath();
|
|
543
543
|
}
|
|
544
|
-
// 新格式 sess_xxx 无法推断 agent,回退
|
|
544
|
+
// 新格式 sess_xxx 无法推断 agent,回退 master
|
|
545
545
|
var target = targetAgent;
|
|
546
546
|
if (!findAgentByPath(target)) {
|
|
547
|
-
console.warn('Session-inferred agent not found, fallback to
|
|
548
|
-
target =
|
|
547
|
+
console.warn('Session-inferred agent not found, fallback to master:', target);
|
|
548
|
+
target = findMasterAgentPath();
|
|
549
549
|
}
|
|
550
550
|
if (target !== state.activeAgent) {
|
|
551
551
|
selectAgent(target);
|
|
@@ -835,7 +835,7 @@ function hasTaskListChanged() {
|
|
|
835
835
|
|
|
836
836
|
async function loadTaskPlan() {
|
|
837
837
|
try {
|
|
838
|
-
var agent = state.activeAgent || '
|
|
838
|
+
var agent = state.activeAgent || '1';
|
|
839
839
|
var session = state.activeSessionId || '';
|
|
840
840
|
var url = '/api/task-plan?agent=' + encodeURIComponent(agent);
|
|
841
841
|
if (session) url += '&session=' + encodeURIComponent(session);
|
|
@@ -906,7 +906,7 @@ async function toggleTaskDone(idx) {
|
|
|
906
906
|
t.done = !t.done;
|
|
907
907
|
}
|
|
908
908
|
try {
|
|
909
|
-
var agent = state.activeAgent || '
|
|
909
|
+
var agent = state.activeAgent || '1';
|
|
910
910
|
var session = state.activeSessionId || '';
|
|
911
911
|
await api('/api/task-plan', {
|
|
912
912
|
method: 'PUT',
|
|
@@ -931,7 +931,7 @@ async function addTaskItem() {
|
|
|
931
931
|
if (!text) return;
|
|
932
932
|
input.value = '';
|
|
933
933
|
try {
|
|
934
|
-
var agent = state.activeAgent || '
|
|
934
|
+
var agent = state.activeAgent || '1';
|
|
935
935
|
var session = state.activeSessionId || '';
|
|
936
936
|
var data = await api('/api/task-plan', {
|
|
937
937
|
method: 'POST',
|
|
@@ -948,7 +948,7 @@ async function addTaskItem() {
|
|
|
948
948
|
async function deleteTaskItem(idx) {
|
|
949
949
|
if (idx < 0 || idx >= state.taskItems.length) return;
|
|
950
950
|
try {
|
|
951
|
-
var agent = state.activeAgent || '
|
|
951
|
+
var agent = state.activeAgent || '1';
|
|
952
952
|
var session = state.activeSessionId || '';
|
|
953
953
|
var url = '/api/task-plan/' + idx + '?agent=' + encodeURIComponent(agent);
|
|
954
954
|
if (session) url += '&session=' + encodeURIComponent(session);
|
|
@@ -1822,6 +1822,28 @@ function findAgentByPath(path) {
|
|
|
1822
1822
|
return state.agentsFlat.find(function(a) { return a.path === path; });
|
|
1823
1823
|
}
|
|
1824
1824
|
|
|
1825
|
+
// [v1.32.4] 查找系统级 Agent 的路径(兼容旧路径 'default'/'配置助手' 和新路径 '1'/'2')
|
|
1826
|
+
function findMasterAgentPath() {
|
|
1827
|
+
// 优先按新路径 "1",兼容旧路径 "default",再按 system 标志+名称兜底
|
|
1828
|
+
var a = state.agentsFlat.find(function(x) { return x.path === '1'; })
|
|
1829
|
+
|| state.agentsFlat.find(function(x) { return x.path === 'default'; })
|
|
1830
|
+
|| state.agentsFlat.find(function(x) { return x.system && x.name === '全权Agent'; })
|
|
1831
|
+
|| state.agentsFlat.find(function(x) { return x.system; });
|
|
1832
|
+
return a ? a.path : 'default';
|
|
1833
|
+
}
|
|
1834
|
+
function findHelperAgentPath() {
|
|
1835
|
+
var a = state.agentsFlat.find(function(x) { return x.path === '2'; })
|
|
1836
|
+
|| state.agentsFlat.find(function(x) { return x.path === '配置助手'; })
|
|
1837
|
+
|| state.agentsFlat.find(function(x) { return x.system && x.name === '配置助手'; })
|
|
1838
|
+
|| null;
|
|
1839
|
+
return a ? a.path : null;
|
|
1840
|
+
}
|
|
1841
|
+
function isSystemAgentPath(path) {
|
|
1842
|
+
var mp = findMasterAgentPath();
|
|
1843
|
+
var hp = findHelperAgentPath();
|
|
1844
|
+
return path === mp || (hp !== null && path === hp);
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1825
1847
|
function getActiveAgentObj() {
|
|
1826
1848
|
return findAgentByPath(state.activeAgent);
|
|
1827
1849
|
}
|
|
@@ -1832,18 +1854,16 @@ async function loadAgents() {
|
|
|
1832
1854
|
state.agentTree = treeData || [];
|
|
1833
1855
|
state.agentsFlat = flattenTree(state.agentTree, 0, null);
|
|
1834
1856
|
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
state.agentTree.unshift(Object.assign({}, defAgent, {children:[]}));
|
|
1839
|
-
}
|
|
1857
|
+
// [v1.32.4] 兼容旧路径:如果后端没有 path='default' 的 agent,不创建合成的 fake agent
|
|
1858
|
+
// 而是依赖 findMasterAgentPath() 来正确解析 master agent 路径
|
|
1859
|
+
var _masterPath = findMasterAgentPath();
|
|
1840
1860
|
|
|
1841
1861
|
for (var i = 0; i < state.agentsFlat.length; i++) {
|
|
1842
1862
|
state.expandedNodes.add(state.agentsFlat[i].path);
|
|
1843
1863
|
}
|
|
1844
1864
|
|
|
1845
1865
|
if (!state.activeAgent || !findAgentByPath(state.activeAgent)) {
|
|
1846
|
-
state.activeAgent =
|
|
1866
|
+
state.activeAgent = _masterPath;
|
|
1847
1867
|
}
|
|
1848
1868
|
|
|
1849
1869
|
renderAgentTree();
|
|
@@ -1852,10 +1872,10 @@ async function loadAgents() {
|
|
|
1852
1872
|
await loadSessions();
|
|
1853
1873
|
loadAllAgentSessions(); // fire-and-forget, will update recent agents
|
|
1854
1874
|
} catch (e) {
|
|
1855
|
-
var defAgent = {path:'
|
|
1875
|
+
var defAgent = {path:'1', name:'全权Agent', depth:0, parent:null, description:'全权Agent - 拥有完整权限的本地助手', avatar_emoji:'🤖', execution_mode:'local', system:true, avatar_color:getAgentColorClass('全权Agent'), session_count:0};
|
|
1856
1876
|
state.agentsFlat = [defAgent];
|
|
1857
1877
|
state.agentTree = [Object.assign({}, defAgent, {children:[]})];
|
|
1858
|
-
state.activeAgent = '
|
|
1878
|
+
state.activeAgent = '1';
|
|
1859
1879
|
renderAgentTree();
|
|
1860
1880
|
updateActiveAgentBadge();
|
|
1861
1881
|
updateExecModeUI();
|
|
@@ -1909,18 +1929,20 @@ function renderRightPanelSections() {
|
|
|
1909
1929
|
function renderMasterAgentCard() {
|
|
1910
1930
|
var el = document.getElementById('rpMasterAgent');
|
|
1911
1931
|
if (!el) return;
|
|
1912
|
-
|
|
1913
|
-
var
|
|
1932
|
+
// [v1.32.4] 使用 findMasterAgentPath() 兼容旧路径 'default' 和新路径 '1'
|
|
1933
|
+
var masterPath = findMasterAgentPath();
|
|
1934
|
+
var agent = findAgentByPath(masterPath);
|
|
1935
|
+
var isActive = state.activeAgent === masterPath;
|
|
1914
1936
|
var name = agent ? agent.name : '全权Agent';
|
|
1915
1937
|
var emoji = agent ? (agent.avatar_emoji || '🛡️') : '🛡️';
|
|
1916
1938
|
var desc = agent ? (agent.description || '拥有完整权限的本地助手') : '拥有完整权限的本地助手';
|
|
1917
1939
|
var color = agent ? (agent.avatar_color || 'linear-gradient(135deg,#6366f1,#8b5cf6)') : 'linear-gradient(135deg,#6366f1,#8b5cf6)';
|
|
1918
1940
|
var bgStyle = color.includes('gradient') ? 'background:' + color : '';
|
|
1919
|
-
var bgClass = color.includes('gradient') ? '' : getAgentColorClass(
|
|
1941
|
+
var bgClass = color.includes('gradient') ? '' : getAgentColorClass(masterPath);
|
|
1920
1942
|
// [v1.20.9] 修复: avatar_image 不为空时显示图片,onerror 回退到 emoji
|
|
1921
1943
|
var avatarContent = (agent && agent.avatar_image) ? '<img src="' + escapeHtml(agent.avatar_image) + '" style="width:100%;height:100%;object-fit:cover;border-radius:12px" onerror="this.outerHTML=\'' + escapeHtml(emoji) + '\'">' : emoji;
|
|
1922
1944
|
|
|
1923
|
-
el.innerHTML = '<div class="rp-master-card ' + (isActive ? 'active' : '') + '" onclick="selectAgent(\'
|
|
1945
|
+
el.innerHTML = '<div class="rp-master-card ' + (isActive ? 'active' : '') + '" onclick="selectAgent(\'' + escapeHtml(masterPath) + '\')">'
|
|
1924
1946
|
+ '<div class="rp-master-avatar ' + bgClass + '" style="' + bgStyle + '">' + avatarContent + '</div>'
|
|
1925
1947
|
+ '<div class="rp-master-info">'
|
|
1926
1948
|
+ '<div class="rp-master-name">' + escapeHtml(name) + '</div>'
|
|
@@ -1931,8 +1953,11 @@ function renderMasterAgentCard() {
|
|
|
1931
1953
|
function renderHelperAgentCard() {
|
|
1932
1954
|
var el = document.getElementById('rpHelperAgent');
|
|
1933
1955
|
if (!el) return;
|
|
1934
|
-
|
|
1935
|
-
var
|
|
1956
|
+
// [v1.32.4] 使用 findHelperAgentPath() 兼容旧路径 '配置助手' 和新路径 '2'
|
|
1957
|
+
var helperPath = findHelperAgentPath();
|
|
1958
|
+
if (!helperPath) { el.innerHTML = ''; return; } // 无配置助手则不渲染
|
|
1959
|
+
var agent = findAgentByPath(helperPath);
|
|
1960
|
+
var isActive = state.activeAgent === helperPath;
|
|
1936
1961
|
var name = agent ? agent.name : '配置助手';
|
|
1937
1962
|
var emoji = agent ? (agent.avatar_emoji || '🛡️') : '🛡️';
|
|
1938
1963
|
var desc = agent ? (agent.description || '智能配置助手') : '智能配置助手';
|
|
@@ -1945,7 +1970,7 @@ function renderHelperAgentCard() {
|
|
|
1945
1970
|
avatarContent = emoji;
|
|
1946
1971
|
}
|
|
1947
1972
|
|
|
1948
|
-
el.innerHTML = '<div class="rp-helper-card ' + (isActive ? 'active' : '') + '" onclick="selectAgent(\'
|
|
1973
|
+
el.innerHTML = '<div class="rp-helper-card ' + (isActive ? 'active' : '') + '" onclick="selectAgent(\'' + escapeHtml(helperPath) + '\')">'
|
|
1949
1974
|
+ '<div class="rp-helper-avatar" style="' + (avatarImage ? 'background:transparent' : '') + '">' + avatarContent + '</div>'
|
|
1950
1975
|
+ '<div class="rp-master-info">'
|
|
1951
1976
|
+ '<div class="rp-master-name" style="font-size:13px">' + escapeHtml(name) + '</div>'
|
|
@@ -1962,7 +1987,7 @@ async function renderRecentAgents() {
|
|
|
1962
1987
|
var recentAgents = [];
|
|
1963
1988
|
|
|
1964
1989
|
for (var key in state.agentSessions) {
|
|
1965
|
-
if (key
|
|
1990
|
+
if (isSystemAgentPath(key)) continue;
|
|
1966
1991
|
var sessions = state.agentSessions[key];
|
|
1967
1992
|
if (sessions && sessions.length > 0) {
|
|
1968
1993
|
var agent = findAgentByPath(key);
|
|
@@ -2168,10 +2193,10 @@ function quickChatAgent(agentPath) {
|
|
|
2168
2193
|
}
|
|
2169
2194
|
|
|
2170
2195
|
async function selectAgent(agentPath) {
|
|
2171
|
-
// 安全校验:agent 必须在已加载的列表中存在,否则回退
|
|
2196
|
+
// 安全校验:agent 必须在已加载的列表中存在,否则回退 master agent
|
|
2172
2197
|
if (!findAgentByPath(agentPath)) {
|
|
2173
|
-
console.warn('selectAgent: agent not found, fallback to
|
|
2174
|
-
agentPath =
|
|
2198
|
+
console.warn('selectAgent: agent not found, fallback to master:', agentPath);
|
|
2199
|
+
agentPath = findMasterAgentPath();
|
|
2175
2200
|
}
|
|
2176
2201
|
// 从群聊视图切换回个人聊天视图
|
|
2177
2202
|
if (currentView === 'group') {
|
|
@@ -2575,7 +2600,7 @@ async function saveAgentModal(editPath, parentPath) {
|
|
|
2575
2600
|
}
|
|
2576
2601
|
|
|
2577
2602
|
async function deleteAgent(agentPath) {
|
|
2578
|
-
if (agentPath ===
|
|
2603
|
+
if (agentPath === findMasterAgentPath()) { toast('不能删除默认 Agent', 'error'); return; }
|
|
2579
2604
|
var msg = '确定删除 Agent "' + agentPath + '" 吗?\n\n删除后将同时清理:\n 📁 工作目录及所有文件\n 💬 该 Agent 的所有会话历史\n 🧠 相关记忆数据\n 🔗 所有子 Agent\n\n⚠️ 此操作不可撤销!';
|
|
2580
2605
|
if (!confirm(msg)) return;
|
|
2581
2606
|
try {
|
|
@@ -2583,7 +2608,7 @@ async function deleteAgent(agentPath) {
|
|
|
2583
2608
|
toast('Agent 已删除', 'success');
|
|
2584
2609
|
closeAgentModal();
|
|
2585
2610
|
if (state.activeAgent === agentPath || state.activeAgent.startsWith(agentPath + '/')) {
|
|
2586
|
-
state.activeAgent =
|
|
2611
|
+
state.activeAgent = findMasterAgentPath();
|
|
2587
2612
|
}
|
|
2588
2613
|
await loadAgents();
|
|
2589
2614
|
} catch (e) {
|
|
@@ -5120,7 +5145,8 @@ function skipSetup() {
|
|
|
5120
5145
|
StatePersistence.markSetupDone();
|
|
5121
5146
|
try { api('/api/setup/complete', { method: 'POST' }); } catch(e) {}
|
|
5122
5147
|
// 自动切换到配置助手 Agent
|
|
5123
|
-
|
|
5148
|
+
var _helperPath = findHelperAgentPath();
|
|
5149
|
+
if (_helperPath) selectAgent(_helperPath);
|
|
5124
5150
|
}
|
|
5125
5151
|
|
|
5126
5152
|
function renderSetupDots() {
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
// ── 输入框草稿持久化(按 agent + session 维度保存,刷新页面不丢失) ──
|
|
8
8
|
var _draftTimer = null;
|
|
9
9
|
function _draftKey() {
|
|
10
|
-
return 'myagent_draft_' + (state.activeAgent || '
|
|
10
|
+
return 'myagent_draft_' + (state.activeAgent || '1') + '_' + (state.activeSessionId || '__new__');
|
|
11
11
|
}
|
|
12
12
|
function saveDraft() {
|
|
13
13
|
if (_draftTimer) clearTimeout(_draftTimer);
|
|
@@ -128,7 +128,7 @@ async function confirmLargeTextAction() {
|
|
|
128
128
|
var text = _pendingLargeText;
|
|
129
129
|
if (text === null || idx === null) return;
|
|
130
130
|
|
|
131
|
-
var agentPath = state.activeAgent || '
|
|
131
|
+
var agentPath = state.activeAgent || '1';
|
|
132
132
|
|
|
133
133
|
if (idx === 0) {
|
|
134
134
|
// 导入知识库
|
package/web/ui/chat/groupchat.js
CHANGED
|
@@ -346,7 +346,7 @@ function exitGroupChat() {
|
|
|
346
346
|
groupMessages = [];
|
|
347
347
|
// [v1.23.81] 清除群聊 ?g= session URL,恢复普通聊天 URL
|
|
348
348
|
var cleanUrl = '/ui/chat/chat_container.html';
|
|
349
|
-
if (typeof state !== 'undefined' && state.activeAgent && state.activeAgent !== '
|
|
349
|
+
if (typeof state !== 'undefined' && state.activeAgent && state.activeAgent !== (typeof findMasterAgentPath === 'function' ? findMasterAgentPath() : '1')) {
|
|
350
350
|
cleanUrl += '?a=' + encodeURIComponent(state.activeAgent);
|
|
351
351
|
}
|
|
352
352
|
if (typeof state !== 'undefined' && state.activeSessionId && state.activeSessionId !== '__new__') {
|
|
@@ -456,7 +456,7 @@ function startPrivateChatFromGroup(agentPath) {
|
|
|
456
456
|
StatePersistence.remove('currentGroupId');
|
|
457
457
|
// 清除群聊 URL 中的 ?g= 参数
|
|
458
458
|
var cleanUrl = '/ui/chat/chat_container.html';
|
|
459
|
-
if (typeof state !== 'undefined' && agentPath && agentPath !== '
|
|
459
|
+
if (typeof state !== 'undefined' && agentPath && agentPath !== (typeof findMasterAgentPath === 'function' ? findMasterAgentPath() : '1')) {
|
|
460
460
|
cleanUrl += '?a=' + encodeURIComponent(agentPath);
|
|
461
461
|
}
|
|
462
462
|
window.history.replaceState({}, '', cleanUrl);
|