myagent-ai 1.10.4 → 1.10.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -420,7 +420,7 @@ class MainAgent(BaseAgent):
420
420
  if all_tool_outputs:
421
421
  messages.append(Message(
422
422
  role="user",
423
- content=f"[上一轮工具执行结果汇总]\n{truncate_str(all_tool_outputs, 15000)}"
423
+ content=f"[上一轮工具执行结果汇总]\n{truncate_str(all_tool_outputs, 30000)}"
424
424
  ))
425
425
  all_tool_outputs = ""
426
426
 
@@ -700,20 +700,59 @@ class MainAgent(BaseAgent):
700
700
 
701
701
  # 发送工具结果事件
702
702
  # 提取实际输出:SkillResult 有 output/message/data,ExecResult 有 stdout/stderr
703
+ def _format_data_for_llm(data):
704
+ """将结构化 data 格式化为 LLM 可读的文本"""
705
+ if data is None:
706
+ return ""
707
+ if isinstance(data, str):
708
+ return data
709
+ if isinstance(data, dict):
710
+ # 搜索结果列表格式 (web_search)
711
+ results = data.get("results")
712
+ if isinstance(results, list):
713
+ lines = []
714
+ for i, r in enumerate(results, 1):
715
+ title = r.get("title", "")
716
+ url = r.get("url", "")
717
+ snippet = r.get("snippet", "")
718
+ lines.append(f"{i}. {title}\n URL: {url}\n {snippet}")
719
+ return "\n".join(lines)
720
+ # 网页内容格式 (web_read)
721
+ if "url" in data and "content" in data:
722
+ title = data.get("title", "")
723
+ content = data.get("content", "")
724
+ lines = [f"标题: {title}", f"URL: {data['url']}", f"内容:\n{content}"]
725
+ return "\n".join(lines)
726
+ # 通用 dict: key-value 格式
727
+ parts = []
728
+ for k, v in data.items():
729
+ if k == "results":
730
+ continue # 已在上面处理
731
+ parts.append(f"{k}: {v}")
732
+ return "\n".join(parts) if parts else str(data)
733
+ if isinstance(data, list):
734
+ return "\n".join(str(item) for item in data)
735
+ return str(data)
736
+
703
737
  def _extract_tool_output(tr):
704
738
  """从工具结果中提取实际输出文本"""
739
+ # 优先使用 output 字段 (技能明确设置的输出)
705
740
  out = tr.get("output", "")
706
741
  if out:
707
742
  return out
743
+ # 如果 output 为空,尝试智能格式化 data 字段
744
+ data = tr.get("data")
745
+ if data is not None:
746
+ formatted = _format_data_for_llm(data)
747
+ if formatted:
748
+ return formatted
749
+ # 降级到 message / stdout / error
708
750
  out = tr.get("message", "")
709
751
  if out:
710
752
  return out
711
753
  out = tr.get("stdout", "")
712
754
  if out:
713
755
  return out
714
- data = tr.get("data")
715
- if data is not None:
716
- return str(data) if not isinstance(data, str) else data
717
756
  return tr.get("error", "")
718
757
 
719
758
  tool_output_text = _extract_tool_output(tool_result)
@@ -745,16 +784,18 @@ class MainAgent(BaseAgent):
745
784
  need_callback = True
746
785
 
747
786
  output_str = tool_output_text
787
+ # 搜索和网页读取类工具允许更长的输出
788
+ _max_output = 6000 if tool_name in ("web_search", "web_read", "url_read") else 3000
748
789
  tool_outputs_parts.append(
749
790
  f"### {before_call}\n"
750
791
  f"**工具**: {tool_name}\n"
751
792
  f"**结果**: {'成功' if tool_result.get('success') else '失败'}\n"
752
- f"{truncate_str(output_str, 2000)}\n"
793
+ f"{truncate_str(output_str, _max_output)}\n"
753
794
  )
754
795
 
755
796
  conversation_history.append(Message(
756
797
  role="assistant",
757
- content=f"执行工具 {tool_name}:\n{truncate_str(output_str, 3000)}",
798
+ content=f"执行工具 {tool_name}:\n{truncate_str(output_str, _max_output)}",
758
799
  ))
759
800
  conversation_history.append(Message(
760
801
  role="user",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.10.4",
3
+ "version": "1.10.5",
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
@@ -2357,6 +2357,9 @@ class ApiServer:
2357
2357
  pass
2358
2358
  return web.json_response({**agent_info, "sessions": sessions})
2359
2359
 
2360
+ # Internal keys that should not appear in chat history UI
2361
+ _HIDDEN_KEYS = {"llm_output", "tool_call", "tool_result"}
2362
+
2360
2363
  async def handle_get_messages(self, request):
2361
2364
  sid = request.match_info["sid"]
2362
2365
  if not self.core.memory: return web.json_response([])
@@ -2364,6 +2367,8 @@ class ApiServer:
2364
2367
  offset = int(request.query.get("offset", 0))
2365
2368
  entries = self.core.memory.get_conversation(sid, limit=limit + offset)
2366
2369
  entries = entries[offset:]
2370
+ # Filter out internal entries (LLM raw output, tool calls/results)
2371
+ entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
2367
2372
  return web.json_response([{"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""} for e in entries])
2368
2373
 
2369
2374
  async def handle_get_messages_query(self, request):
@@ -2376,6 +2381,8 @@ class ApiServer:
2376
2381
  offset = int(request.query.get("offset", 0))
2377
2382
  entries = self.core.memory.get_conversation(sid, limit=limit + offset)
2378
2383
  entries = entries[offset:]
2384
+ # Filter out internal entries (LLM raw output, tool calls/results)
2385
+ entries = [e for e in entries if (e.key or "") not in self._HIDDEN_KEYS]
2379
2386
  return web.json_response([{"role": e.role, "content": e.content, "time": e.created_at, "key": e.key or ""} for e in entries])
2380
2387
 
2381
2388
  async def handle_delete_session(self, request):
@@ -1765,9 +1765,14 @@ async function selectSession(id) {
1765
1765
  const loaded = (Array.isArray(data) ? data : []).filter(function(m) {
1766
1766
  return m && (m.role === 'user' || m.role === 'assistant' || m.role === 'tool');
1767
1767
  }).map(function(m) {
1768
+ var content = (m.content != null) ? String(m.content) : '';
1769
+ // Strip XML tags from assistant messages (backend may store raw LLM XML output)
1770
+ if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
1771
+ content = (typeof _stripXmlTags === 'function') ? _stripXmlTags(content) : content;
1772
+ }
1768
1773
  return {
1769
1774
  role: m.role || 'assistant',
1770
- content: (m.content != null) ? String(m.content) : '',
1775
+ content: content,
1771
1776
  time: m.time || m.created_at || '',
1772
1777
  key: m.key || '',
1773
1778
  };
@@ -1824,9 +1829,14 @@ async function loadMoreMessages() {
1824
1829
  const loaded = data.filter(function(m) {
1825
1830
  return m && (m.role === 'user' || m.role === 'assistant' || m.role === 'tool');
1826
1831
  }).map(function(m) {
1832
+ var content = (m.content != null) ? String(m.content) : '';
1833
+ // Strip XML tags from assistant messages (backend may store raw LLM XML output)
1834
+ if (m.role === 'assistant' && content && content.trim().startsWith('<')) {
1835
+ content = (typeof _stripXmlTags === 'function') ? _stripXmlTags(content) : content;
1836
+ }
1827
1837
  return {
1828
1838
  role: m.role || 'assistant',
1829
- content: (m.content != null) ? String(m.content) : '',
1839
+ content: content,
1830
1840
  time: m.time || m.created_at || '',
1831
1841
  };
1832
1842
  });
@@ -305,22 +305,25 @@ function updateStreamingMessage(msgIdx) {
305
305
  const container = document.getElementById('messagesInner');
306
306
  if (!container) return;
307
307
 
308
- // Find or create the streaming message row
309
- const rows = container.querySelectorAll('.message-row.assistant');
308
+ // Find the streaming message row by counting ALL message rows (not just assistant)
309
+ // msgIdx is the global index in state.messages, so we count all rows to match
310
+ const allRows = container.querySelectorAll('.message-row');
310
311
  let targetRow = null;
311
- // Count assistant rows to find the right one
312
- let assistantCount = 0;
313
- for (const row of rows) {
314
- assistantCount++;
315
- if (assistantCount === msgIdx + 1) {
312
+ let rowCount = 0;
313
+ for (const row of allRows) {
314
+ if (rowCount === msgIdx) {
316
315
  targetRow = row;
317
316
  break;
318
317
  }
318
+ rowCount++;
319
319
  }
320
320
 
321
321
  // Fallback: if we can't find by count, use last assistant row
322
- if (!targetRow && rows.length > 0) {
323
- targetRow = rows[rows.length - 1];
322
+ if (!targetRow) {
323
+ const assistantRows = container.querySelectorAll('.message-row.assistant');
324
+ if (assistantRows.length > 0) {
325
+ targetRow = assistantRows[assistantRows.length - 1];
326
+ }
324
327
  }
325
328
 
326
329
  if (!targetRow) {
@@ -343,12 +346,32 @@ function updateStreamingMessage(msgIdx) {
343
346
  }
344
347
  }
345
348
  if (msg.reasoning) {
346
- // Count words/chars during streaming for live counter
347
349
  const reasoningLen = msg.reasoning.length;
348
350
  const reasoningWordCount = msg.streaming
349
351
  ? '<span class="thought-word-count">' + reasoningLen + ' 字</span>'
350
352
  : '';
351
- const reasoningHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
353
+ if (reasoningDetails) {
354
+ // Incremental update: only update word count and badge, append new text to content
355
+ const label = reasoningDetails.querySelector('.thought-label');
356
+ if (label) label.innerHTML = '模型推理过程' + reasoningWordCount;
357
+ const badge = reasoningDetails.querySelector('.thought-badge');
358
+ if (badge) badge.textContent = msg.streaming ? '推理中...' : '已完成';
359
+ // Incremental text append for streaming (avoid full markdown rebuild)
360
+ const thoughtContent = reasoningDetails.querySelector('.thought-content');
361
+ if (thoughtContent && msg.streaming) {
362
+ const prevLen = reasoningDetails._lastReasoningLen || 0;
363
+ if (msg.reasoning.length > prevLen) {
364
+ const newText = msg.reasoning.substring(prevLen);
365
+ thoughtContent.insertAdjacentHTML('beforeend', renderMarkdown(newText));
366
+ reasoningDetails._lastReasoningLen = msg.reasoning.length;
367
+ }
368
+ } else if (thoughtContent && !msg.streaming) {
369
+ // Final render once streaming stops
370
+ thoughtContent.innerHTML = renderMarkdown(msg.reasoning);
371
+ reasoningDetails._lastReasoningLen = msg.reasoning.length;
372
+ }
373
+ } else {
374
+ const reasoningHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
352
375
  <summary>
353
376
  <span class="thought-icon">💡</span>
354
377
  <span class="thought-label">模型推理过程${reasoningWordCount}</span>
@@ -356,10 +379,10 @@ function updateStreamingMessage(msgIdx) {
356
379
  </summary>
357
380
  <div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
358
381
  </details>`;
359
- if (reasoningDetails) {
360
- reasoningDetails.outerHTML = reasoningHtml;
361
- } else {
362
382
  contentArea.insertAdjacentHTML('afterbegin', reasoningHtml);
383
+ // Set initial length tracking
384
+ const newBlock = contentArea.querySelector(':scope > .thought-block');
385
+ if (newBlock) newBlock._lastReasoningLen = msg.reasoning.length;
363
386
  }
364
387
  }
365
388
 
@@ -378,7 +401,26 @@ function updateStreamingMessage(msgIdx) {
378
401
  const thoughtWordCount = msg.streaming
379
402
  ? '<span class="thought-word-count">' + thoughtLen + ' 字</span>'
380
403
  : '';
381
- const thoughtHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
404
+ if (thoughtBlock) {
405
+ // Incremental update for thought block too
406
+ const label = thoughtBlock.querySelector('.thought-label');
407
+ if (label) label.innerHTML = 'Agent 思考过程' + thoughtWordCount;
408
+ const badge = thoughtBlock.querySelector('.thought-badge');
409
+ if (badge) badge.textContent = msg.streaming ? '思考中...' : '已完成';
410
+ const thoughtContent = thoughtBlock.querySelector('.thought-content');
411
+ if (thoughtContent && msg.streaming) {
412
+ const prevLen = thoughtBlock._lastThoughtLen || 0;
413
+ if (msg.thought.length > prevLen) {
414
+ const newText = msg.thought.substring(prevLen);
415
+ thoughtContent.insertAdjacentHTML('beforeend', renderMarkdown(newText));
416
+ thoughtBlock._lastThoughtLen = msg.thought.length;
417
+ }
418
+ } else if (thoughtContent && !msg.streaming) {
419
+ thoughtContent.innerHTML = renderMarkdown(msg.thought);
420
+ thoughtBlock._lastThoughtLen = msg.thought.length;
421
+ }
422
+ } else {
423
+ const thoughtHtml = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
382
424
  <summary>
383
425
  <span class="thought-icon">💭</span>
384
426
  <span class="thought-label">Agent 思考过程${thoughtWordCount}</span>
@@ -386,16 +428,14 @@ function updateStreamingMessage(msgIdx) {
386
428
  </summary>
387
429
  <div class="thought-content">${renderMarkdown(msg.thought)}</div>
388
430
  </details>`;
389
- if (thoughtBlock) {
390
- thoughtBlock.outerHTML = thoughtHtml;
391
- } else {
392
- // Insert after reasoning block if exists, otherwise at beginning
393
431
  const existingReasoning = contentArea.querySelectorAll('.thought-block');
394
432
  if (existingReasoning.length > 0) {
395
433
  existingReasoning[existingReasoning.length - 1].insertAdjacentHTML('afterend', thoughtHtml);
396
434
  } else {
397
435
  contentArea.insertAdjacentHTML('afterbegin', thoughtHtml);
398
436
  }
437
+ const newBlock = contentArea.querySelectorAll('.thought-block');
438
+ if (newBlock.length > 0) newBlock[newBlock.length - 1]._lastThoughtLen = msg.thought.length;
399
439
  }
400
440
  }
401
441
 
@@ -417,7 +457,26 @@ function updateStreamingMessage(msgIdx) {
417
457
  const v2WordCount = msg.streaming
418
458
  ? '<span class="thought-word-count">' + v2Len + ' 字</span>'
419
459
  : '';
420
- const v2Html = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
460
+ if (v2ReasoningBlock) {
461
+ // Incremental update for V2 reasoning block
462
+ const label = v2ReasoningBlock.querySelector('.thought-label');
463
+ if (label) label.innerHTML = 'V2 推理过程' + v2WordCount;
464
+ const badge = v2ReasoningBlock.querySelector('.thought-badge');
465
+ if (badge) badge.textContent = msg.streaming ? '推理中...' : '已完成';
466
+ const thoughtContent = v2ReasoningBlock.querySelector('.thought-content');
467
+ if (thoughtContent && msg.streaming) {
468
+ const prevLen = v2ReasoningBlock._lastV2Len || 0;
469
+ if (msg._v2Reasoning.length > prevLen) {
470
+ const newText = msg._v2Reasoning.substring(prevLen);
471
+ thoughtContent.insertAdjacentHTML('beforeend', renderMarkdown(newText));
472
+ v2ReasoningBlock._lastV2Len = msg._v2Reasoning.length;
473
+ }
474
+ } else if (thoughtContent && !msg.streaming) {
475
+ thoughtContent.innerHTML = renderMarkdown(msg._v2Reasoning);
476
+ v2ReasoningBlock._lastV2Len = msg._v2Reasoning.length;
477
+ }
478
+ } else if (!msg.thought) {
479
+ const v2Html = `<details class="thought-block ${msg.streaming ? 'streaming' : ''}" ${msg.streaming ? 'open' : ''}>
421
480
  <summary>
422
481
  <span class="thought-icon">🧠</span>
423
482
  <span class="thought-label">V2 推理过程${v2WordCount}</span>
@@ -425,10 +484,9 @@ function updateStreamingMessage(msgIdx) {
425
484
  </summary>
426
485
  <div class="thought-content">${renderMarkdown(msg._v2Reasoning)}</div>
427
486
  </details>`;
428
- if (v2ReasoningBlock) {
429
- v2ReasoningBlock.outerHTML = v2Html;
430
- } else if (!msg.thought) {
431
487
  contentArea.insertAdjacentHTML('afterbegin', v2Html);
488
+ const newBlock = contentArea.querySelector(':scope > .thought-block');
489
+ if (newBlock) newBlock._lastV2Len = msg._v2Reasoning.length;
432
490
  }
433
491
  }
434
492
  }