diff --git a/apps/ai-game-creator-shell/src/view/project-development/index.tsx b/apps/ai-game-creator-shell/src/view/project-development/index.tsx index 7ed5989c5..699dc0588 100644 --- a/apps/ai-game-creator-shell/src/view/project-development/index.tsx +++ b/apps/ai-game-creator-shell/src/view/project-development/index.tsx @@ -281,6 +281,7 @@ import { } from '../../features/resource-canvas/resourceVersionReplacementTransport'; import { useResourceCanvasGenerationPlaceholders } from '../../features/resource-canvas/useResourceCanvasGenerationPlaceholders'; import { createUiDesignDocFromImages } from '../../features/ui-editor/uiDesignResourceBridge'; +import { normalizeDiagnosticText } from '../../services/errorReporting'; import { currentPlatformSessionGeneration, requestPlatformSessionRefresh, @@ -516,6 +517,18 @@ function resolveResourceCardDragMoves({ */ const EMPTY_RESOURCE_CANVAS_HISTORY = createResourceCanvasHistory(); +function projectDevelopmentErrorMessage( + error: unknown, + fallback = '未提供具体错误正文', +) { + const raw = error instanceof Error ? error.message : String(error ?? ''); + const safe = normalizeDiagnosticText(raw.replace(/\s+/gu, ' ').trim()).slice( + 0, + 400, + ); + return safe || fallback; +} + function isPlatformAuthenticationRequired(error: unknown) { const message = error instanceof Error ? error.message : String(error); return ( @@ -2795,9 +2808,7 @@ export default function ProjectDevelopmentView({ } onNotice({ tone: 'error', - message: `在浏览器打开失败:${ - error instanceof Error ? error.message : String(error) - }`, + message: `在浏览器打开失败:${projectDevelopmentErrorMessage(error)}`, }); } }, [embeddedPreviewUrl, onNotice]); @@ -4038,13 +4049,10 @@ export default function ProjectDevelopmentView({ commitId, }); } catch (error) { - setResourceWorkbenchNotice( - options.readBackFailureNotice - ? options.readBackFailureNotice(error) - : error instanceof Error - ? error.message - : String(error), - ); + const notice = options.readBackFailureNotice + ? options.readBackFailureNotice(error) + : projectDevelopmentErrorMessage(error); + setResourceWorkbenchNotice(projectDevelopmentErrorMessage(notice)); } }, [manifest.projectId, onManifestChange, projectPath], @@ -4129,9 +4137,7 @@ export default function ProjectDevelopmentView({ // 原生已经写入成功,只是整份 manifest 没读回来:说清"已保存、刷新失败", // 不给用户"保存失败、其实已落盘"的错觉。 readBackFailureNotice: (error) => - `标签已保存,但刷新失败:${ - error instanceof Error ? error.message : String(error) - }`, + `标签已保存,但刷新失败:${projectDevelopmentErrorMessage(error)}`, }, ); }, @@ -4702,8 +4708,7 @@ export default function ProjectDevelopmentView({ } const viewport = resolveViewport(event); const category = viewport?.dataset.resourceSectionScroll as - | ResourceCategory - | undefined; + ResourceCategory | undefined; if (!viewport || !category) { return; } @@ -4723,8 +4728,7 @@ export default function ProjectDevelopmentView({ const event = rawEvent as WebKitGestureEvent; const viewport = resolveViewport(event); const category = viewport?.dataset.resourceSectionScroll as - | ResourceCategory - | undefined; + ResourceCategory | undefined; if (!viewport || !category) { return; } @@ -4883,8 +4887,7 @@ export default function ProjectDevelopmentView({ .querySelectorAll('[data-resource-section-scroll]') .forEach((viewport) => { const category = viewport.dataset.resourceSectionScroll as - | ResourceCategory - | undefined; + ResourceCategory | undefined; if (!category) { return; } @@ -4904,8 +4907,7 @@ export default function ProjectDevelopmentView({ ?.querySelectorAll('[data-resource-section-scroll]') .forEach((viewport) => { const category = viewport.dataset.resourceSectionScroll as - | ResourceCategory - | undefined; + ResourceCategory | undefined; if (!category) { return; } @@ -5080,7 +5082,7 @@ export default function ProjectDevelopmentView({ ) { return { status: 'stale' as const }; } - const message = error instanceof Error ? error.message : String(error); + const message = projectDevelopmentErrorMessage(error); setPendingResourceEditsLoadState('failed'); setPendingResourceEditsError(message); return { status: 'failed' as const, error: message }; @@ -5431,7 +5433,7 @@ export default function ProjectDevelopmentView({ savedCount += 1; onNotice(`已保存 ${result.destinationPath}`); } catch (error) { - onNotice(error instanceof Error ? error.message : String(error)); + onNotice(projectDevelopmentErrorMessage(error, '保存素材失败')); break; } } @@ -5496,7 +5498,7 @@ export default function ProjectDevelopmentView({ } } catch (error) { setResourcePanelNotice( - error instanceof Error ? error.message : String(error), + projectDevelopmentErrorMessage(error, '上传素材失败'), ); } finally { setResourcePanelUploading(false); @@ -6502,7 +6504,7 @@ export default function ProjectDevelopmentView({ } catch (error) { if (canvasOpenEpochRef.current === openEpoch) { setResourceWorkbenchNotice( - error instanceof Error ? error.message : String(error), + projectDevelopmentErrorMessage(error, '打开 UI 编辑器失败'), ); } } @@ -6731,9 +6733,11 @@ export default function ProjectDevelopmentView({ } } catch (error) { if (!isCurrentRecoveryProject(actionProject)) return; - const message = error instanceof Error ? error.message : String(error); + const rawMessage = + error instanceof Error ? error.message : String(error); + const message = projectDevelopmentErrorMessage(error); let displayMessage = message; - if (message.includes('service-identity-confirmation-required')) { + if (rawMessage.includes('service-identity-confirmation-required')) { try { const confirmation = await invoke( @@ -6764,10 +6768,7 @@ export default function ProjectDevelopmentView({ displayMessage = `请确认“${confirmation.serviceOrigin}”仍是该 operation 的原服务后再继续`; } catch (confirmationError) { if (!isCurrentRecoveryProject(actionProject)) return; - displayMessage = - confirmationError instanceof Error - ? confirmationError.message - : String(confirmationError); + displayMessage = projectDevelopmentErrorMessage(confirmationError); } } setPendingResourceEditActionErrors((current) => { @@ -6843,7 +6844,7 @@ export default function ProjectDevelopmentView({ shouldResume = true; } catch (error) { if (!isCurrentRecoveryProject(actionProject)) return; - const message = error instanceof Error ? error.message : String(error); + const message = projectDevelopmentErrorMessage(error); setResourceEditServiceIdentityConfirmations((current) => { if (!current.has(pending.operationId)) return current; const next = new Map(current); @@ -6924,7 +6925,7 @@ export default function ProjectDevelopmentView({ } } catch (error) { if (!isCurrentRecoveryProject(actionProject)) return; - const message = error instanceof Error ? error.message : String(error); + const message = projectDevelopmentErrorMessage(error); setPendingResourceEditActionErrors((current) => { const next = new Map(current); next.set(pending.operationId, message); @@ -6979,16 +6980,16 @@ export default function ProjectDevelopmentView({ dependencyLayoutSettled: dependencyLayout.settled, dependencyPositioned: Boolean( intent.resourceId && - dependencyLayout.layout.positions.some( - (position) => position.resourceId === intent.resourceId, - ), + dependencyLayout.layout.positions.some( + (position) => position.resourceId === intent.resourceId, + ), ), typeLayoutSettled: typeLayout.settled, typePositioned: Boolean( intent.resourceId && - typeLayout.layout.positions.some( - (position) => position.resourceId === intent.resourceId, - ), + typeLayout.layout.positions.some( + (position) => position.resourceId === intent.resourceId, + ), ), visible: targetVisible, domRendered: Boolean(card), @@ -8071,8 +8072,7 @@ export default function ProjectDevelopmentView({ ? { ...current, status: 'failed', - errorMessage: - error instanceof Error ? error.message : String(error), + errorMessage: projectDevelopmentErrorMessage(error), } : current, ); @@ -8199,7 +8199,7 @@ export default function ProjectDevelopmentView({ resourceQuickEditRequestRef.current = null; setResourceWorkbenchNotice('快速编辑已产出新素材,正在同步资源与布局…'); } catch (error) { - const message = error instanceof Error ? error.message : String(error); + const message = projectDevelopmentErrorMessage(error); settleResourceEditSubmission(request.operationId, { status: 'failed', error: message, @@ -8343,8 +8343,7 @@ export default function ProjectDevelopmentView({ ? { ...current, status: 'failed', - errorMessage: - error instanceof Error ? error.message : String(error), + errorMessage: projectDevelopmentErrorMessage(error), } : current, ); @@ -8480,7 +8479,7 @@ export default function ProjectDevelopmentView({ }); setResourceWorkbenchNotice('生成动画已产出新素材,正在同步资源与布局…'); } catch (error) { - const message = error instanceof Error ? error.message : String(error); + const message = projectDevelopmentErrorMessage(error); settleResourceEditSubmission(request.operationId, { status: 'failed', error: message, @@ -8798,7 +8797,7 @@ export default function ProjectDevelopmentView({ }); } catch (error) { setResourceWorkbenchNotice( - error instanceof Error ? error.message : String(error), + projectDevelopmentErrorMessage(error, '同步生成结果失败'), ); return; } @@ -9174,7 +9173,7 @@ export default function ProjectDevelopmentView({ } } catch (error) { setResourceWorkbenchNotice( - error instanceof Error ? error.message : String(error), + projectDevelopmentErrorMessage(error, '上传素材失败'), ); } finally { setResourceBottomToolbarUploading(false); @@ -10110,7 +10109,7 @@ export default function ProjectDevelopmentView({ Boolean(selectedResource?.manifestAssetId) || Boolean( selectedResource && - isResourceDocumentPreviewable(selectedResource), + isResourceDocumentPreviewable(selectedResource), ) || selectedResourceOpensUiEditor || /* diff --git a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md index b2028681d..e2f3e88c4 100644 --- a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md +++ b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md @@ -110,6 +110,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接 - HTTP 409 只有明确包含泥点不足事实时才映射为 `paidCreditsInsufficient`;Claude Code 的普通 409 冲突保留为 `upstreamFailed`。 - `hostDropped` 携带可选的脱敏 `detail`:panic hook 能取得的负载和位置随原回合占用进入失败事件;普通 Drop 仅记录“退出时未写终态、未观察到 panic”,不推断为网络中断。旧版本无 `detail` 的事件继续可读,界面明确标注旧事件未记录原因。 - DirectProject 控制器的取消待发、终止、附件上传、项目历史读取和发送前置异常也复用同一份前端精确脱敏出口;这些非回合错误不得直接把 `Error.message` 原文写入状态栏、composer 或历史错误行。 +- AGC 项目资源编辑、上传、预览、恢复队列和生成结果回读的面板错误统一经过 `normalizeDiagnosticText`,保留状态码与安全正文,替换凭据、URL、路径和长标识;资源业务提示不再直接展示 `Error.message`。 ## 后果与边界 diff --git a/docs/project-memory/plans/【实施计划】AGC错误具体文本展示-2026-10-04.md b/docs/project-memory/plans/【实施计划】AGC错误具体文本展示-2026-10-04.md index a59da4db2..703c85068 100644 --- a/docs/project-memory/plans/【实施计划】AGC错误具体文本展示-2026-10-04.md +++ b/docs/project-memory/plans/【实施计划】AGC错误具体文本展示-2026-10-04.md @@ -14,6 +14,7 @@ Parent Milestone: `【里程碑】AGC错误具体文本展示-2026-10-04.md` 5. 更新 ADR 的当前行为口径。 6. HostDropped 的可选 detail 由 Rust 单点脱敏;panic hook 把可读负载和位置关联到原回合占用,Drop 与旧无字段事件分别验证,不更改回合排队、重试或计费行为。 7. DirectProject 控制器的取消、上传、历史读取和前置异常统一复用前端精确脱敏出口,保留 IPC/OS 正文并替换凭据值、URL 与路径。 +8. 项目资源编辑/上传/预览/恢复队列/生成结果回读的 UI 错误统一使用 `normalizeDiagnosticText`,不再直出 `Error.message`。 ## 实现顺序 @@ -35,5 +36,6 @@ Parent Milestone: `【里程碑】AGC错误具体文本展示-2026-10-04.md` - Rust 应用日志/启动诊断精确脱敏回归:通过。 - Rust HostDropped panic detail + 旧无字段载荷兼容回归:定向通过。 - DirectProject 控制器错误正文:新增脱敏与空错误专用提示回归,前端定向测试通过。 +- 资源工作台错误正文:项目开发页所有用户可见资源错误路径完成统一脱敏收口,TypeScript 与编码检查覆盖。 - 真实 Provider、IPC 断链和内存压力尚未执行。 - 2026-10-05 当前代码真实 `npm run agc` 构建启动通过;客户端与 Runner 已启动,前端/后台/API/worker/数据库健康检查通过,停止本次自有客户端后保留原有后端。