trace: 标注被截断的链路消息,避免误读为模型未写完

2026-10-29 实弹:468 字 final_answer 在链路里只剩前 300 字、
断在 fu 后,控制台照原样当助手回复展示,用户误以为模型
没写完。截断本身是设计(防链路文件写爆),缺的是标记。

- host.py _summarize_trace_item:agentMessage 超 300 字时
  条目带 truncated=True,短消息不带,完整回复仍在任务
  result_text 里,不在链路条目里。
- 控制台:TraceEntry 加 truncated 字段,mapTrace 与
  traceLineText 在被截断的消息后标注“链路摘要已截断至
  300 字,完整回复见右侧任务结果”,console.html 为构建产物同步。
- 单测:tests/test_host_trace.py 新增截断标记用例(468 字
  截 300 字且带标记、短消息不带标记)。

验证:host 10 项、cloud test_task_trace 14 项全过;
React tsc+vite 构建通过,产物 322151 字节、含截断标注、
含 #150 的 total_tokens/首字/总耗时,无回退。
This commit is contained in:
host committed 2026-10-30 01:01:50 +08:00
1 parent fd9fe5f254
commit ce85043a24
6 files changed
+36 -7

No files matched your search

+1 -1
View File
@@ -117,7 +117,7 @@ export function traceLineText(e: TraceEntry): string {
let body = "";
if (e.kind === "exec") body = (e.command || "") + "(exit=" + (e.exitCode ?? "") + "," + (e.durationMs ?? "") + "ms,cwd=" + (e.cwd || "") + ")";
else if (e.kind === "file") body = (e.path || "") + " " + (e.change || "") + " " + (e.status || "");
else if (e.kind === "message") body = (e.phase ? "[" + e.phase + "] " : "") + (e.text || "");
else if (e.kind === "message") body = (e.phase ? "[" + e.phase + "] " : "") + (e.text || "") + (e.truncated ? "(链路摘要已截断至 300 字,完整回复见右侧任务结果)" : "");
else if (e.kind === "reasoning") body = e.summary || "";
else if (e.kind === "tool") body = (e.server || "") + "/" + (e.tool || "") + " " + (e.status || "");
else if (e.kind === "call") body = e.name || "";
+1 -1
View File
@@ -40,7 +40,7 @@ export interface Automation { id: string; name?: string; status?: string; due?:
export interface Member { user_id: string; username?: string; role?: string }
export interface SkillItem { name?: string; id?: string; description?: string; detail?: string }
export interface McpServer { name?: string; id?: string; status?: string; detail?: string; details?: string; state?: string }
export interface TraceEntry { id?: string; kind?: string; command?: string; exitCode?: number | string; durationMs?: number | string; cwd?: string; path?: string; change?: string; status?: string; phase?: string; text?: string; summary?: string; server?: string; tool?: string; name?: string; prompt?: string }
export interface TraceEntry { id?: string; kind?: string; command?: string; exitCode?: number | string; durationMs?: number | string; cwd?: string; path?: string; change?: string; status?: string; phase?: string; text?: string; summary?: string; server?: string; tool?: string; name?: string; prompt?: string; truncated?: boolean }
export interface Agent { kind?: string; name?: string; status?: string; prompt?: string; updated_ts?: number; calls?: TraceEntry[] }
export interface ThreadInfo { id?: string; name?: string; thread_name?: string; archived?: boolean | null; cwd?: string; turns?: number; turn_count?: number; turns_count?: number }
export interface Triage {
@@ -13,7 +13,12 @@ export interface ChatMessage { id: string; role: ChatRole; text: string; label?:
export function traceEntryToChatMessage(e: TraceEntry, i: number): ChatMessage {
const id = e.id || "trace-" + i;
if (e.kind === "message") {
return { id, role: "assistant", text: (e.phase ? "[" + e.phase + "] " : "") + (e.text || ""), label: "助手" };
// 链路消息是 host 侧截断后的摘要(消息截 300 字),不是模型
// 的完整回复:被截断时必须标注,否则半个词结尾会被误读
// 成模型没写完(2026-10-29 实弹:468 字 final_answer 断在 fu)。
// 完整回复在任务的 result_text(右侧详情),不在链路条目里。
const text = (e.phase ? "[" + e.phase + "] " : "") + (e.text || "");
return { id, role: "assistant", text: e.truncated ? text + "\n(链路摘要已截断至 300 字,完整回复见右侧任务结果)" : text, label: "助手" };
}
if (e.kind === "exec" || e.kind === "file" || e.kind === "tool" || e.kind === "call") {
const label = e.kind === "exec" ? "命令" : e.kind === "file" ? "文件" : e.kind === "tool" ? "工具" : "调用";