From 8ea1f018fe8ff823aa734b658a0c74f0d99d4e5c Mon Sep 17 00:00:00 2001 From: kdletters <61648117+kdletters@users.noreply.github.com> Date: Mon, 5 Oct 2026 18:54:29 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=9D=E7=95=99=E5=89=8D=E7=AB=AF=E7=BB=93?= =?UTF-8?q?=E6=9E=84=E5=8C=96=E9=94=99=E8=AF=AF=E6=AD=A3=E6=96=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 避免对象错误退化为 object Object 补充前端错误出口回归测试 --- .../src/services/errorReporting.ts | 26 ++++++++++++++++--- .../tests/errorReporting.test.ts | 14 ++++++++++ ...GC命令错误结构化与错误报告口径-2026-10-01.md | 1 + ...€�里程碑】AGC错误具体文本展示-2026-10-04.md | 1 + 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/apps/ai-game-creator-shell/src/services/errorReporting.ts b/apps/ai-game-creator-shell/src/services/errorReporting.ts index 7e7368b66..37ac949be 100644 --- a/apps/ai-game-creator-shell/src/services/errorReporting.ts +++ b/apps/ai-game-creator-shell/src/services/errorReporting.ts @@ -76,12 +76,32 @@ export function normalizeDiagnosticText(value: string) { .replace(/\b[0-9a-f]{8,}\b/giu, ''); } +function clientErrorText(error: unknown): string { + if (error instanceof Error) return error.message; + if (typeof error === 'string') return error; + if (error && typeof error === 'object') { + const record = error as Record; + const fields = ['message', 'detail', 'reason', 'code'] + .map((field) => record[field]) + .filter((value): value is string => typeof value === 'string') + .map((value) => value.trim()) + .filter(Boolean); + if (fields.length > 0) return fields.join(';'); + try { + return JSON.stringify(error); + } catch { + return ''; + } + } + return String(error ?? ''); +} + /** 用户可见的普通客户端错误正文:保留状态码/原因,替换凭据、URL、路径和长标识。 */ export function visibleClientErrorMessage( error: unknown, fallback = '未提供具体错误正文', ) { - const raw = error instanceof Error ? error.message : String(error ?? ''); + const raw = clientErrorText(error); const safe = normalizeDiagnosticText(raw.replace(/\s+/gu, ' ').trim()).slice( 0, 400, @@ -104,10 +124,10 @@ export async function captureClientError( } else if (error instanceof Error) { errorValue = error; } else { - errorValue = new Error(String(error)); + errorValue = new Error(clientErrorText(error)); } const message = - normalizeDiagnosticText(errorValue.message || '').slice(0, 400) || + normalizeDiagnosticText(clientErrorText(errorValue) || '').slice(0, 400) || '未知客户端错误'; const stack = errorValue.stack ? normalizeDiagnosticText(errorValue.stack).slice(0, 8_000) diff --git a/apps/ai-game-creator-shell/tests/errorReporting.test.ts b/apps/ai-game-creator-shell/tests/errorReporting.test.ts index 707d99f9a..833f1cd34 100644 --- a/apps/ai-game-creator-shell/tests/errorReporting.test.ts +++ b/apps/ai-game-creator-shell/tests/errorReporting.test.ts @@ -83,6 +83,7 @@ import { resetClientErrorEventsForTests, submitErrorReportBatch, subscribeClientErrorEvents, + visibleClientErrorMessage, } from '../src/services/errorReporting'; import type { ClientAuthError } from '../src/services/generated/ClientAuthError'; import { resetTauriRuntime } from './tauriRuntimeFake'; @@ -94,6 +95,19 @@ describe('客户端错误报告池', () => { vi.clearAllMocks(); }); + it('结构化客户端错误保留 message、code 和 detail,不退化为 object Object', () => { + expect( + visibleClientErrorMessage({ + code: 'ERR_NETWORK', + message: '连接被对端重置', + detail: 'socket hang up', + }), + ).toBe('连接被对端重置;socket hang up;ERR_NETWORK'); + expect(visibleClientErrorMessage({ reason: 'ipc-disconnected' })).toBe( + 'ipc-disconnected', + ); + }); + it('按 fingerprint 合并重复错误并累计次数', async () => { const first = (await captureClientError(new Error('重复错误'), { source: 'test', diff --git a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md index e853f1829..949963a59 100644 --- a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md +++ b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md @@ -116,6 +116,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接 - Codex CLI/Agent Runner 的 stderr、子任务 join、请求序列化/响应解析和本地资源/字体读取错误保留底层 `std::io`、serde 与 UTF-8 原因;路径安全校验仍单独使用稳定的安全拒绝码。 - Codex Provider proxy 的请求体读取/JSON 改写、上游请求、响应构造和 SSE 传输错误也必须把 serde、join、reqwest 或流错误写入返回正文,不能只回 `provider upstream unavailable`。 - 浏览器健康检查的发现、临时目录、配置、DevTools 版本请求和清理失败保留底层错误/超时阶段;稳定浏览器码用于分类,但不能覆盖诊断正文。 +- WebView 普通错误出口遇到 Tauri 的结构化对象时,优先展示 `message/detail/reason/code`,再有界序列化对象;不得把对象转成 `[object Object]` 后回落“未提供具体错误正文”。 - 浏览器启动/DevTools 握手与 Codex model-catalog 子进程失败保留 stderr、退出状态、解析错误和阶段;稳定机器码仍用于分类,但不能单独成为用户可见正文。 - Node/npm 环境探测和 Web scaffold 构建持续排空并保存有界 stdout/stderr 尾部,失败返回退出状态、超时阶段和脱敏正文;环境预检回执同时下发 `code` 与 `diagnostic`。 - HTTP 409 只有明确包含泥点不足事实时才映射为 `paidCreditsInsufficient`;Claude Code 的普通 409 冲突保留为 `upstreamFailed`。 diff --git a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md index f4fffb4cd..2e333217d 100644 --- a/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md +++ b/docs/project-memory/plans/【里程碑】AGC错误具体文本展示-2026-10-04.md @@ -19,6 +19,7 @@ DirectProject 回合失败在确认不是客户端内部不可归类故障时, - Codex CLI/Agent Runner 及本地素材/字体读取保留 stderr、join、序列化/解析、UTF-8 和 OS 错误正文;路径边界拒绝仍保留稳定安全码。 - Codex Provider proxy 的请求体改写、上游发送、响应构造和 SSE 错误保留底层正文,避免代理层再次把具体网络/解析故障压成单一 unavailable。 - 浏览器健康检查不再把发现、配置、DevTools 版本和清理失败只返回机器码;阶段码保留,同时带底层错误或明确的超时预算。 +- WebView 结构化对象错误保留 `message/detail/reason/code` 或有界 JSON 正文,避免 `[object Object]` 触发普通错误兜底。 - Transport / Stream / IPC / host process / memory exhaustion 的回归测试与错误事件证据。 ## 不做