收敛 Direct 控制器错误正文
统一取消、上传、历史读取和前置异常的精确脱敏展示 保留 IPC 与操作系统错误并补充回归和文档
This commit is contained in:
+22
-13
@@ -35,6 +35,10 @@ import {
|
||||
enqueueSystemFailureNoticeText,
|
||||
invokeTurnEnqueue,
|
||||
} from '../conversation/directTurnEnqueueFailure';
|
||||
import {
|
||||
visibleDirectControllerError,
|
||||
visibleDirectFailureDetail,
|
||||
} from '../conversation/directTurnFailure';
|
||||
import type { HistorySlice } from '../generated/HistorySlice';
|
||||
import type { QueueRemovalOutcome } from '../generated/QueueRemovalOutcome';
|
||||
import type { ThreadItem } from '../generated/ThreadItem';
|
||||
@@ -322,9 +326,7 @@ export function useDirectProjectChatController({
|
||||
} catch (error) {
|
||||
if (projectPathRef.current !== nextProjectPath) return;
|
||||
setComposerNotice(
|
||||
`取消待发消息失败:${
|
||||
error instanceof Error ? error.message : String(error)
|
||||
}`,
|
||||
visibleDirectControllerError(error, '取消待发消息失败'),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -394,7 +396,7 @@ export function useDirectProjectChatController({
|
||||
} catch (error) {
|
||||
if (projectPathRef.current === projectPath) {
|
||||
setAttachmentNotice(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
visibleDirectControllerError(error, '文件上传失败'),
|
||||
);
|
||||
}
|
||||
return [];
|
||||
@@ -491,7 +493,13 @@ export function useDirectProjectChatController({
|
||||
// 写权限门等前置步骤抛出时不能只留一个未处理的 rejection:回合会静默失败,
|
||||
// 这一轮的任何说明都没有出口。
|
||||
if (projectPathRef.current === nextProjectPath) {
|
||||
onRuntimeError(error instanceof Error ? error.message : String(error));
|
||||
onRuntimeError(
|
||||
projectRuntimeVisibleError(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
'陶泥儿智能创作',
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
draftSafe = true;
|
||||
} finally {
|
||||
@@ -625,9 +633,7 @@ export function useDirectProjectChatController({
|
||||
setComposerNotice(message);
|
||||
}
|
||||
} catch (error) {
|
||||
onRuntimeError(
|
||||
`终止失败:${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
onRuntimeError(visibleDirectControllerError(error, '终止失败'));
|
||||
setComposerNotice('');
|
||||
} finally {
|
||||
setTurnCancelling(false);
|
||||
@@ -680,10 +686,10 @@ export function useDirectProjectChatController({
|
||||
if (pages.error) throw pages.error;
|
||||
setStatusNotice('');
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
if (projectPathRef.current !== nextProjectPath) return;
|
||||
const message = visibleDirectControllerError(error, '项目对话读取失败');
|
||||
onRuntimeError(message);
|
||||
setStatusNotice(`项目对话读取失败:${message}`);
|
||||
setStatusNotice(message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -734,9 +740,12 @@ export function useDirectProjectChatController({
|
||||
} catch (error) {
|
||||
// 与加载成功路径同样按世代守卫:切项目后在飞的读取失败不能算到新项目头上。
|
||||
if (isStaleLoad()) return;
|
||||
const message =
|
||||
(error instanceof Error ? error.message : String(error)) ||
|
||||
DIRECT_HISTORY_LOAD_ERROR_FALLBACK;
|
||||
const detail = visibleDirectFailureDetail(
|
||||
error instanceof Error ? error.message : String(error),
|
||||
);
|
||||
const message = detail
|
||||
? `读取更早的对话历史失败:${detail}`
|
||||
: DIRECT_HISTORY_LOAD_ERROR_FALLBACK;
|
||||
historyErrorRef.current = message;
|
||||
setHistoryError(message);
|
||||
} finally {
|
||||
|
||||
+10
@@ -65,6 +65,16 @@ export function visibleDirectFailureDetail(
|
||||
: safe;
|
||||
}
|
||||
|
||||
/** 控制器非回合错误的统一可见正文:只替换敏感值,空错误也保留专用未分类提示。 */
|
||||
export function visibleDirectControllerError(
|
||||
error: unknown,
|
||||
subject: string,
|
||||
): string {
|
||||
const raw = error instanceof Error ? error.message : String(error ?? '');
|
||||
const detail = visibleDirectFailureDetail(raw);
|
||||
return detail ? `${subject}:${detail}` : `${subject}:未提供具体错误正文`;
|
||||
}
|
||||
|
||||
function withVisibleFailureDetail(
|
||||
message: string,
|
||||
detail: string | null | undefined,
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { directTurnFailureNoticeText } from '../src/view/project-development/chat/conversation/directTurnFailure';
|
||||
import {
|
||||
directTurnFailureNoticeText,
|
||||
visibleDirectControllerError,
|
||||
} from '../src/view/project-development/chat/conversation/directTurnFailure';
|
||||
|
||||
describe('DirectProject 上游失败文案', () => {
|
||||
it.each([401, 403, 408, 429, 500, 502, 503, 504])(
|
||||
@@ -121,4 +124,23 @@ describe('DirectProject 上游失败文案', () => {
|
||||
expect(text).toContain('旧错误事件未记录具体原因');
|
||||
expect(text).not.toContain('服务连接');
|
||||
});
|
||||
|
||||
it('控制器错误也保留正文并只替换敏感值', () => {
|
||||
const text = visibleDirectControllerError(
|
||||
new Error(
|
||||
'IPC invoke failed: EPIPE;Authorization: Bearer fixture-secret;C:\\Users\\demo\\game',
|
||||
),
|
||||
'终止失败',
|
||||
);
|
||||
expect(text).toContain('EPIPE');
|
||||
expect(text).toContain('[已隐藏凭据]');
|
||||
expect(text).not.toContain('fixture-secret');
|
||||
expect(text).not.toContain('C:\\Users\\demo');
|
||||
});
|
||||
|
||||
it('空控制器错误使用专用未分类正文提示', () => {
|
||||
expect(visibleDirectControllerError(new Error(''), '读取失败')).toBe(
|
||||
'读取失败:未提供具体错误正文',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -109,6 +109,7 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接
|
||||
- Claude Code(cc)侧车的非零退出、RPC `error`、stdout JSON/UTF-8 解析失败、stderr 和静默超时也走同一映射;侧车 stderr 只在有界收口窗口内读取并进入同一脱敏 detail,不再只写裸 `eprintln!`。
|
||||
- HTTP 409 只有明确包含泥点不足事实时才映射为 `paidCreditsInsufficient`;Claude Code 的普通 409 冲突保留为 `upstreamFailed`。
|
||||
- `hostDropped` 携带可选的脱敏 `detail`:panic hook 能取得的负载和位置随原回合占用进入失败事件;普通 Drop 仅记录“退出时未写终态、未观察到 panic”,不推断为网络中断。旧版本无 `detail` 的事件继续可读,界面明确标注旧事件未记录原因。
|
||||
- DirectProject 控制器的取消待发、终止、附件上传、项目历史读取和发送前置异常也复用同一份前端精确脱敏出口;这些非回合错误不得直接把 `Error.message` 原文写入状态栏、composer 或历史错误行。
|
||||
|
||||
## 后果与边界
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ Parent Milestone: `【里程碑】AGC错误具体文本展示-2026-10-04.md`
|
||||
4. 补充 upstream、transport、stream、IPC、内存不足与 HostDropped 边界测试。
|
||||
5. 更新 ADR 的当前行为口径。
|
||||
6. HostDropped 的可选 detail 由 Rust 单点脱敏;panic hook 把可读负载和位置关联到原回合占用,Drop 与旧无字段事件分别验证,不更改回合排队、重试或计费行为。
|
||||
7. DirectProject 控制器的取消、上传、历史读取和前置异常统一复用前端精确脱敏出口,保留 IPC/OS 正文并替换凭据值、URL 与路径。
|
||||
|
||||
## 实现顺序
|
||||
|
||||
@@ -33,5 +34,6 @@ Parent Milestone: `【里程碑】AGC错误具体文本展示-2026-10-04.md`
|
||||
- 真实 AGC dev smoke 复现并定位 `Reached maximum number of turns (8)` → `transport-closed`;修复后客户端已热重编译重启,未再次触发付费 Provider 请求。
|
||||
- Rust 应用日志/启动诊断精确脱敏回归:通过。
|
||||
- Rust HostDropped panic detail + 旧无字段载荷兼容回归:定向通过。
|
||||
- DirectProject 控制器错误正文:新增脱敏与空错误专用提示回归,前端定向测试通过。
|
||||
- 真实 Provider、IPC 断链和内存压力尚未执行。
|
||||
- 2026-10-05 当前代码真实 `npm run agc` 构建启动通过;客户端与 Runner 已启动,前端/后台/API/worker/数据库健康检查通过,停止本次自有客户端后保留原有后端。
|
||||
|
||||
Reference in New Issue
Block a user