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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.27.0",
3
+ "version": "1.27.2",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
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.25.7] 使用 agent_id 字段精确匹配,同时兼容旧数据(agent_id为空时回退到前缀匹配)
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='' agent_id='default' 旧格式 web_default/sess_*
4307
- rows = self.core.memory._get_conn().execute(
4308
- """SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories
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 ('llm_output', 'llm_input', 'tool_result_raw', 'conversation_insight')
4311
- AND (agent_id = '' OR agent_id = 'default' OR session_id = 'web_default' OR session_id LIKE 'sess_%')
4312
- GROUP BY session_id ORDER BY last DESC LIMIT 100""").fetchall()
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=agent 或 session_id 前缀匹配(旧数据)
4315
- rows = self.core.memory._get_conn().execute(
4316
- """SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories
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 ('llm_output', 'llm_input', 'tool_result_raw', 'conversation_insight')
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
- (agent, f"{agent}_%")).fetchall()
4339
+ (target_aid, target_aid, f"{agent}_%")).fetchall()
4322
4340
  else:
4323
- rows = self.core.memory._get_conn().execute(
4324
- "SELECT DISTINCT session_id, COUNT(*) as cnt, MAX(created_at) as last FROM memories "
4325
- "WHERE category = 'session' AND role != '' "
4326
- "AND key NOT IN ('llm_output', 'llm_input', 'tool_result_raw', 'conversation_insight') "
4327
- "GROUP BY session_id ORDER BY last DESC LIMIT 100").fetchall()
4328
- sessions = [{"id": r["session_id"], "messages": r["cnt"], "last": r["last"]} for r in rows]
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.25.7] 使用 agent_id 字段精确匹配,同时兼容旧数据
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='' 或 agent_id='default' 或 旧格式 web_default/sess_*
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 = '' OR agent_id = 'default' OR session_id = 'web_default' OR session_id LIKE 'sess_%')
4364
- GROUP BY session_id ORDER BY last DESC LIMIT 100""").fetchall()
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=name 或 session_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
- (name, f"{name}_%")).fetchall()
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
- limit = min(int(request.query.get("limit", 500)), 500)
4396
- offset = int(request.query.get("offset", 0))
4397
- entries = self.core.memory.get_conversation(sid, limit=limit + offset)
4398
- entries = entries[offset:]
4399
- # Filter out internal entries (LLM raw output only)
4400
- entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
4401
- result = []
4402
- for e in entries:
4403
- msg = {"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""}
4404
- # [v1.16.17] 附加附件元数据
4405
- meta = e.metadata or {}
4406
- if meta.get("images"):
4407
- msg["images"] = meta["images"]
4408
- # [v1.23.19] 分离 _files 和 _media(前端用 msg._files 和 msg._media)
4409
- _raw_files = meta.get("files") or []
4410
- if _raw_files:
4411
- _file_items = [f for f in _raw_files if f.get("_type") != "media"]
4412
- _media_items = [f for f in _raw_files if f.get("_type") == "media"]
4413
- if _file_items:
4414
- msg["_files"] = _file_items
4415
- if _media_items:
4416
- msg["_media"] = _media_items
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
- limit = min(int(request.query.get("limit", 500)), 500)
4457
- offset = int(request.query.get("offset", 0))
4458
- entries = self.core.memory.get_conversation(sid, limit=limit + offset)
4459
- entries = entries[offset:]
4460
- # Filter out internal entries (LLM raw output only)
4461
- entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
4462
- result = []
4463
- for e in entries:
4464
- msg = {"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""}
4465
- # [v1.16.17] 附加附件元数据
4466
- meta = e.metadata or {}
4467
- if meta.get("images"):
4468
- msg["images"] = meta["images"]
4469
- # [v1.23.19] 分离 _files 和 _media(前端用 msg._files 和 msg._media)
4470
- _raw_files = meta.get("files") or []
4471
- if _raw_files:
4472
- _file_items = [f for f in _raw_files if f.get("_type") != "media"]
4473
- _media_items = [f for f in _raw_files if f.get("_type") == "media"]
4474
- if _file_items:
4475
- msg["_files"] = _file_items
4476
- if _media_items:
4477
- msg["_media"] = _media_items
4478
- result.append(msg)
4479
- return web.json_response(result)
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?agent='+encodeURIComponent(path)+'&mode=exec';
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,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;').replace(/"/g,'&quot;');}
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
- // session_id 提取 agent
11
- // 旧格式: agent_web_timestamp → 按 _web_ 分割
12
- // 新格式: sess_uuid → 显示为 "未知Agent" 或从后端获取
13
- let agentName='';
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 if((s.id||'').indexOf('_')>=0){
19
- // 尝试取第一段作为 agent 名
20
- agentName=(s.id||'').split('_')[0]||'default';
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)}','${escHtml(agentName)}')">切入</button>
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)}','${escHtml(sid.split('_web_')[0]||'default')}')">在聊天中查看完整记录</button></div>`;
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
- function enterSession(sid,agentName){
239
- window.location.href='/ui/chat/chat_container.html?agent='+encodeURIComponent(agentName)+'&mode=exec&session='+encodeURIComponent(sid);
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 = {};
@@ -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
- if (state.activeAgent) params.set('agent', state.activeAgent);
142
- if (state.activeSessionId && state.activeSessionId !== '__new__') params.set('session', state.activeSessionId);
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 参数处理: ?a=xxx&mode=exec&s=xxx&g=xxx&popout=1(agent/session 使用 UrlCodec 编码)
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 urlAgent = UrlCodec.decode(urlParams.get('a') || '') || UrlCodec.decode(urlParams.get('agent') || '');
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
- // 现在可以安全地做 agent/session 选择(不会再和 sendMessage 竞态)
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
- // 不再依赖 JS 状态传递(from_group),完全通过 URL session ID 恢复
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.set('a', UrlCodec.encode(state.activeAgent || 'default'));
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
- url.searchParams.set('a', UrlCodec.encode(state.activeAgent || 'default'));
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
- if (currentView === 'group') {
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
- url.searchParams.set('a', UrlCodec.encode(state.activeAgent || 'default'));
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
- url.searchParams.set('a', UrlCodec.encode(state.activeAgent || 'default'));
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}')