myagent-ai 1.27.0 → 1.27.2
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 +110 -70
- package/web/ui/admin/admin-agents.js +2 -1
- package/web/ui/admin/admin-core.js +3 -0
- package/web/ui/admin/admin-sessions.js +19 -19
- package/web/ui/chat/chat_main.js +80 -18
- package/web/ui/chat/flow_engine.js +12 -2
- package/worklog.md +0 -23
- package/.phoneide_tmp.py +0 -8
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -3505,6 +3505,9 @@ window.addEventListener('beforeunload', function() {{
|
|
|
3505
3505
|
session_counts[ap] = session_counts.get(ap, 0) + r["cnt"]
|
|
3506
3506
|
for a in agents_flat:
|
|
3507
3507
|
a["session_count"] = session_counts.get(a["path"], 0)
|
|
3508
|
+
# [v1.27.2] 附加数字 agent_id 到每个 agent(供 URL 参数使用)
|
|
3509
|
+
for a in agents_flat:
|
|
3510
|
+
a["aid"] = self.core.memory.get_agent_id(a["path"])
|
|
3508
3511
|
# 系统 agent 排在最前
|
|
3509
3512
|
agents_flat.sort(key=lambda a: (0 if a.get("system") else 1, a.get("path", "")))
|
|
3510
3513
|
tree = self._build_agent_tree(agents_flat)
|
|
@@ -4299,33 +4302,60 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4299
4302
|
async def handle_list_sessions(self, request):
|
|
4300
4303
|
if not self.core.memory: return web.json_response([])
|
|
4301
4304
|
agent = request.query.get("agent", "")
|
|
4305
|
+
conn = self.core.memory._get_conn()
|
|
4306
|
+
|
|
4307
|
+
# [v1.27.2] 构建 agent_id → agent_name 的映射(数字 ID → 路径名)
|
|
4308
|
+
_agent_id_map = {} # {int_id: str_name}
|
|
4309
|
+
try:
|
|
4310
|
+
for ar in conn.execute("SELECT id, name FROM agents").fetchall():
|
|
4311
|
+
_agent_id_map[int(ar["id"])] = ar["name"]
|
|
4312
|
+
except Exception:
|
|
4313
|
+
pass
|
|
4314
|
+
|
|
4315
|
+
_hidden = '(' + ','.join(["'llm_output'", "'llm_input'", "'tool_result_raw'", "'conversation_insight'"]) + ')'
|
|
4302
4316
|
# 只统计用户可见的消息数(排除 llm_output 和 conversation_insight)
|
|
4303
4317
|
if agent:
|
|
4304
|
-
# [v1.
|
|
4318
|
+
# [v1.27.2] 先将 agent 路径转为数字 ID,用数字 ID 精确过滤
|
|
4319
|
+
target_aid = self.core.memory.get_agent_id(agent) if agent else 0
|
|
4305
4320
|
if agent == "default":
|
|
4306
|
-
# default agent: agent_id=
|
|
4307
|
-
rows =
|
|
4308
|
-
"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last
|
|
4321
|
+
# default agent: agent_id=0(未分配)或 agent_id=1(default 的数字 ID)或 旧格式
|
|
4322
|
+
rows = conn.execute(
|
|
4323
|
+
f"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last,
|
|
4324
|
+
MAX(CASE WHEN agent_id > 1 THEN agent_id ELSE NULL END) as raw_agent_id FROM memories
|
|
4309
4325
|
WHERE category = 'session' AND role != ''
|
|
4310
|
-
AND key NOT IN
|
|
4311
|
-
AND (agent_id =
|
|
4312
|
-
GROUP BY session_id ORDER BY last DESC LIMIT 100"""
|
|
4326
|
+
AND key NOT IN {_hidden}
|
|
4327
|
+
AND (agent_id = 0 OR agent_id = ? OR session_id = 'web_default' OR session_id LIKE 'sess_%')
|
|
4328
|
+
GROUP BY session_id ORDER BY last DESC LIMIT 100""",
|
|
4329
|
+
(target_aid,)).fetchall()
|
|
4313
4330
|
else:
|
|
4314
|
-
# 其他 agent: agent_id
|
|
4315
|
-
rows =
|
|
4316
|
-
"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last
|
|
4331
|
+
# 其他 agent: agent_id=数字ID 或 session_id 前缀匹配(旧数据)
|
|
4332
|
+
rows = conn.execute(
|
|
4333
|
+
f"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last,
|
|
4334
|
+
MAX(CASE WHEN agent_id != ? THEN agent_id ELSE NULL END) as raw_agent_id FROM memories
|
|
4317
4335
|
WHERE category = 'session' AND role != ''
|
|
4318
|
-
AND key NOT IN
|
|
4336
|
+
AND key NOT IN {_hidden}
|
|
4319
4337
|
AND (agent_id = ? OR session_id LIKE ?)
|
|
4320
4338
|
GROUP BY session_id ORDER BY last DESC LIMIT 100""",
|
|
4321
|
-
(
|
|
4339
|
+
(target_aid, target_aid, f"{agent}_%")).fetchall()
|
|
4322
4340
|
else:
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
"
|
|
4326
|
-
"
|
|
4327
|
-
"
|
|
4328
|
-
|
|
4341
|
+
# [v1.27.2] 无 agent 过滤时也返回 agent_id,供后台管理页面使用
|
|
4342
|
+
rows = conn.execute(
|
|
4343
|
+
f"SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last, "
|
|
4344
|
+
f"MAX(CASE WHEN agent_id > 0 THEN agent_id ELSE NULL END) as raw_agent_id FROM memories "
|
|
4345
|
+
f"WHERE category = 'session' AND role != '' "
|
|
4346
|
+
f"AND key NOT IN {_hidden} "
|
|
4347
|
+
f"GROUP BY session_id ORDER BY last DESC LIMIT 100").fetchall()
|
|
4348
|
+
# [v1.27.2] 将数字 agent_id 转为 agent 名称
|
|
4349
|
+
sessions = []
|
|
4350
|
+
for r in rows:
|
|
4351
|
+
raw_aid = r["raw_agent_id"]
|
|
4352
|
+
agent_name = ""
|
|
4353
|
+
if raw_aid is not None:
|
|
4354
|
+
agent_name = _agent_id_map.get(int(raw_aid), "")
|
|
4355
|
+
sessions.append({"id": r["session_id"], "messages": r["cnt"], "last": r["last"],
|
|
4356
|
+
"agent_id": agent_name, # 返回 agent 名称(非数字 ID)
|
|
4357
|
+
"agent_db_id": (int(raw_aid) if raw_aid is not None else 0), # 数字 ID 供 URL 使用
|
|
4358
|
+
"display_name": "", "preview": ""})
|
|
4329
4359
|
# 批量获取自定义会话名称
|
|
4330
4360
|
sids = [s["id"] for s in sessions]
|
|
4331
4361
|
name_map = self.core.memory.list_session_names(sids) if sids else {}
|
|
@@ -4354,22 +4384,24 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4354
4384
|
name = request.match_info["name"]
|
|
4355
4385
|
if not self.core.memory:
|
|
4356
4386
|
return web.json_response({"agent": name, "sessions": []})
|
|
4357
|
-
# [v1.
|
|
4387
|
+
# [v1.27.2] 使用数字 agent_id 精确过滤,同时兼容旧数据
|
|
4388
|
+
target_aid = self.core.memory.get_agent_id(name)
|
|
4358
4389
|
if name == "default":
|
|
4359
|
-
# default agent: agent_id=
|
|
4390
|
+
# default agent: agent_id=0 或 agent_id=1 或 旧格式
|
|
4360
4391
|
rows = self.core.memory._get_conn().execute(
|
|
4361
4392
|
"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories
|
|
4362
4393
|
WHERE category = 'session'
|
|
4363
|
-
AND (agent_id =
|
|
4364
|
-
GROUP BY session_id ORDER BY last DESC LIMIT 100"""
|
|
4394
|
+
AND (agent_id = 0 OR agent_id = ? OR session_id = 'web_default' OR session_id LIKE 'sess_%')
|
|
4395
|
+
GROUP BY session_id ORDER BY last DESC LIMIT 100""",
|
|
4396
|
+
(target_aid,)).fetchall()
|
|
4365
4397
|
else:
|
|
4366
|
-
# 其他 agent: agent_id
|
|
4398
|
+
# 其他 agent: agent_id=数字ID 或 session_id 前缀匹配(旧数据)
|
|
4367
4399
|
rows = self.core.memory._get_conn().execute(
|
|
4368
4400
|
"""SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories
|
|
4369
4401
|
WHERE category = 'session'
|
|
4370
4402
|
AND (agent_id = ? OR session_id LIKE ?)
|
|
4371
4403
|
GROUP BY session_id ORDER BY last DESC LIMIT 100""",
|
|
4372
|
-
(
|
|
4404
|
+
(target_aid, f"{name}_%")).fetchall()
|
|
4373
4405
|
sessions = [{"id": r["session_id"], "messages": r["cnt"], "last": r["last"]} for r in rows]
|
|
4374
4406
|
# 批量获取自定义会话名称
|
|
4375
4407
|
sids = [s["id"] for s in sessions]
|
|
@@ -4392,30 +4424,34 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4392
4424
|
async def handle_get_messages(self, request):
|
|
4393
4425
|
sid = request.match_info["sid"]
|
|
4394
4426
|
if not self.core.memory: return web.json_response([])
|
|
4395
|
-
|
|
4396
|
-
|
|
4397
|
-
|
|
4398
|
-
|
|
4399
|
-
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4427
|
+
try:
|
|
4428
|
+
limit = min(int(request.query.get("limit", 500)), 500)
|
|
4429
|
+
offset = int(request.query.get("offset", 0))
|
|
4430
|
+
entries = self.core.memory.get_conversation(sid, limit=limit + offset)
|
|
4431
|
+
entries = entries[offset:]
|
|
4432
|
+
# Filter out internal entries (LLM raw output only)
|
|
4433
|
+
entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
|
|
4434
|
+
result = []
|
|
4435
|
+
for e in entries:
|
|
4436
|
+
msg = {"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""}
|
|
4437
|
+
# [v1.16.17] 附加附件元数据
|
|
4438
|
+
meta = e.metadata or {}
|
|
4439
|
+
if meta.get("images"):
|
|
4440
|
+
msg["images"] = meta["images"]
|
|
4441
|
+
# [v1.23.19] 分离 _files 和 _media(前端用 msg._files 和 msg._media)
|
|
4442
|
+
_raw_files = meta.get("files") or []
|
|
4443
|
+
if _raw_files:
|
|
4444
|
+
_file_items = [f for f in _raw_files if f.get("_type") != "media"]
|
|
4445
|
+
_media_items = [f for f in _raw_files if f.get("_type") == "media"]
|
|
4446
|
+
if _file_items:
|
|
4447
|
+
msg["_files"] = _file_items
|
|
4448
|
+
if _media_items:
|
|
4449
|
+
msg["_media"] = _media_items
|
|
4417
4450
|
result.append(msg)
|
|
4418
4451
|
return web.json_response(result)
|
|
4452
|
+
except Exception as ex:
|
|
4453
|
+
logger.error(f"加载会话消息失败 sid={sid}: {ex}")
|
|
4454
|
+
return web.json_response({"error": f"加载消息失败: {str(ex)[:200]}"}, status=500)
|
|
4419
4455
|
|
|
4420
4456
|
async def handle_get_raw_messages(self, request):
|
|
4421
4457
|
"""GET /api/sessions/{sid}/raw - 获取会话全部原始消息(含 llm_output 等)"""
|
|
@@ -4453,30 +4489,34 @@ window.addEventListener('beforeunload', function() {{
|
|
|
4453
4489
|
if not sid:
|
|
4454
4490
|
return web.json_response([])
|
|
4455
4491
|
if not self.core.memory: return web.json_response([])
|
|
4456
|
-
|
|
4457
|
-
|
|
4458
|
-
|
|
4459
|
-
|
|
4460
|
-
|
|
4461
|
-
|
|
4462
|
-
|
|
4463
|
-
|
|
4464
|
-
|
|
4465
|
-
|
|
4466
|
-
|
|
4467
|
-
|
|
4468
|
-
|
|
4469
|
-
|
|
4470
|
-
|
|
4471
|
-
|
|
4472
|
-
|
|
4473
|
-
|
|
4474
|
-
|
|
4475
|
-
|
|
4476
|
-
|
|
4477
|
-
|
|
4478
|
-
|
|
4479
|
-
|
|
4492
|
+
try:
|
|
4493
|
+
limit = min(int(request.query.get("limit", 500)), 500)
|
|
4494
|
+
offset = int(request.query.get("offset", 0))
|
|
4495
|
+
entries = self.core.memory.get_conversation(sid, limit=limit + offset)
|
|
4496
|
+
entries = entries[offset:]
|
|
4497
|
+
# Filter out internal entries (LLM raw output only)
|
|
4498
|
+
entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
|
|
4499
|
+
result = []
|
|
4500
|
+
for e in entries:
|
|
4501
|
+
msg = {"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""}
|
|
4502
|
+
# [v1.16.17] 附加附件元数据
|
|
4503
|
+
meta = e.metadata or {}
|
|
4504
|
+
if meta.get("images"):
|
|
4505
|
+
msg["images"] = meta["images"]
|
|
4506
|
+
# [v1.23.19] 分离 _files 和 _media(前端用 msg._files 和 msg._media)
|
|
4507
|
+
_raw_files = meta.get("files") or []
|
|
4508
|
+
if _raw_files:
|
|
4509
|
+
_file_items = [f for f in _raw_files if f.get("_type") != "media"]
|
|
4510
|
+
_media_items = [f for f in _raw_files if f.get("_type") == "media"]
|
|
4511
|
+
if _file_items:
|
|
4512
|
+
msg["_files"] = _file_items
|
|
4513
|
+
if _media_items:
|
|
4514
|
+
msg["_media"] = _media_items
|
|
4515
|
+
result.append(msg)
|
|
4516
|
+
return web.json_response(result)
|
|
4517
|
+
except Exception as ex:
|
|
4518
|
+
logger.error(f"加载会话消息失败 sid={sid}: {ex}")
|
|
4519
|
+
return web.json_response({"error": f"加载消息失败: {str(ex)[:200]}"}, status=500)
|
|
4480
4520
|
|
|
4481
4521
|
def _cleanup_session_state(self, sid: str):
|
|
4482
4522
|
"""删除会话时清理所有关联的后端状态"""
|
|
@@ -557,8 +557,9 @@ function confirmDeleteAgent(path,name){
|
|
|
557
557
|
}
|
|
558
558
|
|
|
559
559
|
// 直接以执行模式打开与指定 Agent 的对话
|
|
560
|
+
// [v1.27.2] 使用 a 参数(UrlCodec 编码),不再明文暴露 agent 路径
|
|
560
561
|
function chatWithAgent(path){
|
|
561
|
-
window.location.href='/ui/chat/chat_container.html?
|
|
562
|
+
window.location.href='/ui/chat/chat_container.html?mode=exec&a='+UrlCodec.encode(path);
|
|
562
563
|
}
|
|
563
564
|
|
|
564
565
|
if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
|
|
@@ -7,6 +7,9 @@ let allAgentsCache=[];
|
|
|
7
7
|
let allModelsCache=[];
|
|
8
8
|
let allDeptsCache=[];
|
|
9
9
|
|
|
10
|
+
// [v1.27.2] UrlCodec: 与 chat 页面共享的 URL 编解码(避免 agent/session 明文暴露)
|
|
11
|
+
var UrlCodec=(function(){var _p='x';function _encode(str){if(!str)return'';try{var bytes=new TextEncoder().encode(str);var bin='';for(var i=0;i<bytes.length;i++)bin+=String.fromCharCode(bytes[i]);return _p+btoa(bin).replace(/=/g,'').replace(/\+/g,'-').replace(/\//g,'_')}catch(e){return str}}function _decode(encoded){if(!encoded||encoded[0]!==_p)return encoded;try{var b64=encoded.substring(1).replace(/-/g,'+').replace(/_/g,'/');while(b64.length%4)b64+='=';var bin=atob(b64);var bytes=new Uint8Array(bin.length);for(var i=0;i<bin.length;i++)bytes[i]=bin.charCodeAt(i);return new TextDecoder().decode(bytes)}catch(e){return encoded}}return{encode:_encode,decode:_decode}})();
|
|
12
|
+
|
|
10
13
|
function esc(s){if(!s)return'';return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
|
11
14
|
|
|
12
15
|
async function api(url,opts={}){
|
|
@@ -7,25 +7,24 @@ async function renderSessions(){
|
|
|
7
7
|
}
|
|
8
8
|
let html='<div class="table-wrap"><table><tr><th>会话</th><th>Agent</th><th>消息数</th><th>最后活动</th><th>操作</th></tr>';
|
|
9
9
|
for(const s of (ss||[])){
|
|
10
|
-
//
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}else{
|
|
22
|
-
agentName='default';
|
|
10
|
+
// [v1.27.2] 优先使用后端返回的 agent_id(现在是 agent 名称),回退从前缀猜测
|
|
11
|
+
let agentName=s.agent_id||'';
|
|
12
|
+
if(!agentName){
|
|
13
|
+
// 回退:从 session_id 前缀提取(兼容旧格式)
|
|
14
|
+
if((s.id||'').indexOf('_web_')>=0){
|
|
15
|
+
agentName=(s.id||'').split('_web_')[0]||'default';
|
|
16
|
+
}else if((s.id||'').indexOf('_cli_')>=0){
|
|
17
|
+
agentName=(s.id||'').split('_cli_')[0]||'default';
|
|
18
|
+
}else{
|
|
19
|
+
agentName='default';
|
|
20
|
+
}
|
|
23
21
|
}
|
|
24
22
|
const displayName=s.display_name||s.id;
|
|
23
|
+
const dbId=s.agent_db_id||1;
|
|
25
24
|
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>
|
|
26
|
-
<td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}'
|
|
27
|
-
<button class="btn btn-sm btn-ghost" onclick="viewSession('${s.id}')">查看</button>
|
|
28
|
-
<button class="btn btn-sm btn-danger" onclick="clearSession('${s.id}')">清除</button></td></tr>`;}
|
|
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
28
|
html+='</table></div>';
|
|
30
29
|
$('content').innerHTML=html;
|
|
31
30
|
}
|
|
@@ -88,7 +87,7 @@ async function _loadSessionMessages(){
|
|
|
88
87
|
if(hasMore)html+=`<button class="btn btn-ghost mt-8" onclick="window._viewSessionOffset=${offset+100};_loadSessionMessages()">加载更多...</button>`;
|
|
89
88
|
html+='<div class="flex gap-8 mt-8"><button class="btn btn-ghost" onclick="goBack()">返回</button>';
|
|
90
89
|
html+=`<button class="btn" style="background:var(--accent);color:#fff" onclick="viewSessionRaw('${escHtml(sid)}')">Raw 原始消息</button>`;
|
|
91
|
-
html+=`<button class="btn btn-primary" onclick="enterSession('${escHtml(sid)}',
|
|
90
|
+
html+=`<button class="btn btn-primary" onclick="enterSession('${escHtml(sid)}',1)">在聊天中查看完整记录</button></div>`;
|
|
92
91
|
$('content').innerHTML=html;
|
|
93
92
|
}
|
|
94
93
|
// ========== Raw 原始消息查看 ==========
|
|
@@ -235,8 +234,9 @@ function _buildTimeNav(msgs){
|
|
|
235
234
|
}
|
|
236
235
|
async function clearSession(sid){await api(`/api/sessions/${encodeURIComponent(sid)}`,{method:'DELETE'});renderSessions()}
|
|
237
236
|
// 切入会话: 打开聊天页面并自动加载指定会话
|
|
238
|
-
|
|
239
|
-
|
|
237
|
+
// [v1.27.2] 使用 aid(数字 agent ID)+ s(编码 session ID),不再暴露明文 agent 名字
|
|
238
|
+
function enterSession(sid,agentDbId){
|
|
239
|
+
window.location.href='/ui/chat/chat_container.html?aid='+encodeURIComponent(agentDbId||1)+'&mode=exec&s='+UrlCodec.encode(sid);
|
|
240
240
|
}
|
|
241
241
|
|
|
242
242
|
if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
|
package/web/ui/chat/chat_main.js
CHANGED
|
@@ -136,10 +136,12 @@ function popoutChat() {
|
|
|
136
136
|
if (isMobile()) return;
|
|
137
137
|
// Build URL with current agent + session + mode, plus popout flag
|
|
138
138
|
// Note: chat_container.html is always at /ui/chat/chat_container.html
|
|
139
|
+
// [v1.27.2] 使用 aid + s,不再暴露明文 agent 名字
|
|
139
140
|
const baseUrl = window.location.origin + '/ui/chat/chat_container.html';
|
|
140
141
|
const params = new URLSearchParams();
|
|
141
|
-
|
|
142
|
-
if (
|
|
142
|
+
var _popAgent = findAgentByPath(state.activeAgent);
|
|
143
|
+
if (_popAgent && _popAgent.aid) params.set('aid', _popAgent.aid);
|
|
144
|
+
if (state.activeSessionId && state.activeSessionId !== '__new__') params.set('s', UrlCodec.encode(state.activeSessionId));
|
|
143
145
|
if (state.chatMode) params.set('mode', state.chatMode);
|
|
144
146
|
params.set('popout', '1');
|
|
145
147
|
const popoutUrl = baseUrl + '?' + params.toString();
|
|
@@ -373,9 +375,12 @@ async function initChat() {
|
|
|
373
375
|
// [v1.16.12] 初始化附件上传 UI
|
|
374
376
|
initAttachmentUI();
|
|
375
377
|
|
|
376
|
-
// URL 参数处理: ?
|
|
378
|
+
// URL 参数处理: ?aid=数字ID&mode=exec&s=编码session&g=群聊ID&popout=1
|
|
379
|
+
// [v1.27.2] 使用 aid(数字 agent ID)代替明文 agent 名字
|
|
377
380
|
const urlParams = new URLSearchParams(window.location.search);
|
|
378
|
-
const
|
|
381
|
+
const urlAid = parseInt(urlParams.get('aid') || '', 10) || 0;
|
|
382
|
+
// 兼容旧格式 ?a= 编码 或 ?agent= 明文
|
|
383
|
+
const urlAgentLegacy = UrlCodec.decode(urlParams.get('a') || '') || UrlCodec.decode(urlParams.get('agent') || '');
|
|
379
384
|
const urlMode = urlParams.get('mode');
|
|
380
385
|
const urlSession = UrlCodec.decode(urlParams.get('s') || '') || UrlCodec.decode(urlParams.get('session') || '');
|
|
381
386
|
const isPopout = urlParams.get('popout') === '1';
|
|
@@ -463,25 +468,47 @@ async function initChat() {
|
|
|
463
468
|
await loadAgents();
|
|
464
469
|
|
|
465
470
|
// loadAgents 完成后,agent 列表和 sessions 已加载完毕
|
|
466
|
-
//
|
|
471
|
+
// [v1.27.2] 构建 aid → path 映射表
|
|
472
|
+
var _aidToPath = {};
|
|
473
|
+
state.agentsFlat.forEach(function(a) {
|
|
474
|
+
if (a.aid) _aidToPath[a.aid] = a.path;
|
|
475
|
+
});
|
|
476
|
+
// 从 URL 解析 agent: 优先 aid(数字),兼容 a/agent(编码/明文)
|
|
477
|
+
var urlAgent = '';
|
|
478
|
+
if (urlAid && _aidToPath[urlAid]) {
|
|
479
|
+
urlAgent = _aidToPath[urlAid];
|
|
480
|
+
} else if (urlAgentLegacy) {
|
|
481
|
+
urlAgent = urlAgentLegacy;
|
|
482
|
+
}
|
|
483
|
+
// 清理 URL 中的旧格式参数(agent/a/session),统一使用 aid/s
|
|
484
|
+
try {
|
|
485
|
+
var _cleanUrl = new URL(window.location.href);
|
|
486
|
+
_cleanUrl.searchParams.delete('agent');
|
|
487
|
+
_cleanUrl.searchParams.delete('a');
|
|
488
|
+
_cleanUrl.searchParams.delete('session');
|
|
489
|
+
if (urlAgent && !urlAid) {
|
|
490
|
+
// 如果没有 aid 但有旧格式 agent,添加 aid
|
|
491
|
+
var _agentObj = findAgentByPath(urlAgent);
|
|
492
|
+
if (_agentObj && _agentObj.aid) _cleanUrl.searchParams.set('aid', _agentObj.aid);
|
|
493
|
+
}
|
|
494
|
+
window.history.replaceState({}, '', _cleanUrl.toString());
|
|
495
|
+
} catch (_) {}
|
|
496
|
+
|
|
467
497
|
if (isPopout) {
|
|
468
498
|
const agentObj = findAgentByPath(urlAgent);
|
|
469
499
|
document.title = (agentObj ? agentObj.name : urlAgent || 'MyAgent') + ' - MyAgent';
|
|
470
500
|
}
|
|
471
501
|
|
|
472
502
|
// [v1.23.81] 群聊恢复逻辑:从 URL 的 ?g= 纯数字 session ID 恢复(优先级最高)
|
|
473
|
-
|
|
474
|
-
state._pendingGroupRestore = null; // 不再使用 localStorage 中的群聊状态
|
|
503
|
+
state._pendingGroupRestore = null;
|
|
475
504
|
|
|
476
505
|
async function _restoreGroupBySession(sessionId) {
|
|
477
|
-
// 通过 API 将纯数字 session ID 解析为 group_id
|
|
478
506
|
try {
|
|
479
507
|
var resolveData = await api('/api/group-session/' + encodeURIComponent(sessionId));
|
|
480
508
|
if (resolveData && resolveData.group_id) {
|
|
481
509
|
if (typeof groups !== 'undefined' && groups.length > 0) {
|
|
482
510
|
selectGroup(resolveData.group_id);
|
|
483
511
|
} else {
|
|
484
|
-
// groups 尚未加载,等待后重试
|
|
485
512
|
setTimeout(function() { _restoreGroupBySession(sessionId); }, 200);
|
|
486
513
|
}
|
|
487
514
|
} else {
|
|
@@ -494,14 +521,12 @@ async function initChat() {
|
|
|
494
521
|
|
|
495
522
|
// 如果 URL 中有 ?g= 纯数字 session ID,优先恢复群聊
|
|
496
523
|
if (_effectiveGroupSession && /^[0-9]+$/.test(_effectiveGroupSession)) {
|
|
497
|
-
// 群聊模式:如果有 from_agent 参数,更新 state.activeAgent 以便后续群聊私聊时使用
|
|
498
524
|
if (urlAgent && urlAgent !== state.activeAgent) {
|
|
499
525
|
state.activeAgent = urlAgent;
|
|
500
526
|
StatePersistence.save('activeAgent', urlAgent);
|
|
501
527
|
}
|
|
502
528
|
_restoreGroupBySession(_effectiveGroupSession);
|
|
503
529
|
} else if (urlAgent) {
|
|
504
|
-
// URL 指定了 agent,校验后切换
|
|
505
530
|
var resolved = urlAgent;
|
|
506
531
|
if (!findAgentByPath(resolved)) {
|
|
507
532
|
console.warn('URL agent not found, fallback to default:', resolved);
|
|
@@ -2181,8 +2206,8 @@ async function selectAgent(agentPath) {
|
|
|
2181
2206
|
try { state.abortController.abort(); } catch (_) {}
|
|
2182
2207
|
state.abortController = null;
|
|
2183
2208
|
}
|
|
2184
|
-
hideTypingIndicator();
|
|
2185
|
-
stopExecTimerPolling();
|
|
2209
|
+
if (typeof hideTypingIndicator === 'function') hideTypingIndicator();
|
|
2210
|
+
if (typeof stopExecTimerPolling === 'function') stopExecTimerPolling();
|
|
2186
2211
|
// 立即清空聊天区域DOM,防止切换agent时旧消息残留
|
|
2187
2212
|
var _mi = document.getElementById('messagesInner');
|
|
2188
2213
|
if (_mi) _mi.innerHTML = '';
|
|
@@ -2798,11 +2823,17 @@ function newChat() {
|
|
|
2798
2823
|
state.activeSessionId = '__new__';
|
|
2799
2824
|
state._selectedSessionLabel = null; // [v1.18.9] 清除选中的会话名称
|
|
2800
2825
|
// ── 更新 URL(新对话移除 session 参数) ──
|
|
2826
|
+
// [v1.27.2] 使用 aid 代替 a
|
|
2801
2827
|
try {
|
|
2802
2828
|
const url = new URL(window.location.href);
|
|
2803
2829
|
url.searchParams.delete('s');
|
|
2804
2830
|
url.searchParams.delete('session');
|
|
2805
|
-
url.searchParams.
|
|
2831
|
+
url.searchParams.delete('a');
|
|
2832
|
+
url.searchParams.delete('agent');
|
|
2833
|
+
var _ncAgentObj = findAgentByPath(state.activeAgent);
|
|
2834
|
+
if (_ncAgentObj && _ncAgentObj.aid) {
|
|
2835
|
+
url.searchParams.set('aid', _ncAgentObj.aid);
|
|
2836
|
+
}
|
|
2806
2837
|
window.history.replaceState({}, '', url.toString());
|
|
2807
2838
|
} catch (_) {}
|
|
2808
2839
|
state.messages = [];
|
|
@@ -2899,6 +2930,27 @@ state._msgLoadOffset = 0;
|
|
|
2899
2930
|
state._msgLoadTotal = 0;
|
|
2900
2931
|
|
|
2901
2932
|
async function selectSession(id) {
|
|
2933
|
+
// [v1.25.6] 确保从群聊视图切回个人聊天视图(与 selectAgent/exitGroupChat 保持一致)
|
|
2934
|
+
if (currentView === 'group') {
|
|
2935
|
+
currentView = 'chat';
|
|
2936
|
+
currentGroupId = null;
|
|
2937
|
+
groupMessages = [];
|
|
2938
|
+
// 恢复头部 UI
|
|
2939
|
+
var _gbb = document.getElementById('groupBackBtn'); if (_gbb) _gbb.style.display = 'none';
|
|
2940
|
+
var _gsb = document.getElementById('groupSettingsBtn'); if (_gsb) _gsb.style.display = 'none';
|
|
2941
|
+
var _ccb2 = document.getElementById('clearChatBtn'); if (_ccb2) _ccb2.style.display = '';
|
|
2942
|
+
var _dot = document.querySelector('.main-title .dot');
|
|
2943
|
+
if (_dot) _dot.style.display = '';
|
|
2944
|
+
document.getElementById('userInput').placeholder = '输入消息... (Enter 发送, Shift+Enter 换行)';
|
|
2945
|
+
// 恢复侧边栏 UI
|
|
2946
|
+
var _newChatBtn2 = document.querySelector('.new-chat-btn');
|
|
2947
|
+
if (_newChatBtn2) _newChatBtn2.style.display = '';
|
|
2948
|
+
var _searchInput2 = document.getElementById('searchInput');
|
|
2949
|
+
if (_searchInput2) { _searchInput2.placeholder = '搜索对话...'; }
|
|
2950
|
+
StatePersistence.remove('currentView');
|
|
2951
|
+
StatePersistence.remove('currentGroupId');
|
|
2952
|
+
}
|
|
2953
|
+
|
|
2902
2954
|
if (id === '__new__') {
|
|
2903
2955
|
newChat();
|
|
2904
2956
|
if (isMobile()) closeMobileSidebar();
|
|
@@ -2915,8 +2967,8 @@ async function selectSession(id) {
|
|
|
2915
2967
|
try { state.abortController.abort(); } catch (_) {}
|
|
2916
2968
|
state.abortController = null;
|
|
2917
2969
|
}
|
|
2918
|
-
hideTypingIndicator();
|
|
2919
|
-
stopExecTimerPolling();
|
|
2970
|
+
if (typeof hideTypingIndicator === 'function') hideTypingIndicator();
|
|
2971
|
+
if (typeof stopExecTimerPolling === 'function') stopExecTimerPolling();
|
|
2920
2972
|
document.getElementById('sendBtn').style.display = '';
|
|
2921
2973
|
document.getElementById('sendBtn').disabled = true;
|
|
2922
2974
|
document.getElementById('stopBtn').style.display = 'none';
|
|
@@ -2927,10 +2979,19 @@ async function selectSession(id) {
|
|
|
2927
2979
|
state._msgLoadTotal = 0;
|
|
2928
2980
|
|
|
2929
2981
|
// ── 更新 URL 参数,保留会话 ID(刷新页面可恢复会话) ──
|
|
2982
|
+
// [v1.27.2] 使用 aid(数字)代替 a(编码),移除明文 agent/session 参数
|
|
2930
2983
|
try {
|
|
2931
2984
|
const url = new URL(window.location.href);
|
|
2932
2985
|
url.searchParams.set('s', UrlCodec.encode(id));
|
|
2933
|
-
|
|
2986
|
+
// 移除旧格式参数
|
|
2987
|
+
url.searchParams.delete('a');
|
|
2988
|
+
url.searchParams.delete('agent');
|
|
2989
|
+
url.searchParams.delete('session');
|
|
2990
|
+
// 设置 aid(数字 agent ID)
|
|
2991
|
+
var _selAgentObj = findAgentByPath(state.activeAgent);
|
|
2992
|
+
if (_selAgentObj && _selAgentObj.aid) {
|
|
2993
|
+
url.searchParams.set('aid', _selAgentObj.aid);
|
|
2994
|
+
}
|
|
2934
2995
|
if (state.chatMode) url.searchParams.set('mode', state.chatMode);
|
|
2935
2996
|
window.history.replaceState({}, '', url.toString());
|
|
2936
2997
|
} catch (_) {}
|
|
@@ -3783,7 +3844,8 @@ function renderMessages() {
|
|
|
3783
3844
|
}
|
|
3784
3845
|
|
|
3785
3846
|
function _renderMessagesInner() {
|
|
3786
|
-
|
|
3847
|
+
// [v1.25.6] 与 renderSessions 保持一致:同时检查 groups.length > 0
|
|
3848
|
+
if (currentView === 'group' && typeof groups !== 'undefined' && groups.length > 0) {
|
|
3787
3849
|
renderGroupMessages();
|
|
3788
3850
|
return;
|
|
3789
3851
|
}
|
|
@@ -1648,7 +1648,12 @@ async function sendMessage(opts) {
|
|
|
1648
1648
|
try {
|
|
1649
1649
|
const url = new URL(window.location.href);
|
|
1650
1650
|
url.searchParams.set('s', UrlCodec.encode(sessionId));
|
|
1651
|
-
|
|
1651
|
+
// [v1.27.2] 使用 aid 代替 a
|
|
1652
|
+
url.searchParams.delete('a');
|
|
1653
|
+
url.searchParams.delete('agent');
|
|
1654
|
+
url.searchParams.delete('session');
|
|
1655
|
+
var _feAgentObj = (typeof findAgentByPath === 'function') ? findAgentByPath(state.activeAgent) : null;
|
|
1656
|
+
if (_feAgentObj && _feAgentObj.aid) url.searchParams.set('aid', _feAgentObj.aid);
|
|
1652
1657
|
window.history.replaceState({}, '', url.toString());
|
|
1653
1658
|
} catch (_) {}
|
|
1654
1659
|
}
|
|
@@ -2874,7 +2879,12 @@ const StreamingRecovery = {
|
|
|
2874
2879
|
try {
|
|
2875
2880
|
const url = new URL(window.location.href);
|
|
2876
2881
|
url.searchParams.set('s', UrlCodec.encode(sessionId));
|
|
2877
|
-
|
|
2882
|
+
// [v1.27.2] 使用 aid 代替 a
|
|
2883
|
+
url.searchParams.delete('a');
|
|
2884
|
+
url.searchParams.delete('agent');
|
|
2885
|
+
url.searchParams.delete('session');
|
|
2886
|
+
var _rvAgentObj = (typeof findAgentByPath === 'function') ? findAgentByPath(state.activeAgent) : null;
|
|
2887
|
+
if (_rvAgentObj && _rvAgentObj.aid) url.searchParams.set('aid', _rvAgentObj.aid);
|
|
2878
2888
|
window.history.replaceState({}, '', url.toString());
|
|
2879
2889
|
} catch (e) {
|
|
2880
2890
|
console.error('[Recovery] 更新URL失败:', e);
|
package/worklog.md
CHANGED
|
@@ -1,26 +1,3 @@
|
|
|
1
|
-
---
|
|
2
|
-
Task ID: [20260420-05]
|
|
3
|
-
Agent: Main Agent
|
|
4
|
-
Task: 发布 v1.27.0 到 GitHub 和 npm(完成)
|
|
5
|
-
|
|
6
|
-
Work Log:
|
|
7
|
-
- 升级版本号:v1.26.0 → v1.27.0(重大架构升级)
|
|
8
|
-
- Git 推送:推送至 GitHub origin/main
|
|
9
|
-
- npm 发布:发布 myagent-ai@1.27.0 到 npmjs.com
|
|
10
|
-
- 同步更新:package.json、worklog.md、core/version.py
|
|
11
|
-
|
|
12
|
-
版本说明:
|
|
13
|
-
- 数字 agent_id 系统(agents 表 + agent_id INTEGER)
|
|
14
|
-
- session_id 完全独立(不再包含 agent 前缀)
|
|
15
|
-
- goodbye CLI 工具(清空数据 + 卸载项目)
|
|
16
|
-
- 移除所有迁移逻辑,使用全新数据库结构
|
|
17
|
-
|
|
18
|
-
Stage Summary:
|
|
19
|
-
- ✅ GitHub: https://github.com/ctz168/myagent
|
|
20
|
-
- ✅ npm: https://www.npmjs.com/package/myagent-ai
|
|
21
|
-
- ✅ 版本:v1.27.0
|
|
22
|
-
- ✅ 所有功能测试通过
|
|
23
|
-
|
|
24
1
|
---
|
|
25
2
|
Task ID: [20260420-04]
|
|
26
3
|
Agent: Main Agent
|
package/.phoneide_tmp.py
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import subprocess, sys
|
|
2
|
-
proc = subprocess.Popen([sys.executable, '-m', 'pip', 'install', '-r', 'requirements.txt'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, bufsize=1)
|
|
3
|
-
for line in proc.stdout:
|
|
4
|
-
print(line, end='')
|
|
5
|
-
sys.stdout.flush()
|
|
6
|
-
proc.wait()
|
|
7
|
-
if proc.returncode != 0:
|
|
8
|
-
print(f'\nExit code: {proc.returncode}')
|