fix: 全面修复内置工具问题 + 架构缺陷修复 (v0.14.8)

P0: replace_in_files glob重写, search_files正则修复, list_directory递归分页修复, git stash/tag参数修复, buildSearchResponse query字段修复; P1: compress命令注入修复, run_command输出限制, download_file UA+重试; P2: web_fetch extract_mode/mobile_ua生效, read_multiple_files默认值对齐, CONFIRM_TOOLS扩展, 工具图标/名称映射补全; P3: random死代码清理, IPC类型补全; 架构: 系统提示词重复渲染修复, 版本号动态注入, 上下文余量字段修复, 工具记录丢失修复
This commit is contained in:
紫影233
2026-07-07 11:44:48 +08:00
parent e67d638e06
commit 7c78a65a9c
14 changed files with 355 additions and 140 deletions
+28 -38
View File
@@ -7,6 +7,7 @@ import { logError, logSession } from '../services/log-service.js';
import { marked } from '../utils/marked-config.js';
import { escapeHtml, formatTime } from '../utils/utils.js';
import { estimateTokens } from '../services/context-manager.js';
import { getToolIcon, formatToolName } from '../services/tool-registry.js';
import type { ChatSession, ChatMessage, ToolCallRecord } from '../types.js';
function getFileIcon(filename: string): string {
@@ -111,6 +112,9 @@ export function resetAutoScroll(): void {
/** P1-1: 已渲染消息索引集合,用于稳定增量 diff(替代 DOM querySelectorAll 计数) */
const _renderedMsgIndices = new Set<number>();
/** 标记当前渲染批次中系统提示词卡片是否已渲染(每会话仅首条 assistant 消息展示) */
let _sysPromptRendered = false;
export function renderMessages(): void {
const currentSession = state.get<ChatSession | null>(KEYS.CURRENT_SESSION);
const msgs = currentSession ? currentSession.messages : [];
@@ -119,6 +123,7 @@ export function renderMessages(): void {
messagesContainerEl.innerHTML = '';
currentPlaceholder = null;
_renderedMsgIndices.clear();
_sysPromptRendered = false; // 重置:每轮全量渲染时重新判定首条 assistant
if (msgs.length === 0) {
emptyStateEl.style.display = '';
@@ -155,10 +160,13 @@ export function appendMessageDOM(msg: ChatMessage, index: number): void {
let safeContent = (msg.content != null) ? String(msg.content) : '';
if (msg.role === 'assistant') {
// ── 系统提示词折叠卡片(每条助理消息顶部展示)──
const sysPrompt = state.get<string>('_lastSystemPrompt', '');
if (sysPrompt) {
contentHtml += renderSystemPromptCard(sysPrompt);
// ── 系统提示词折叠卡片(仅会话首条 assistant 消息展示,避免重复)──
if (!_sysPromptRendered) {
const sysPrompt = state.get<string>('_lastSystemPrompt', '');
if (sysPrompt) {
contentHtml += renderSystemPromptCard(sysPrompt);
}
_sysPromptRendered = true;
}
if (msg.think) {
@@ -252,34 +260,12 @@ export function appendMessageDOM(msg: ChatMessage, index: number): void {
}
function renderToolCallCard(tc: ToolCallRecord): string {
const icons: Record<string, string> = {
read_file: '📄', write_file: '✏️', list_directory: '📁',
search_files: '🔍', create_directory: '📂', delete_file: '🗑️', run_command: '💻',
move_file: '📦', copy_file: '📋', web_fetch: '🌐',
edit_file: '✂️', get_file_info: '️', tree: '🌳', download_file: '⬇️',
diff_files: '🔀', replace_in_files: '🔄', read_multiple_files: '📚',
git: '🔖', compress: '🗜️', web_search: '🔍',
memory: '🧠', session_list: '📋', session_read: '📖', spawn_task: '🤖', plan_track: '📋',
browser_open: '🌐', browser_screenshot: '📸', browser_evaluate: '⚡', browser_extract: '📰',
browser_click: '👆', browser_type: '⌨️', browser_scroll: '↕️', browser_close: '❌'
};
const names: Record<string, string> = {
read_file: '读取文件', write_file: '写入文件', list_directory: '列出目录',
search_files: '搜索文件', create_directory: '创建目录', delete_file: '删除文件', run_command: '执行命令',
move_file: '移动文件', copy_file: '复制文件', web_fetch: '网页抓取',
edit_file: '编辑文件', get_file_info: '文件信息', tree: '目录树', download_file: '下载文件',
diff_files: '文件对比', replace_in_files: '批量替换', read_multiple_files: '批量读取',
git: 'Git 操作', compress: '压缩/解压', web_search: '联网搜索',
memory: '记忆管理', session_list: '会话列表', session_read: '读取会话', spawn_task: '子代理委派', plan_track: '计划追踪',
browser_open: '打开网页', browser_screenshot: '浏览器截图', browser_evaluate: '执行JS', browser_extract: '提取内容',
browser_click: '点击元素', browser_type: '输入文本', browser_scroll: '滚动页面', browser_close: '关闭浏览器'
};
const statusLabels: Record<string, string> = {
pending: '📝 准备中…', running: '🔄 执行中', success: '✅ 完成', error: '❌ 失败', cancelled: '🚫 已取消'
};
const icon = icons[tc.name] || '🔧';
const name = names[tc.name] || tc.name;
const icon = getToolIcon(tc.name);
const name = formatToolName(tc.name);
const status = statusLabels[tc.status] || tc.status;
const args = tc.arguments || {};
@@ -328,18 +314,22 @@ export function updateLastAssistantMessage(
if (loadingDots) loadingDots.remove();
if (loadingText) loadingText.remove();
// 流式消息首次转正:注入系统提示词折叠卡片
// 流式消息首次转正:注入系统提示词折叠卡片(仅当会话中无前序 assistant 消息时)
const sysPrompt = state.get<string>('_lastSystemPrompt', '');
if (sysPrompt && !lastMsg.querySelector('.system-prompt-card')) {
const msgBody = lastMsg.querySelector('.msg-body');
const tempDiv = document.createElement('div');
tempDiv.innerHTML = renderSystemPromptCard(sysPrompt);
const card = tempDiv.firstElementChild!;
const thinkBlock = msgBody?.querySelector('.think-block');
if (thinkBlock) {
msgBody!.insertBefore(card, thinkBlock);
} else {
msgBody!.insertBefore(card, msgBody!.firstChild);
const session = state.get<ChatSession | null>(KEYS.CURRENT_SESSION);
const hasPrevAssistant = session?.messages.some(m => m.role === 'assistant') ?? false;
if (!hasPrevAssistant) {
const msgBody = lastMsg.querySelector('.msg-body');
const tempDiv = document.createElement('div');
tempDiv.innerHTML = renderSystemPromptCard(sysPrompt);
const card = tempDiv.firstElementChild!;
const thinkBlock = msgBody?.querySelector('.think-block');
if (thinkBlock) {
msgBody!.insertBefore(card, thinkBlock);
} else {
msgBody!.insertBefore(card, msgBody!.firstChild);
}
}
}
}
+36 -12
View File
@@ -503,6 +503,8 @@ async function handleRetry(): Promise<void> {
let retryContent = '';
let retryThinkContent = '';
let retryIterations = 0;
// P1 修复:追踪当前迭代的工具记录(与 send 路径保持一致)
let retryIterationToolRecords: ToolCallRecord[] = [];
// 构建重试用的完整内容和 images(含文件内容和视频帧)
let retryUserContent = userMsg.content || '';
@@ -537,7 +539,9 @@ async function handleRetry(): Promise<void> {
role: 'assistant', content: retryContent || '', model: getSelectedModel(),
timestamp: now,
...(retryThinkContent && { think: retryThinkContent }),
...(retryIterationToolRecords.length > 0 && { toolCalls: [...retryIterationToolRecords] }),
};
retryIterationToolRecords = [];
state.update(KEYS.CURRENT_SESSION, (s: any) => ({
...s, messages: [...s.messages, prevMsg], updatedAt: Date.now()
}));
@@ -560,14 +564,20 @@ async function handleRetry(): Promise<void> {
name, arguments: call.function.arguments,
result, status: result.success ? 'success' : 'error', timestamp: Date.now()
});
updateMessageToolRecord(name, result.success ? 'success' : 'error', result);
retryIterationToolRecords.push({
name, arguments: call.function.arguments,
result, status: result.success ? 'success' : 'error', timestamp: Date.now()
});
},
onToolCallError: (name, error, call) => {
updateToolCard({
name, arguments: call.function.arguments,
result: { success: false, error }, status: 'error', timestamp: Date.now()
});
updateMessageToolRecord(name, 'error', { success: false, error });
retryIterationToolRecords.push({
name, arguments: call.function.arguments,
result: { success: false, error }, status: 'error', timestamp: Date.now()
});
},
onConfirmTool: async (call) => showToolConfirm(call),
onPlanReady: async (plan: string, steps: string[]) => {
@@ -580,14 +590,16 @@ async function handleRetry(): Promise<void> {
return true;
}
},
onDone: async (finalContent, toolRecords, loopStats) => {
onDone: async (finalContent, _toolRecords, loopStats) => {
retryContent = finalContent;
// P1 修复:使用本地追踪的当前迭代工具记录,而非引擎的 allToolRecords(含所有迭代,会与中间消息重复)
const finalToolRecords = retryIterationToolRecords.length > 0 ? retryIterationToolRecords : undefined;
if (retryIterations > 0) {
if (finalContent) {
const lastMsg: ChatMessage = {
role: 'assistant', content: finalContent, model: getSelectedModel(), timestamp: Date.now(),
...(retryThinkContent && { think: retryThinkContent }),
...(toolRecords?.length && { toolCalls: toolRecords }),
...(finalToolRecords?.length && { toolCalls: finalToolRecords }),
...(loopStats?.eval_count && { eval_count: loopStats.eval_count }),
...(loopStats?.prompt_eval_count && { prompt_eval_count: loopStats.prompt_eval_count }),
...(loopStats?.total_duration && { total_duration: loopStats.total_duration }),
@@ -600,7 +612,7 @@ async function handleRetry(): Promise<void> {
const assistantMsg: ChatMessage = {
role: 'assistant', content: finalContent || '', model: getSelectedModel(), timestamp: Date.now(),
...(retryThinkContent && { think: retryThinkContent }),
...(toolRecords?.length && { toolCalls: toolRecords }),
...(finalToolRecords?.length && { toolCalls: finalToolRecords }),
...(loopStats?.eval_count && { eval_count: loopStats.eval_count }),
...(loopStats?.prompt_eval_count && { prompt_eval_count: loopStats.prompt_eval_count }),
...(loopStats?.total_duration && { total_duration: loopStats.total_duration }),
@@ -1231,6 +1243,8 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
let assistantContent = '';
let thinkContent = '';
// P1 修复:追踪当前迭代的工具记录,onNewIteration 时保存到消息中
let currentIterationToolRecords: ToolCallRecord[] = [];
state.set('_currentEvalCount', 0);
// ── 监控定时器:流式输出期间持续显示工作提示 ──
@@ -1251,14 +1265,16 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
});
},
onNewIteration: (toolCalls) => {
// 保存上一轮的卡片(含工具记录,工具统一在 onDone 挂载
// 保存上一轮的卡片(含工具记录)
const prevMsg: ChatMessage = {
role: 'assistant',
content: assistantContent || '',
model: getSelectedModel(),
timestamp: Date.now(),
...(thinkContent && { think: thinkContent }),
...(currentIterationToolRecords.length > 0 && { toolCalls: [...currentIterationToolRecords] }),
};
currentIterationToolRecords = [];
state.update(KEYS.CURRENT_SESSION, (session: any) => ({
...session,
messages: [...session.messages, prevMsg],
@@ -1290,14 +1306,20 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
name, arguments: call.function.arguments,
result, status: result.success ? 'success' : 'error', timestamp: Date.now()
});
updateMessageToolRecord(name, result.success ? 'success' : 'error', result);
currentIterationToolRecords.push({
name, arguments: call.function.arguments,
result, status: result.success ? 'success' : 'error', timestamp: Date.now()
});
},
onToolCallError: (name, error, call) => {
updateToolCard({
name, arguments: call.function.arguments,
result: { success: false, error }, status: 'error', timestamp: Date.now()
});
updateMessageToolRecord(name, 'error', { success: false, error });
currentIterationToolRecords.push({
name, arguments: call.function.arguments,
result: { success: false, error }, status: 'error', timestamp: Date.now()
});
},
onConfirmTool: async (call) => {
return showToolConfirm(call);
@@ -1317,14 +1339,16 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
return true; // 加载失败时自动批准,不阻断执行
}
},
onDone: async (finalContent, toolRecords, loopStats) => {
onDone: async (finalContent, _toolRecords, loopStats) => {
// P1 修复:使用本地追踪的当前迭代工具记录,而非引擎的 allToolRecords(含所有迭代,会与中间消息重复)
const finalToolRecords = currentIterationToolRecords.length > 0 ? currentIterationToolRecords : undefined;
const assistantMsg: ChatMessage = {
role: 'assistant',
content: finalContent || '',
model: getSelectedModel(),
timestamp: Date.now(),
...(thinkContent && { think: thinkContent }),
...(toolRecords?.length && { toolCalls: toolRecords }),
...(finalToolRecords?.length && { toolCalls: finalToolRecords }),
...(loopStats?.eval_count && { eval_count: loopStats.eval_count }),
...(loopStats?.prompt_eval_count && { prompt_eval_count: loopStats.prompt_eval_count }),
...(loopStats?.total_duration && { total_duration: loopStats.total_duration }),
@@ -1348,8 +1372,8 @@ async function sendMessageWithAgentLoop(text: string, currentSession: ChatSessio
}));
}
await saveCurrentSession();
// 更新剩余上下文
if (loopStats?.prompt_eval_count) updateCtxRemain(loopStats.prompt_eval_count);
// 更新剩余上下文 — P0 修复:应使用 ctx_tokens(总上下文占用估算)而非 prompt_eval_count(仅最后一轮输入 token
if (loopStats?.ctx_tokens) updateCtxRemain(loopStats.ctx_tokens);
}
});
} catch (err) {
+1 -1
View File
@@ -28,7 +28,7 @@
<div class="header-left">
<span class="logo">🦙</span>
<span class="app-title">Metona Ollama</span>
<span class="app-version">v0.14.7</span>
<span class="app-version">v0.14.8</span>
<button class="icon-btn help-btn" id="btnHelp" title="使用帮助">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<circle cx="12" cy="12" r="10"/><path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3"/>
+9
View File
@@ -31,6 +31,7 @@ import { initLogPanel, addLog } from './services/log-service.js';
import { logInfo, logSuccess, logError, logDebug, logInit, logWarn } from './services/log-service.js';
import { initSearxngModal, closeSearxngModal, loadSearxngConfig } from './components/searxng-modal.js';
import { initHarnessHooks } from './services/hooks.js';
import { setAppVersion } from './services/agent-metrics.js';
import type { ChatSession } from './types.js';
// ─── v4.0 数据迁移:IndexedDB → SQLite ───
@@ -355,6 +356,14 @@ async function init(): Promise<void> {
// Agent Metrics 无需显式初始化(按需启动)
// Context Indexer 在 Agent INIT 状态按需构建
// ── 注入应用版本号到 Agent Metrics ──
const _bridge = window.metonaDesktop;
if (_bridge?.info) {
_bridge.info().then((info: { version?: string }) => {
if (info?.version) setAppVersion(info.version);
}).catch(() => {});
}
logInit('所有组件已就绪(含 Harness');
const savedModel = await db.getSetting('selectedModel', '');
+30 -13
View File
@@ -323,13 +323,17 @@ function getToolCacheKey(name: string, args: Record<string, unknown>): string {
}
}
/** 检测当前轮次是否存在重复工具调用 */
/** 检测当前轮次是否存在重复工具调用(同一参数的工具被调用超过 1 次) */
function isDuplicateCall(call: ToolCall, allCalls: ToolCall[]): boolean {
const callKey = getToolCacheKey(call.function.name, call.function.arguments);
return allCalls.some((prev, idx) => {
if (idx === allCalls.length - 1) return false;
return getToolCacheKey(prev.function.name, prev.function.arguments) === callKey;
});
let count = 0;
for (const prev of allCalls) {
if (getToolCacheKey(prev.function.name, prev.function.arguments) === callKey) {
count++;
if (count > 1) return true;
}
}
return false;
}
/** 格式化工具结果的通用默认路径 */
@@ -453,10 +457,10 @@ function formatToolResultForModel(toolName: string, result: ToolResult): string
if ((result as any).duplicate) {
return `⚠️ 重复添加: ${(result as any).message || '相同内容已存在'}\n✅ 记忆操作已全部完成,不要再调用 memory 工具,直接输出最终回答。`;
}
// read_all / search 结果用可读文本格式,确保模型不会"看漏"
// read_all / search 结果:包裹在 JSON 中以保持与其他工具一致的格式
if ((result as any).action === 'read_all') {
const entries = ((result as any).entries || []) as Array<{ id: string; type: string; content: string; importance: number; tags: string[] }>;
if (entries.length === 0) return '记忆为空,没有任何已保存的记忆条目。';
if (entries.length === 0) return JSON.stringify({ success: true, action: 'read_all', message: '记忆为空,没有任何已保存的记忆条目。', total: 0 });
const grouped: Record<string, typeof entries> = {};
for (const e of entries) {
const t = e.type || 'fact';
@@ -470,16 +474,16 @@ function formatToolResultForModel(toolName: string, result: ToolResult): string
lines.push(` • [${e.type}] ${e.content}(重要性:${e.importance}, 标签: ${(e.tags || []).join(', ') || '无'}`);
}
}
return lines.join('\n');
return JSON.stringify({ success: true, action: 'read_all', formatted: lines.join('\n'), total: entries.length });
}
if ((result as any).action === 'search') {
const results = ((result as any).results || []) as Array<{ id: string; type: string; content: string; importance: number; score: number }>;
if (results.length === 0) return '未找到匹配的记忆。';
if (results.length === 0) return JSON.stringify({ success: true, action: 'search', message: '未找到匹配的记忆。', total: 0 });
const lines = [`[记忆搜索结果] 共 ${results.length} 条:`];
for (const r of results) {
lines.push(` • [${r.type || 'fact'}] ${r.content}(重要性:${r.importance}, 匹配度:${(r.score || 0).toFixed(0)}`);
}
return lines.join('\n');
return JSON.stringify({ success: true, action: 'search', formatted: lines.join('\n'), total: results.length });
}
// 其他 actionadd/replace/remove)→ 保留完整 JSON,走 default 逻辑
return formatDefaultToolResult(toolName, result);
@@ -1625,11 +1629,14 @@ async function handleObserving(
}
}
// ── 通用重复调用检测:工具结果含去重信号 或 同工具连续成功 2+ 次 ──
// ── 通用重复调用检测:工具结果含去重信号 或 同工具同参数连续成功 2+ 次 ──
const lastToolContents = ctx.messages.filter(m => m.role === 'tool').slice(-3).map(m => m.content || '');
const hasDedupSignal = lastToolContents.some(c => c.includes('⚠️ 重复添加') || c.includes('已跳过') || c.includes('duplicate'));
const recentSuccess = ctx.allToolRecords.filter(r => r.status === 'success').slice(-2);
const allSameTool = recentSuccess.length >= 2 && recentSuccess.every(r => r.name === recentSuccess[0].name);
// P0 修复:仅当工具名 AND 参数完全相同时才判定为重复(避免读两个不同文件被误杀)
const allSameTool = recentSuccess.length >= 2
&& recentSuccess.every(r => r.name === recentSuccess[0].name)
&& getToolCacheKey(recentSuccess[0].name, recentSuccess[0].arguments) === getToolCacheKey(recentSuccess[1].name, recentSuccess[1].arguments);
if (hasDedupSignal || allSameTool) {
ctx.messages.push({
role: 'user',
@@ -1700,9 +1707,19 @@ async function handleReflecting(
}
// ── Plan Mode: 第一轮回复 → 检测是否为计划 → 等待用户确认 ──
if (ctx.mode === 'plan' && ctx.loopCount === 1 && ctx.toolCalls.length === 0 && ctx.content.length > 50) {
// P1 修复:移除 ctx.toolCalls.length === 0 条件 — 模型可能忽略 Plan Mode 指令直接调用工具,
// 此时应取消工具执行,转为等待用户确认计划
if (ctx.mode === 'plan' && ctx.loopCount === 1 && ctx.content.length > 50) {
const isPlanLike = /计划|步骤|step|plan|思路|方案|流程/.test(ctx.content.slice(0, 200));
if (isPlanLike && callbacks.onPlanReady) {
// 如果模型在第一轮就调用了工具,取消这些工具调用(Plan Mode 要求先规划后执行)
if (ctx.toolCalls.length > 0) {
logWarn(`Plan Mode: 模型在首轮调用了 ${ctx.toolCalls.length} 个工具,取消并转为计划确认`);
for (const call of ctx.toolCalls) {
callbacks.onToolCallError(call.function.name, 'Plan Mode: 首轮不应调用工具,请先输出计划', call);
}
ctx.toolCalls.length = 0;
}
const steps = extractPlanSteps(ctx.content);
const approved = await callbacks.onPlanReady(ctx.content, steps);
if (!approved) {
+12 -1
View File
@@ -12,6 +12,17 @@
import { logInfo, logDebug } from './log-service.js';
import type { AgentMetrics, LoopContext } from '../types.js';
// ═══════════════════════════════════════════════════════════════
// 应用版本号(由 main.ts 初始化时注入,避免硬编码)
// ═══════════════════════════════════════════════════════════════
let _appVersion = 'unknown';
/** 初始化应用版本号(应在 app 启动时调用) */
export function setAppVersion(version: string): void {
_appVersion = version;
}
// ═══════════════════════════════════════════════════════════════
// 度量采集
// ═══════════════════════════════════════════════════════════════
@@ -268,7 +279,7 @@ export function exportMetricsJSON(): string {
return JSON.stringify({
timestamp: new Date().toISOString(),
application: 'metona-ollama-desktop',
version: '0.13.8',
version: _appVersion,
metrics: {
sessions_total: metrics.totalSessions,
avg_iterations_per_task: metrics.avgIterationsPerTask,
+17 -4
View File
@@ -283,7 +283,7 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
required: ['paths'],
properties: {
paths: { type: 'array', items: { type: 'string' }, description: 'Array of file paths to read.' },
max_chars_per_file: { type: 'integer', description: 'Max chars per file. Default: 2000.' }
max_chars_per_file: { type: 'integer', description: 'Max chars per file. Default: 10000.' }
}
}
}
@@ -302,6 +302,8 @@ export const TOOL_DEFINITIONS: ToolDefinition[] = [
files: { type: 'array', items: { type: 'string' }, description: 'Files for add/diff/checkout.' },
message: { type: 'string', description: 'Commit message for commit action.' },
branch: { type: 'string', description: 'Branch name for branch/checkout/merge.' },
tag_name: { type: 'string', description: 'Tag name for tag action (creates an annotated tag if message is provided).' },
stash_sub: { type: 'string', enum: ['push', 'pop', 'apply', 'list', 'drop'], description: 'Stash subcommand. Default: push.' },
remote: { type: 'string', description: 'Remote name for push/pull/remote.' },
remote_url: { type: 'string', description: 'Remote URL for remote add.' },
count: { type: 'integer', description: 'Number of log entries. Default: 20.' },
@@ -673,7 +675,14 @@ CRITICAL: For add action, you MUST include both "type" (fact/preference/rule) an
}
];
const CONFIRM_TOOLS = ['run_command'];
// 需要用户确认的工具:写操作、删除、命令执行、压缩、浏览器操作
const CONFIRM_TOOLS = [
'run_command',
'write_file', 'create_directory', 'delete_file',
'edit_file', 'replace_in_files', 'move_file', 'copy_file',
'download_file', 'compress',
'browser_open', 'browser_click', 'browser_type', 'browser_evaluate',
];
export function needsConfirmation(toolName: string): boolean {
if (toolName === 'run_command') {
@@ -963,7 +972,9 @@ export function getToolIcon(name: string): string {
session_list: '📋', session_read: '📖', spawn_task: '🤖',
plan_track: '📋',
browser_open: '🌐', browser_screenshot: '📸', browser_evaluate: '⚡', browser_extract: '📰',
browser_click: '👆', browser_type: '⌨️', browser_scroll: '↕️', browser_close: '❌'
browser_click: '👆', browser_type: '⌨️', browser_scroll: '↕️', browser_close: '❌',
browser_wait: '⏳',
datetime: '🕐', calculator: '🔢', random: '🎲', uuid: '🔑', json_format: '📝', hash: '#️⃣'
};
return icons[name] || '🔧';
}
@@ -1039,7 +1050,9 @@ export function formatToolName(name: string): string {
session_list: '会话列表', session_read: '读取会话', spawn_task: '子代理委派',
plan_track: '计划追踪',
browser_open: '打开网页', browser_screenshot: '浏览器截图', browser_evaluate: '执行JS', browser_extract: '提取内容',
browser_click: '点击元素', browser_type: '输入文本', browser_scroll: '滚动页面', browser_close: '关闭浏览器'
browser_click: '点击元素', browser_type: '输入文本', browser_scroll: '滚动页面', browser_close: '关闭浏览器',
browser_wait: '等待',
datetime: '日期时间', calculator: '计算器', random: '随机数', uuid: '生成UUID', json_format: 'JSON格式化', hash: '哈希计算'
};
return names[name] || name;
}