feat(frontend): 子智能体结果添加统计信息并规范 meta/content 分区

- create_sub_agent 增强显示增加工作时间、调用次数、工具次数
- 将统计信息与最终回复放入 tool-result-content(结果区)
- 工具参数保留在 tool-result-meta(参数区)
- 在 AGENTS.md 新增 §5.6 工具结果显示规范
This commit is contained in:
JOJO 2026-06-25 12:38:58 +08:00
parent 38ff51416f
commit ed9bbeecb7
3 changed files with 109 additions and 11 deletions

View File

@ -1,6 +1,6 @@
# Repository Guidelines (Code-Verified)
> Last verified against current codebase: 2026-06-21
> Last verified against current codebase: 2026-06-25
> Scope: `/Users/jojo/Desktop/agents/正在修复中/agents`
这份文档基于当前仓库实际代码重写。若与未来代码冲突,以代码为准并及时更新本文档。
@ -183,6 +183,14 @@
8. **窗口设最大尺寸 + 内部滚动**:所有弹窗 / 面板 / 列表设置 `max-height` / `max-width`,超出由内部容器滚动,不顶大整个窗口;滚动条要么隐藏,要么做样式适配,不暴露原生粗滚动条。
9. **实体面板禁止半透明**:实体 UI 容器(对话区 / 侧栏 / 下拉/二级菜单 / 抽屉 / 对话框本体 / 状态条 / tooltip / 输入栏)背景必须不透明,且不加 `backdrop-filter` 磨砂。唯一例外:遮罩层 scrim`--overlay-scrim``position:fixed;inset:0`)和刻意玻璃质感装饰保留半透明。
## 5.6) 工具结果显示规范(强制)
> 适用范围:所有使用 `tool-result-meta` / `tool-result-content` 结构渲染的工具结果(如 `static/src/components/chat/actions/ToolAction.vue`、`toolRenderers.ts` 等)。
> 约束级别:新增或修改工具结果渲染时**必须遵守**。
1. **meta 部分只放参数**`tool-result-meta` 仅用于展示工具调用的参数、配置、状态标识等元信息。例如状态、ID、路径、任务描述、超时时间等。禁止在 meta 区域放置执行结果、统计摘要、输出内容等。
2. **content 部分只放结果**`tool-result-content` 仅用于展示工具执行后的结果、输出、统计、总结等。例如:文件内容、命令输出、搜索命中、执行统计(工作时间 / 调用次数 / 工具次数)、最终回复摘要等。禁止在 content 区域重复展示工具参数。
## 6) Git 工作流(开发 + Review
### 6.1 核心原则

View File

@ -89,6 +89,22 @@ function formatBytes(bytes: number): string {
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
function formatDuration(seconds: number): string {
if (seconds <= 0) return '0 秒';
if (seconds < 60) return `${seconds}`;
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (minutes < 60) {
return remainingSeconds > 0 ? `${minutes}${remainingSeconds}` : `${minutes}`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (remainingMinutes === 0 && remainingSeconds === 0) {
return `${hours} 小时`;
}
return `${hours} 小时 ${remainingMinutes}${remainingSeconds}`;
}
function formatToolStatusLabel(result: any, successLabel: string, failedLabel = '✗ 失败'): string {
const state = String(result?.status || props.action?.tool?.status || '').toLowerCase();
if (state === 'awaiting_user_answer') {
@ -1099,6 +1115,18 @@ function renderCreateSubAgent(result: any, args: any): string {
const taskDescription = args.task ?? '';
const message = result.message ?? result.summary ?? '';
const stats = result.stats || {};
const runtimeSeconds = result.runtime_seconds ?? result.elapsed_seconds ?? stats.runtime_seconds ?? 0;
const apiCalls = stats.api_calls ?? stats.turn_count ?? 0;
const toolCount =
(stats.files_read || 0) +
(stats.write_files || 0) +
(stats.edit_files || 0) +
(stats.searches || 0) +
(stats.web_pages || 0) +
(stats.commands || 0);
const hasStats = runtimeSeconds > 0 || apiCalls > 0 || toolCount > 0;
let html = '<div class="tool-result-meta">';
html += `<div><strong>状态:</strong>${status}</div>`;
if (agentId !== '') {
@ -1117,9 +1145,26 @@ function renderCreateSubAgent(result: any, args: any): string {
}
html += '</div>';
if (message) {
html += '<div class="tool-result-content">';
html += `<div>${escapeHtml(String(message))}</div>`;
if (message || hasStats) {
html += '<div class="tool-result-content scrollable">';
if (hasStats) {
html += '<div class="content-label">执行统计</div>';
if (runtimeSeconds > 0) {
html += `<div><strong>工作时间:</strong>${formatDuration(runtimeSeconds)}</div>`;
}
if (apiCalls > 0) {
html += `<div><strong>调用次数:</strong>${apiCalls} 次</div>`;
}
if (toolCount > 0) {
html += `<div><strong>工具次数:</strong>${toolCount} 次</div>`;
}
}
if (message) {
if (hasStats) {
html += '<div class="content-label">最终回复</div>';
}
html += `<div>${escapeHtml(String(message))}</div>`;
}
html += '</div>';
}
@ -1143,7 +1188,7 @@ function renderTerminateSubAgent(result: any, args: any): string {
html += '</div>';
if (message) {
html += '<div class="tool-result-content">';
html += '<div class="tool-result-content scrollable">';
html += `<div>${escapeHtml(String(message))}</div>`;
html += '</div>';
}
@ -1196,7 +1241,7 @@ function renderGetSubAgentStatus(result: any, args: any): string {
html += '</div>';
if (summary) {
html += '<div class="tool-result-content">';
html += '<div class="tool-result-content scrollable">';
html += `<div>${escapeHtml(String(summary))}</div>`;
html += '</div>';
}

View File

@ -14,6 +14,22 @@ export function formatBytes(bytes: number): string {
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + ' ' + sizes[i];
}
function formatDuration(seconds: number): string {
if (seconds <= 0) return '0 秒';
if (seconds < 60) return `${seconds}`;
const minutes = Math.floor(seconds / 60);
const remainingSeconds = seconds % 60;
if (minutes < 60) {
return remainingSeconds > 0 ? `${minutes}${remainingSeconds}` : `${minutes}`;
}
const hours = Math.floor(minutes / 60);
const remainingMinutes = minutes % 60;
if (remainingMinutes === 0 && remainingSeconds === 0) {
return `${hours} 小时`;
}
return `${hours} 小时 ${remainingMinutes}${remainingSeconds}`;
}
function formatToolStatusLabel(result: any, successLabel: string, failedLabel = '✗ 失败'): string {
const state = String(result?.status || '').toLowerCase();
if (state === 'awaiting_user_answer') {
@ -1067,6 +1083,18 @@ function renderCreateSubAgent(result: any, args: any): string {
const taskDescription = args.task ?? '';
const message = result.message ?? result.summary ?? '';
const stats = result.stats || {};
const runtimeSeconds = result.runtime_seconds ?? result.elapsed_seconds ?? stats.runtime_seconds ?? 0;
const apiCalls = stats.api_calls ?? stats.turn_count ?? 0;
const toolCount =
(stats.files_read || 0) +
(stats.write_files || 0) +
(stats.edit_files || 0) +
(stats.searches || 0) +
(stats.web_pages || 0) +
(stats.commands || 0);
const hasStats = runtimeSeconds > 0 || apiCalls > 0 || toolCount > 0;
let html = '<div class="tool-result-meta">';
html += `<div><strong>状态:</strong>${status}</div>`;
if (agentId !== '') {
@ -1085,9 +1113,26 @@ function renderCreateSubAgent(result: any, args: any): string {
}
html += '</div>';
if (message) {
html += '<div class="tool-result-content">';
html += `<div>${escapeHtml(String(message))}</div>`;
if (message || hasStats) {
html += '<div class="tool-result-content scrollable">';
if (hasStats) {
html += '<div class="content-label">执行统计</div>';
if (runtimeSeconds > 0) {
html += `<div><strong>工作时间:</strong>${formatDuration(runtimeSeconds)}</div>`;
}
if (apiCalls > 0) {
html += `<div><strong>调用次数:</strong>${apiCalls} 次</div>`;
}
if (toolCount > 0) {
html += `<div><strong>工具次数:</strong>${toolCount} 次</div>`;
}
}
if (message) {
if (hasStats) {
html += '<div class="content-label">最终回复</div>';
}
html += `<div>${escapeHtml(String(message))}</div>`;
}
html += '</div>';
}
@ -1111,7 +1156,7 @@ function renderTerminateSubAgent(result: any, args: any): string {
html += '</div>';
if (message) {
html += '<div class="tool-result-content">';
html += '<div class="tool-result-content scrollable">';
html += `<div>${escapeHtml(String(message))}</div>`;
html += '</div>';
}
@ -1164,7 +1209,7 @@ function renderGetSubAgentStatus(result: any, args: any): string {
html += '</div>';
if (summary) {
html += '<div class="tool-result-content">';
html += '<div class="tool-result-content scrollable">';
html += `<div>${escapeHtml(String(summary))}</div>`;
html += '</div>';
}