收敛资源工作台错误正文

统一资源编辑、上传、预览和恢复队列的诊断脱敏

保留错误状态与正文并补充当前错误处理文档
This commit is contained in:
kdletters
2026-10-05 10:17:09 +08:00
parent 7369956762
commit 715b74824f
3 changed files with 50 additions and 48 deletions
@@ -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<HTMLElement>('[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<HTMLElement>('[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<ResourceEditServiceIdentityConfirmation>(
@@ -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 ||
/*
@@ -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`。
## 后果与边界
@@ -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/数据库健康检查通过,停止本次自有客户端后保留原有后端。