myagent-ai 1.32.8 → 1.32.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.32.8",
3
+ "version": "1.32.10",
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
@@ -3515,7 +3515,7 @@ window.addEventListener('beforeunload', function() {{
3515
3515
  """GET /api/agents - 返回扁平 agent 列表(系统 agent 排在最前)"""
3516
3516
  await self._ensure_agents_initialized()
3517
3517
  agents = self._scan_agents_flat()
3518
- # 统计会话数
3518
+ # 统计会话数 + 附加数字 agent_id
3519
3519
  if self.core.memory:
3520
3520
  rows = self.core.memory._get_conn().execute(
3521
3521
  "SELECT session_id, COUNT(*) as cnt FROM memories "
@@ -3527,6 +3527,7 @@ window.addEventListener('beforeunload', function() {{
3527
3527
  session_counts[ap] = session_counts.get(ap, 0) + r["cnt"]
3528
3528
  for a in agents:
3529
3529
  a["session_count"] = session_counts.get(a["path"], 0)
3530
+ a["aid"] = self.core.memory.get_agent_id(a["path"])
3530
3531
  # 系统 agent 排在最前
3531
3532
  agents.sort(key=lambda a: (0 if a.get("system") else 1, a.get("path", "")))
3532
3533
  return web.json_response(agents)
@@ -405,12 +405,19 @@ async function deleteAgentKB(path,filename){
405
405
 
406
406
  async function loadAgentSessions(){
407
407
  const path=window._currentEditAgentPath;if(!path)return;
408
+ // 查找该 agent 的数字 ID(aid)
409
+ var _editAgentAid = 1;
410
+ if (typeof allAgentsCache !== 'undefined' && Array.isArray(allAgentsCache)) {
411
+ for (var i = 0; i < allAgentsCache.length; i++) {
412
+ if (allAgentsCache[i].path === path) { _editAgentAid = allAgentsCache[i].aid || 1; break; }
413
+ }
414
+ }
408
415
  const data=await api(`/api/agents/${encodeURIComponent(path)}/sessions`);
409
416
  const sessions=Array.isArray(data)?data:(data?.sessions||[]);
410
417
  let html=`<div class="flex justify-between items-center mb-16"><h4 style="font-size:14px;color:var(--text2)">会话 (${sessions.length})</h4></div>`;
411
418
  if(sessions.length===0){html+='<div class="empty">暂无会话</div>';}
412
419
  else{html+='<div class="table-wrap"><table><tr><th>会话</th><th>消息数</th><th>最后活动</th><th></th></tr>';
413
- for(const s of sessions){const dn=s.display_name||s.id;html+=`<tr><td style="max-width:200px;overflow:hidden;text-overflow:ellipsis" title="${escHtml(s.id)}">${escHtml(dn.length>25?dn.slice(0,25)+'...':dn)}</td><td>${s.messages||0}</td><td>${fmtTimeAgo(s.last)}</td><td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}','${escHtml(path)}')">切入</button> <button class="btn btn-sm btn-ghost" onclick="viewSessionMsgs('${escHtml(s.id)}')">查看</button></td></tr>`}
420
+ for(const s of sessions){const dn=s.display_name||s.id;html+=`<tr><td style="max-width:200px;overflow:hidden;text-overflow:ellipsis" title="${escHtml(s.id)}">${escHtml(dn.length>25?dn.slice(0,25)+'...':dn)}</td><td>${s.messages||0}</td><td>${fmtTimeAgo(s.last)}</td><td><button class="btn btn-sm" style="background:var(--success);color:#fff" onclick="enterSession('${escHtml(s.id)}',${_editAgentAid})">切入</button> <button class="btn btn-sm btn-ghost" onclick="viewSessionMsgs('${escHtml(s.id)}')">查看</button></td></tr>`}
414
421
  html+='</table></div>';}
415
422
  $('sessionsContent').innerHTML=html;
416
423
  }
@@ -11,18 +11,8 @@ async function renderSessions(){
11
11
  }
12
12
  let html='<div class="table-wrap"><table><tr><th>会话</th><th>Agent</th><th>消息数</th><th>最后活动</th><th>操作</th></tr>';
13
13
  for(const s of (ss||[])){
14
- // [v1.27.2] 优先使用后端返回的 agent_id(现在是 agent 名称),回退从前缀猜测
14
+ // 使用后端返回的 agent_idagent 名称)
15
15
  let agentName=s.agent_id||'';
16
- if(!agentName){
17
- // 回退:从 session_id 前缀提取(兼容旧格式)
18
- if((s.id||'').indexOf('_web_')>=0){
19
- agentName=(s.id||'').split('_web_')[0]||'default';
20
- }else if((s.id||'').indexOf('_cli_')>=0){
21
- agentName=(s.id||'').split('_cli_')[0]||'default';
22
- }else{
23
- agentName='default';
24
- }
25
- }
26
16
  const displayName=s.display_name||s.id;
27
17
  const dbId=s.agent_db_id||1;
28
18
  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>
@@ -2815,13 +2815,9 @@ function newChat() {
2815
2815
  state.activeSessionId = '__new__';
2816
2816
  state._selectedSessionLabel = null; // [v1.18.9] 清除选中的会话名称
2817
2817
  // ── 更新 URL(新对话移除 session 参数) ──
2818
- // [v1.27.2] 使用 aid 代替 a
2819
2818
  try {
2820
2819
  const url = new URL(window.location.href);
2821
2820
  url.searchParams.delete('s');
2822
- url.searchParams.delete('session');
2823
- url.searchParams.delete('a');
2824
- url.searchParams.delete('agent');
2825
2821
  var _ncAgentObj = findAgentByPath(state.activeAgent);
2826
2822
  if (_ncAgentObj && _ncAgentObj.aid) {
2827
2823
  url.searchParams.set('aid', _ncAgentObj.aid);
@@ -3530,6 +3526,8 @@ function groupHistoryMessages(messages) {
3530
3526
  // ── [v1.23.20] 统一消息渲染函数:历史消息和流式消息共享 ──
3531
3527
  // 生成单条消息的完整 HTML(message-row 外壳 + 所有内部内容)
3532
3528
  // 流式结束后也用此函数重建 DOM,确保历史/流式样式完全一致
3529
+ // [v1.32.9] 流式结束后 _normalizeMessageAfterStreaming() 将 parts 转为 groupHistoryMessages() 格式,
3530
+ // 所以无论数据来源如何,此函数都走同一渲染分支
3533
3531
  window.buildMessageHtml = function(msg, idx, agent) {
3534
3532
  const isUser = msg.role === 'user';
3535
3533
  if (msg.role === 'tool') return '';
@@ -1373,11 +1373,13 @@ function showToolResultModal(msgIndex, eventId) {
1373
1373
  if (msg.exec_events) {
1374
1374
  evt = msg.exec_events.find(e => String(e.id) === String(eventId));
1375
1375
  }
1376
- // 再从 parts 查找(V2 格式:{type:'v2_tool', data:{id, tool_name, ...}})
1376
+ // 再从 parts 查找(V2 格式或规范化后的 exec 格式)
1377
1377
  if (!evt && msg.parts) {
1378
1378
  for (const part of msg.parts) {
1379
- if (part.type === 'v2_tool' && part.data && String(part.data.id) === String(eventId)) {
1380
- evt = part.data;
1379
+ // [v1.32.9] 同时支持 v2_tool 和规范化后的 exec 格式
1380
+ const inner = (part.type === 'v2_tool') ? part.data : (part.type === 'exec') ? part.data : null;
1381
+ if (inner && String(inner.id) === String(eventId)) {
1382
+ evt = inner;
1381
1383
  break;
1382
1384
  }
1383
1385
  }
@@ -1532,6 +1534,112 @@ function _assembleV2Content(msg, msgParts) {
1532
1534
  return '(无回复)';
1533
1535
  }
1534
1536
 
1537
+ // ══════════════════════════════════════════════════════
1538
+ // ── [v1.32.9] Post-Streaming Normalization ──
1539
+ // ══════════════════════════════════════════════════════
1540
+
1541
+ /**
1542
+ * 流式结束后,将 msg.parts 规范化为与 groupHistoryMessages() 一致的格式。
1543
+ * 这确保 buildMessageHtml() 在流式和历史两条路径上走同一个渲染分支,
1544
+ * 避免视觉差异。
1545
+ *
1546
+ * 主要处理:
1547
+ * 1. v2_tool (tool_start + tool_result) → exec (合并卡片,与历史格式对齐)
1548
+ * 2. 仅含1个文本部分且无工具调用时 → 删除 parts(走简单气泡路径,与历史对齐)
1549
+ * 3. v2_ask → 保留原样
1550
+ */
1551
+ function _normalizeMessageAfterStreaming(msg) {
1552
+ if (!msg || !Array.isArray(msg.parts) || msg.parts.length === 0) return;
1553
+
1554
+ // ── Step 1: 转换 v2_tool 为 exec 格式,并合并 tool_start + tool_result ──
1555
+ var normalizedParts = [];
1556
+ var evtIdCounter = 0;
1557
+
1558
+ for (var i = 0; i < msg.parts.length; i++) {
1559
+ var part = msg.parts[i];
1560
+
1561
+ if (part.type === 'v2_tool') {
1562
+ var inner = part.data || {};
1563
+ // tool_start: 创建 exec 卡片
1564
+ if (inner.type === 'tool_start' || inner.status === 'running') {
1565
+ var execPart = {
1566
+ type: 'exec',
1567
+ data: {
1568
+ id: evtIdCounter,
1569
+ type: 'tool_call',
1570
+ title: inner.title || inner.tool_name || '工具调用',
1571
+ tool_name: inner.tool_name || '',
1572
+ params: inner.params,
1573
+ status: 'done',
1574
+ has_result: false,
1575
+ result_data: null,
1576
+ }
1577
+ };
1578
+ // 向后搜索同一 tool_name 的 tool_result 进行合并
1579
+ for (var ri = i + 1; ri < msg.parts.length; ri++) {
1580
+ var rp = msg.parts[ri];
1581
+ if (rp.type === 'v2_tool' && rp.data && rp.data.type === 'tool_result') {
1582
+ var rInner = rp.data;
1583
+ if (rInner.tool_name === inner.tool_name || rInner.id === inner.id) {
1584
+ execPart.data.has_result = true;
1585
+ execPart.data.success = rInner.success;
1586
+ execPart.data.summary = rInner.summary ? rInner.summary.substring(0, 500).trim() : undefined;
1587
+ execPart.data.result = rInner.result || { output: rInner.summary || '' };
1588
+ if (rInner.title) execPart.data.title = rInner.title;
1589
+ // 标记已消费
1590
+ rp._consumed = true;
1591
+ break;
1592
+ }
1593
+ }
1594
+ // 遇到下一个 tool_start 就停止搜索
1595
+ if (rp.type === 'v2_tool' && rp.data && (rp.data.type === 'tool_start' || rp.data.status === 'running')) break;
1596
+ }
1597
+ evtIdCounter++;
1598
+ normalizedParts.push(execPart);
1599
+ } else if (inner.type === 'tool_result' && !part._consumed) {
1600
+ // 孤立的 tool_result(没有匹配的 tool_start)
1601
+ evtIdCounter++;
1602
+ normalizedParts.push({
1603
+ type: 'exec',
1604
+ data: {
1605
+ id: evtIdCounter,
1606
+ type: 'tool_result',
1607
+ title: inner.title || inner.tool_name || '工具结果',
1608
+ tool_name: inner.tool_name || '',
1609
+ success: inner.success,
1610
+ summary: inner.summary ? inner.summary.substring(0, 500).trim() : undefined,
1611
+ result: inner.result || { output: inner.summary || '' },
1612
+ has_result: true,
1613
+ }
1614
+ });
1615
+ }
1616
+ // 其他 v2_tool 类型(如已完成但非 start/result 的),也转为 exec
1617
+ else if (inner.type !== 'tool_start' && inner.type !== 'tool_result') {
1618
+ evtIdCounter++;
1619
+ normalizedParts.push({
1620
+ type: 'exec',
1621
+ data: Object.assign({}, inner, { id: evtIdCounter })
1622
+ });
1623
+ }
1624
+ } else {
1625
+ // text / exec / v2_ask 等类型保持不变
1626
+ normalizedParts.push(part);
1627
+ }
1628
+ }
1629
+
1630
+ // ── Step 2: 与 groupHistoryMessages() 对齐——决定是否保留 parts ──
1631
+ // groupHistoryMessages 逻辑: 仅当 hasExecParts || textParts.length > 1 时设置 parts
1632
+ var textParts = normalizedParts.filter(function(p) { return p.type === 'text'; });
1633
+ var hasExecParts = normalizedParts.some(function(p) { return p.type === 'exec'; });
1634
+
1635
+ if (hasExecParts || textParts.length > 1) {
1636
+ msg.parts = normalizedParts;
1637
+ } else {
1638
+ // 仅1段文本且无工具调用 → 删除 parts,走简单气泡渲染路径
1639
+ delete msg.parts;
1640
+ }
1641
+ }
1642
+
1535
1643
  // ══════════════════════════════════════════════════════
1536
1644
  // ── Voice Input: User Bubble Replacement ──
1537
1645
  // ══════════════════════════════════════════════════════
@@ -1648,10 +1756,6 @@ async function sendMessage(opts) {
1648
1756
  try {
1649
1757
  const url = new URL(window.location.href);
1650
1758
  url.searchParams.set('s', UrlCodec.encode(sessionId));
1651
- // [v1.27.2] 使用 aid 代替 a
1652
- url.searchParams.delete('a');
1653
- url.searchParams.delete('agent');
1654
- url.searchParams.delete('session');
1655
1759
  var _feAgentObj = (typeof findAgentByPath === 'function') ? findAgentByPath(state.activeAgent) : null;
1656
1760
  if (_feAgentObj && _feAgentObj.aid) url.searchParams.set('aid', _feAgentObj.aid);
1657
1761
  window.history.replaceState({}, '', url.toString());
@@ -1903,6 +2007,8 @@ async function sendMessage(opts) {
1903
2007
  state.messages[msgIdx].content = _assembleV2Content(state.messages[msgIdx], msgParts);
1904
2008
  state.messages[msgIdx]._streamingText = '';
1905
2009
  if (allExecEvents.length > 0) state.messages[msgIdx].exec_events = [...allExecEvents];
2010
+ // [v1.32.9] 规范化 parts 为 groupHistoryMessages() 格式
2011
+ _normalizeMessageAfterStreaming(state.messages[msgIdx]);
1906
2012
  }
1907
2013
  // Start new message
1908
2014
  state.messages.push({ role: 'user', content: evt.message, time: new Date().toISOString() });
@@ -2312,6 +2418,8 @@ async function sendMessage(opts) {
2312
2418
  state.messages[msgIdx].exec_events = allExecEvents;
2313
2419
  // Assemble final content: prefer V2 reasoning/ask text over raw XML
2314
2420
  state.messages[msgIdx].content = _assembleV2Content(state.messages[msgIdx], msgParts);
2421
+ // [v1.32.9] 规范化 parts 为 groupHistoryMessages() 格式,确保 buildMessageHtml() 渲染一致
2422
+ _normalizeMessageAfterStreaming(state.messages[msgIdx]);
2315
2423
  }
2316
2424
 
2317
2425
  // ── 流式传输完成,清除恢复数据 ──
@@ -2420,7 +2528,13 @@ async function sendMessage(opts) {
2420
2528
  state.isGenerating = false;
2421
2529
  state.abortController = null;
2422
2530
  // 重置所有消息的流式标志
2423
- state.messages.forEach(m => { if(m.streaming) m.streaming = false; });
2531
+ state.messages.forEach(m => {
2532
+ if(m.streaming) {
2533
+ m.streaming = false;
2534
+ // [v1.32.9] 规范化 parts,确保异常结束时也和历史格式一致
2535
+ _normalizeMessageAfterStreaming(m);
2536
+ }
2537
+ });
2424
2538
  hideTypingIndicator();
2425
2539
  stopExecTimerPolling();
2426
2540
  document.getElementById('sendBtn').style.display = '';
@@ -2879,10 +2993,6 @@ const StreamingRecovery = {
2879
2993
  try {
2880
2994
  const url = new URL(window.location.href);
2881
2995
  url.searchParams.set('s', UrlCodec.encode(sessionId));
2882
- // [v1.27.2] 使用 aid 代替 a
2883
- url.searchParams.delete('a');
2884
- url.searchParams.delete('agent');
2885
- url.searchParams.delete('session');
2886
2996
  var _rvAgentObj = (typeof findAgentByPath === 'function') ? findAgentByPath(state.activeAgent) : null;
2887
2997
  if (_rvAgentObj && _rvAgentObj.aid) url.searchParams.set('aid', _rvAgentObj.aid);
2888
2998
  window.history.replaceState({}, '', url.toString());