myagent-ai 1.10.9 → 1.11.1

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.10.9",
3
+ "version": "1.11.1",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -482,6 +482,11 @@ input,textarea,select{font:inherit}
482
482
  @keyframes badgePulse{0%,100%{opacity:1}50%{opacity:.6}}
483
483
  .thought-block:not(.streaming) summary .thought-badge{background:var(--bg4);color:var(--text3);animation:none}
484
484
  .thought-content{width:100%;padding:10px 14px 14px;font-size:13px;line-height:1.7;color:var(--text2);border-top:1px solid var(--border-light);max-height:300px;overflow-y:auto;overflow-x:hidden;word-break:break-word;overflow-wrap:break-word}
485
+ /* Reasoning (模型推理过程): limit to ~5 lines, scrollable */
486
+ .thought-block .thought-content.reasoning-content{max-height:calc(1.7em * 5 + 28px);min-height:0}
487
+ .thought-block .thought-content.reasoning-content::-webkit-scrollbar{width:4px}
488
+ .thought-block .thought-content.reasoning-content::-webkit-scrollbar-thumb{background:var(--bg4);border-radius:2px}
489
+ .thought-block .thought-content.reasoning-content::-webkit-scrollbar-track{background:transparent}
485
490
  .thought-content p{margin:4px 0}
486
491
  .thought-content p:first-child{margin-top:0}
487
492
  .thought-content p:last-child{margin-bottom:0}
@@ -1600,6 +1600,7 @@ async function clearSessionFromMenu(id) {
1600
1600
  }
1601
1601
 
1602
1602
  function formatSessionName(id) {
1603
+ if (!id) return '新会话';
1603
1604
  if (id.startsWith('web_')) return id.replace('web_', '').replace(/_/g, ' ');
1604
1605
  if (id.startsWith('cli_')) return 'CLI: ' + id.replace('cli_', '');
1605
1606
  // Strip agent prefix if present (e.g., "default_web_2024..." -> "web_2024...")
@@ -2001,108 +2002,112 @@ async function clearCurrentChat() {
2001
2002
  }
2002
2003
 
2003
2004
  // ── Group History Messages ──
2004
- // Groups consecutive non-user messages (assistant + tool) into single assistant messages
2005
- // with parts[] for timeline rendering, matching the streaming display format.
2006
- // This creates: user → [assistant (speak → tool → speak → tool)] → user → ...
2005
+ // Groups consecutive non-user messages (assistant + tool_call + tool_result) into single
2006
+ // assistant messages with parts[] for timeline rendering, matching the streaming display.
2007
+ //
2008
+ // DB storage pattern:
2009
+ // user_input → role="user", key="user_input"
2010
+ // text → role="assistant", key=""
2011
+ // tool_call → role="assistant", key="tool_call" (NOT role="tool"!)
2012
+ // tool_result→ role="tool", key="tool_result"
2013
+ //
2014
+ // Result: user → [assistant { text → tool_call → tool_result → text → ... }] → user
2007
2015
  function groupHistoryMessages(messages) {
2008
2016
  if (!Array.isArray(messages) || messages.length === 0) return messages;
2009
2017
 
2010
2018
  const grouped = [];
2011
2019
  let i = 0;
2020
+ let _evtId = 0; // Event ID counter
2012
2021
 
2013
2022
  while (i < messages.length) {
2014
2023
  const msg = messages[i];
2015
2024
 
2016
2025
  if (msg.role === 'user') {
2017
- // User message: pass through as-is
2018
2026
  grouped.push({ role: 'user', content: msg.content, time: msg.time || '' });
2019
2027
  i++;
2020
2028
  } else if (msg.role === 'assistant') {
2021
- // Start of a new agent group: collect all consecutive non-user messages
2029
+ // Start of a new agent group: collect ALL consecutive non-user messages
2022
2030
  const parts = [];
2023
2031
  let lastAssistantTime = msg.time || '';
2024
2032
 
2025
- // If assistant has content, add as text part
2026
- if (msg.content && msg.content.trim() && msg.content !== '(无回复)') {
2033
+ // Process the first assistant message
2034
+ if (msg.key === 'tool_call') {
2035
+ const toolName = (msg.content.match(/^调用工具:\s*(\S+)/) || [])[1] || '';
2036
+ parts.push({
2037
+ type: 'exec',
2038
+ data: {
2039
+ id: _evtId++,
2040
+ type: 'tool_call',
2041
+ title: toolName ? ('调用工具: ' + toolName) : msg.content.substring(0, 100),
2042
+ tool_name: toolName,
2043
+ status: 'done',
2044
+ }
2045
+ });
2046
+ } else if (msg.content && msg.content.trim() && msg.content !== '(无回复)') {
2027
2047
  parts.push({ type: 'text', content: msg.content });
2028
2048
  }
2029
2049
 
2030
- i++; // Move to next message
2050
+ i++;
2031
2051
 
2032
- // Collect following tool messages
2033
- while (i < messages.length && messages[i].role === 'tool') {
2034
- const toolMsg = messages[i];
2035
- const isResult = toolMsg.key === 'tool_result';
2036
- const isCall = toolMsg.key === 'tool_call';
2052
+ // Collect all following tool_call (role=assistant), tool_result (role=tool),
2053
+ // and assistant text messages into the same group.
2054
+ // This handles: text → tool_call → tool_result → text → tool_call → tool_result → text
2055
+ while (i < messages.length) {
2056
+ const next = messages[i];
2037
2057
 
2038
- if (isCall) {
2039
- // Extract tool name from content
2040
- const toolName = (toolMsg.content.match(/^调用工具:\s*(\S+)/) || [])[1] || '';
2058
+ if (next.role === 'tool') {
2059
+ const isOk = !next.content.includes('失败');
2041
2060
  parts.push({
2042
2061
  type: 'exec',
2043
2062
  data: {
2044
- id: 'hist_tool_' + i,
2045
- type: 'tool_call',
2046
- title: toolMsg.content.substring(0, 100) || ('调用工具: ' + toolName),
2047
- tool_name: toolName,
2048
- status: 'done',
2049
- }
2050
- });
2051
- } else if (isResult) {
2052
- // Determine success/failure from content
2053
- const isOk = !toolMsg.content.includes('失败');
2054
- parts.push({
2055
- type: 'exec',
2056
- data: {
2057
- id: 'hist_tool_' + i,
2063
+ id: _evtId++,
2058
2064
  type: 'tool_result',
2059
- title: (toolMsg.content.substring(0, 80) || '工具执行结果'),
2065
+ title: next.content.substring(0, 80) || '工具执行结果',
2060
2066
  success: isOk,
2061
- summary: toolMsg.content.substring(0, 500),
2067
+ summary: next.content.substring(0, 500),
2068
+ result: { output: next.content.substring(0, 2000) },
2062
2069
  }
2063
2070
  });
2064
- } else {
2065
- // Generic tool message
2071
+ i++;
2072
+ } else if (next.role === 'assistant' && next.key === 'tool_call') {
2073
+ const toolName = (next.content.match(/^调用工具:\s*(\S+)/) || [])[1] || '';
2066
2074
  parts.push({
2067
2075
  type: 'exec',
2068
2076
  data: {
2069
- id: 'hist_tool_' + i,
2077
+ id: _evtId++,
2070
2078
  type: 'tool_call',
2071
- title: toolMsg.content.substring(0, 100) || '工具调用',
2079
+ title: toolName ? ('调用工具: ' + toolName) : next.content.substring(0, 100),
2080
+ tool_name: toolName,
2072
2081
  status: 'done',
2073
2082
  }
2074
2083
  });
2075
- }
2076
-
2077
- i++;
2078
-
2079
- // If next message is an assistant message, add its content as a text part and continue
2080
- // This handles the pattern: text → tool → text → tool
2081
- if (i < messages.length && messages[i].role === 'assistant') {
2082
- const nextAssistant = messages[i];
2083
- if (nextAssistant.content && nextAssistant.content.trim() && nextAssistant.content !== '(无回复)') {
2084
- parts.push({ type: 'text', content: nextAssistant.content });
2085
- lastAssistantTime = nextAssistant.time || lastAssistantTime;
2084
+ lastAssistantTime = next.time || lastAssistantTime;
2085
+ i++;
2086
+ } else if (next.role === 'assistant') {
2087
+ if (next.content && next.content.trim() && next.content !== '(无回复)') {
2088
+ parts.push({ type: 'text', content: next.content });
2086
2089
  }
2090
+ lastAssistantTime = next.time || lastAssistantTime;
2087
2091
  i++;
2092
+ } else {
2093
+ break;
2088
2094
  }
2089
2095
  }
2090
2096
 
2091
- // Create grouped assistant message with parts
2092
- // Assemble content from text parts for backward compat
2093
2097
  const textParts = parts.filter(p => p.type === 'text');
2094
- const assembledContent = textParts.map(p => p.content).join('\n\n');
2098
+ const assembledContent = textParts.length > 0
2099
+ ? textParts[textParts.length - 1].content
2100
+ : '';
2095
2101
 
2096
2102
  grouped.push({
2097
2103
  role: 'assistant',
2098
- content: assembledContent || '',
2104
+ content: assembledContent,
2099
2105
  time: lastAssistantTime,
2100
2106
  parts: parts.length > 0 ? parts : undefined,
2101
- // Also collect exec_events for backward compat display
2102
2107
  exec_events: parts.filter(p => p.type === 'exec').map(p => p.data),
2103
2108
  });
2104
2109
  } else if (msg.role === 'tool') {
2105
- // Orphan tool message (no preceding assistant) — wrap in an assistant group
2110
+ // Orphan tool message — wrap in an assistant group
2106
2111
  const parts = [];
2107
2112
  const isResult = msg.key === 'tool_result';
2108
2113
  const isCall = msg.key === 'tool_call';
@@ -2112,9 +2117,9 @@ function groupHistoryMessages(messages) {
2112
2117
  parts.push({
2113
2118
  type: 'exec',
2114
2119
  data: {
2115
- id: 'hist_tool_' + i,
2120
+ id: _evtId++,
2116
2121
  type: 'tool_call',
2117
- title: msg.content.substring(0, 100) || ('调用工具: ' + toolName),
2122
+ title: toolName ? ('调用工具: ' + toolName) : msg.content.substring(0, 100),
2118
2123
  tool_name: toolName,
2119
2124
  status: 'done',
2120
2125
  }
@@ -2124,34 +2129,65 @@ function groupHistoryMessages(messages) {
2124
2129
  parts.push({
2125
2130
  type: 'exec',
2126
2131
  data: {
2127
- id: 'hist_tool_' + i,
2132
+ id: _evtId++,
2128
2133
  type: 'tool_result',
2129
2134
  title: msg.content.substring(0, 80) || '工具执行结果',
2130
2135
  success: isOk,
2131
2136
  summary: msg.content.substring(0, 500),
2137
+ result: { output: msg.content.substring(0, 2000) },
2132
2138
  }
2133
2139
  });
2134
2140
  }
2135
2141
 
2136
2142
  i++;
2137
- // Check if next is assistant (to include its content)
2138
- if (i < messages.length && messages[i].role === 'assistant') {
2139
- const nextAssistant = messages[i];
2140
- if (nextAssistant.content && nextAssistant.content.trim() && nextAssistant.content !== '(无回复)') {
2141
- parts.push({ type: 'text', content: nextAssistant.content });
2143
+ while (i < messages.length) {
2144
+ const next = messages[i];
2145
+ if (next.role === 'assistant' && next.key === 'tool_call') {
2146
+ const toolName = (next.content.match(/^调用工具:\s*(\S+)/) || [])[1] || '';
2147
+ parts.push({
2148
+ type: 'exec',
2149
+ data: {
2150
+ id: _evtId++,
2151
+ type: 'tool_call',
2152
+ title: toolName ? ('调用工具: ' + toolName) : next.content.substring(0, 100),
2153
+ tool_name: toolName,
2154
+ status: 'done',
2155
+ }
2156
+ });
2157
+ i++;
2158
+ } else if (next.role === 'assistant') {
2159
+ if (next.content && next.content.trim() && next.content !== '(无回复)') {
2160
+ parts.push({ type: 'text', content: next.content });
2161
+ }
2162
+ i++;
2163
+ } else if (next.role === 'tool') {
2164
+ const isOk = !next.content.includes('失败');
2165
+ parts.push({
2166
+ type: 'exec',
2167
+ data: {
2168
+ id: _evtId++,
2169
+ type: 'tool_result',
2170
+ title: next.content.substring(0, 80) || '工具执行结果',
2171
+ success: isOk,
2172
+ summary: next.content.substring(0, 500),
2173
+ result: { output: next.content.substring(0, 2000) },
2174
+ }
2175
+ });
2176
+ i++;
2177
+ } else {
2178
+ break;
2142
2179
  }
2143
- i++;
2144
2180
  }
2145
2181
 
2182
+ const textParts = parts.filter(p => p.type === 'text');
2146
2183
  grouped.push({
2147
2184
  role: 'assistant',
2148
- content: parts.filter(p => p.type === 'text').map(p => p.content).join('\n\n'),
2185
+ content: textParts.length > 0 ? textParts[textParts.length - 1].content : '',
2149
2186
  time: msg.time || '',
2150
2187
  parts: parts.length > 0 ? parts : undefined,
2151
2188
  exec_events: parts.filter(p => p.type === 'exec').map(p => p.data),
2152
2189
  });
2153
2190
  } else {
2154
- // Skip unknown roles
2155
2191
  i++;
2156
2192
  }
2157
2193
  }
@@ -2261,7 +2297,7 @@ function _renderMessagesInner() {
2261
2297
  <span class="thought-label">模型推理过程</span>
2262
2298
  ${isStreaming ? '<span class="thought-badge">推理中...</span>' : '<span class="thought-badge">已完成</span>'}
2263
2299
  </summary>
2264
- <div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
2300
+ <div class="thought-content reasoning-content">${renderMarkdown(msg.reasoning)}</div>
2265
2301
  </details>`;
2266
2302
  })() : '';
2267
2303
  const actionBtns = (!isUser && msg.content) ? `
@@ -2323,7 +2359,7 @@ function _renderMessagesInner() {
2323
2359
  const execEventsHtml = (!isUser && !hasParts && msg.exec_events && msg.exec_events.length > 0)
2324
2360
  ? renderExecEvents(msg.exec_events, i) : '';
2325
2361
  html += `
2326
- <div class="message-row ${msg.role}">
2362
+ <div class="message-row ${msg.role}${msg.streaming ? ' streaming' : ''}">
2327
2363
  <div class="message-avatar">${avatar}</div>
2328
2364
  <div class="message-content" style="flex:1;min-width:0">
2329
2365
  ${reasoningHtml}
@@ -2441,6 +2477,21 @@ function formatTime(timeStr) {
2441
2477
  function scrollToBottom(force) {
2442
2478
  const c = document.getElementById('messagesContainer');
2443
2479
  if (!c) return;
2480
+ // During streaming: pin the active assistant message to the top of the chat window
2481
+ // so the user can see the full response content below
2482
+ const isStreaming = state.isGenerating;
2483
+ if (isStreaming && !force) {
2484
+ const activeRow = c.querySelector('.message-row.assistant.streaming, .message-row.assistant:last-of-type');
2485
+ if (activeRow) {
2486
+ requestAnimationFrame(() => {
2487
+ const rowTop = activeRow.offsetTop;
2488
+ // Scroll so the assistant row sits at the very top of the visible area
2489
+ c.scrollTop = rowTop;
2490
+ updateScrollToBottomBtn(c.scrollHeight - c.scrollTop - c.clientHeight);
2491
+ });
2492
+ return;
2493
+ }
2494
+ }
2444
2495
  requestAnimationFrame(() => {
2445
2496
  // Smart scroll: only auto-scroll if user is near bottom (within 120px)
2446
2497
  // or if force is true
@@ -377,7 +377,7 @@ function updateStreamingMessage(msgIdx) {
377
377
  <span class="thought-label">模型推理过程${reasoningWordCount}</span>
378
378
  ${msg.streaming ? '<span class="thought-badge">推理中...</span>' : '<span class="thought-badge">已完成</span>'}
379
379
  </summary>
380
- <div class="thought-content">${renderMarkdown(msg.reasoning)}</div>
380
+ <div class="thought-content reasoning-content">${renderMarkdown(msg.reasoning)}</div>
381
381
  </details>`;
382
382
  contentArea.insertAdjacentHTML('afterbegin', reasoningHtml);
383
383
  // Set initial length tracking
@@ -1438,25 +1438,29 @@ async function sendMessage() {
1438
1438
  // evt.result contains: {success, output, error, ...}
1439
1439
  // 记录到调试控制台
1440
1440
  if (window.addDebugLog) {
1441
- window.addDebugLog(evt.result && evt.result.success ? 'tool' : 'error',
1442
- (evt.tool.toolname || '工具') + ' 执行' + (evt.result && evt.result.success ? '成功' : '失败'), {
1443
- tool: evt.tool.toolname,
1444
- success: evt.result && evt.result.success,
1445
- error: evt.result && evt.result.error,
1446
- result: evt.result && (evt.result.output || evt.result.error)
1441
+ var _toolResult = evt.result || {};
1442
+ var _toolInfo = evt.tool || {};
1443
+ window.addDebugLog(_toolResult.success ? 'tool' : 'error',
1444
+ (_toolInfo.toolname || '工具') + ' 执行' + (_toolResult.success ? '成功' : '失败'), {
1445
+ tool: _toolInfo.toolname,
1446
+ success: _toolResult.success,
1447
+ error: _toolResult.error,
1448
+ result: _toolResult.output || _toolResult.error
1447
1449
  });
1448
1450
  }
1451
+ var _r = evt.result || {};
1452
+ var _t = evt.tool || {};
1449
1453
  var resultEvent = {
1450
1454
  type: 'v2_tool',
1451
1455
  data: {
1452
1456
  id: 'v2tool_' + Date.now() + '_' + allExecEvents.length,
1453
1457
  type: 'tool_result',
1454
- title: (evt.tool.toolname || '工具') + ' 执行完成',
1455
- tool_name: evt.tool.toolname,
1456
- success: evt.result.success,
1457
- summary: (evt.result.output || evt.result.error || '').substring(0, 500),
1458
- result: evt.result,
1459
- callback: evt.tool.callback
1458
+ title: (_t.toolname || '工具') + ' 执行完成',
1459
+ tool_name: _t.toolname,
1460
+ success: !!_r.success,
1461
+ summary: (_r.output || _r.error || '').substring(0, 500),
1462
+ result: _r,
1463
+ callback: _t.callback
1460
1464
  }
1461
1465
  };
1462
1466
  msgParts.push(resultEvent);