diff --git a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md index 319d4002c..e896f0188 100644 --- a/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md +++ b/docs/adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md @@ -45,19 +45,24 @@ DirectProject 已经解决过同一类问题([`【ADR】DirectProject命令接 谁抛出、谁判定。分层规则: - **预期业务拒绝**(用户输入、前置条件、预期 4xx):由调用方消化并给用户反馈,**永不进池**。 -- **真故障**(网络不可达、5xx、写盘/运行时安装失败、agent 终态失败):由调用方带上文重抛, - 经 `window.onerror` / `unhandledrejection` 入池;Rust 侧 agent 终态失败仍由失败投影入池。 +- **真故障**(网络不可达、5xx、写盘/运行时安装失败、agent 终态失败):由调用方带上文交给错误池 + (`ClientActionError` + `captureClientError`);`window.onerror` / `unhandledrejection` 只兜底 + 没人接手的错误。Rust 侧 agent 终态失败仍由失败投影入池。 - **WebView 全局 handler 是兜底**:任何没人 catch 的错误都进池。 -- **API 客户端(`clientApi`)在抛出前判定 408/5xx/网络为缺陷**:它是 `fetch` 的调用方, - 这一判定就发生在它这一层;4xx 一律不报,交给上层调用方。这条边界保持现状,不放宽也不收紧。 +- **408/5xx/网络的判定由调用方在 catch 里做**:AGC shell 的 WebView 侧没有 fetch 边界的自动判定 + (`shouldCaptureClientError` 只认测试构造过、生产代码从不产生的 `{status}` / `{networkError}` + 形状,随本 ADR 删除);4xx 一律不报,交给上层调用方。 -### 3. 前端按变体分流,认不出就抛 +### 3. 前端按变体分流,认不出就交池 -- `isClientAuthError` 只做形状读取(`type` 是稳定判别键),`clientAuthErrorNotice` 用 - `switch (error.type)` 给出可展示文案;`default → null` 表示"认不出"。 -- 认不出、系统类、非结构化拒绝 → `throw new ClientActionError(message, context, cause)`; - `ClientActionError` 只承载 `source/action/page` 上下文与 `cause`,由全局 handler 用 - `instanceof` 解包后入池(指纹/展示字段与今天一致)。 +- `isClientAuthError` 只做形状读取(`type` 是稳定判别键),`clientAuthErrorKind` 用 + `switch (error.type)` 给出 `input` / `session` / `fault` 三类;`fault` 表示"认不出或宿主/环境事实"。 +- 认不出、系统类、非结构化拒绝 → 调用方包成 `ClientActionError(message, context, cause)` 交给 + `captureClientError`:`instanceof` 解包 `context` 与 `cause`,指纹/展示字段与今天一致。 + **不把 rejection 留在没人接手的 Promise 上**:`onSubmit` / `onClick` 这类 `void` 掉的 handler 抛错 + 最终以 `unhandledrejection` 结算,生产 WebView 里也能入池,但在 jsdom 下既不触发 `window` + 的 `unhandledrejection` 事件、又会让 `vitest run` 以 unhandled error 失败;调用方判定完直接 + 交给错误池,语义相同、可断言。全局 handler 仍保留同一套解包,接住真正漏出的 `ClientActionError`。 - 删除 `shouldCaptureClientError`:不再存在"叶子自己判定要不要报"的口径。 ### 4. 报告面板与通知行为不变 diff --git a/docs/project-memory/shared-memory/decision-log.md b/docs/project-memory/shared-memory/decision-log.md index 97fb31948..38e3d5b51 100644 --- a/docs/project-memory/shared-memory/decision-log.md +++ b/docs/project-memory/shared-memory/decision-log.md @@ -3,9 +3,9 @@ ## 2026-10-01 AGC 命令错误结构化与错误报告口径 - 决策:AGC 命令失败按**具体变体**建模(Rust `#[derive(Serialize, TS)]` 枚举 + `#[serde(tag = "type", rename_all = "camelCase")]` + ts-rs 导出,生成物不手改),`#[tauri::command]` 的 `Err` 直接携带结构化枚举;前端只按 `type` 分流,**任何地方都不对错误文案做判断**。做法沿用 DirectProject 既有约定(`enqueue_direct_codex_turn -> Result<(), DirectTurnEnqueueFailure>`),不是新机制。 -- 决策:错误报告池只收**没有任何调用方处理**的错误。预期业务拒绝(用户输入 / 前置条件 / 预期 4xx)由调用方消化并给反馈,永不进池;真故障由调用方带上下文重抛(`ClientActionError` 承载 `source/action/page`),经 `window.onerror` / `unhandledrejection` 入池;`clientApi` 作为 `fetch` 的调用方在抛出前判定 408/5xx/网络为缺陷(4xx 一律不报);Rust agent 终态失败仍由失败投影入池。删除 WebView 侧 `shouldCaptureClientError`。 -- 边界:变体按**可判定的事实**命名——服务端 400 只给 `status + message`(`AppError.code` 仍是通用 `BAD_REQUEST`),所以 400 变体按"哪条请求的输入被拒"命名(如 `passwordEntryInputRejected`),不假装能区分密码长度/手机号格式。报告面板默认全选、只由通知打开的既有承诺不变。`captureAgentRuntimeError`、`ResourceReferenceInput` 偏好写盘、`invokeDiagnostic` 三处显式采集点保持原行为,按同一口径重抛/删除留在后续变更。 -- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/{auth_error.rs,auth_session.rs}`、`apps/ai-game-creator-shell/src/services/{clientAuthError.ts,clientActionError.ts,errorReporting.ts,clientApi.ts,clientAuth.ts}`、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`、`apps/ai-game-creator-shell/src/services/generated/ClientAuthError.ts`(ts-rs 生成)。 +- 决策:错误报告池只收**没有任何调用方处理**的错误。预期业务拒绝(用户输入 / 前置条件 / 预期 4xx)由调用方消化并给反馈,永不进池;真故障由调用方带上下文交给错误池(`ClientActionError` 承载 `source/action/page`,`captureClientError` 用 `instanceof` 解包),`window.onerror` / `unhandledrejection` 只兜底没人接手的错误;408/5xx/网络的判定由调用方在 catch 里做(4xx 一律不报);Rust agent 终态失败仍由失败投影入池。删除 WebView 侧 `shouldCaptureClientError`。 +- 边界:变体按**可判定的事实**命名——服务端 400 只给 `status + message`(`AppError.code` 仍是通用 `BAD_REQUEST`),所以 400 变体按"哪条请求的输入被拒"命名(如 `passwordEntryInputRejected`),不假装能区分密码长度/手机号格式。报告面板默认全选、只由通知打开的既有承诺不变。`captureAgentRuntimeError`、`ResourceReferenceInput` 偏好写盘、`invokeDiagnostic` 三处显式采集点保持原行为,按同一口径改造或删除留在后续变更。 +- 影响范围:`apps/ai-game-creator-shell/src-tauri/src/{auth_error.rs,auth_session.rs}`、`apps/ai-game-creator-shell/src/services/{clientAuthError.ts,clientActionError.ts,errorReporting.ts,clientAuth.ts}`、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`、`apps/ai-game-creator-shell/src/services/generated/ClientAuthError.ts`(ts-rs 生成)。 - 验证:见 [`【ADR】AGC命令错误结构化与错误报告口径-2026-10-01`](../../adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md) 的验收清单;关键判据是"登录 400/401 业务变体不产生 `report_client_error`、不弹「发现问题」"。 ## 2026-09-30 release 渠道移除产品名与包名后缀 diff --git a/docs/project-memory/shared-memory/pitfalls.md b/docs/project-memory/shared-memory/pitfalls.md index 1d7122faa..763312568 100644 --- a/docs/project-memory/shared-memory/pitfalls.md +++ b/docs/project-memory/shared-memory/pitfalls.md @@ -6,8 +6,8 @@ - **现象**:登录页密码输错(或密码长度不合规)后弹出「发现问题」,报告面板「错误事件(2)」列出 `密码长度需要在 6 到 128 位之间 — auth · 1 次` 与 `手机号或密码错误 — auth · 1 次`,默认全选,与 react-render / 5xx / agent-runtime 终态失败视觉等价。 - **原因**:① 登录已下沉 Rust,`login_client_with_password` 等命令失败返回 `Err(String)`,Tauri 以**裸字符串**拒绝 `invoke`,前端拿不到任何类型信息;② `shouldCaptureClientError` 对非 object 值走默认 `return true`,`handleLoginSubmit` 的 catch 把预期业务拒绝报进了错误池。技术方案里"预期 4xx 登录/鉴权失败不进池"的口径早就成立,是错误通道的实现方式违背了它。 -- **处理(现行口径)**:命令错误一律按具体变体结构化(`Result<_, ClientAuthError>` + ts-rs 导出),UI 调用方按 `type` 分流:认得的业务变体只给用户反馈,系统变体/未识别变体/非结构化拒绝原样抛出走上报链路;删除 `shouldCaptureClientError`。详见 [`【ADR】AGC命令错误结构化与错误报告口径-2026-10-01`](../../adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md)。 -- **判据/取证**:`npx vitest run apps/ai-game-creator-shell/tests/errorReporting.test.ts`——登录返回结构化业务变体时 `report_client_error` 不被调用;系统变体经 `unhandledrejection` 只上报一次且 `source=auth`。Rust 侧 `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml auth_error` 钉住变体 `type` 与 400/401/429/5xx/网络映射。 +- **处理(现行口径)**:命令错误一律按具体变体结构化(`Result<_, ClientAuthError>` + ts-rs 导出),UI 调用方按 `type` 分流:认得的业务变体只给用户反馈,系统变体/未识别变体/非结构化拒绝由调用方包成 `ClientActionError` 交给错误池;删除 `shouldCaptureClientError`。详见 [`【ADR】AGC命令错误结构化与错误报告口径-2026-10-01`](../../adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md)。 +- **判据/取证**:`npx vitest run apps/ai-game-creator-shell/tests/errorReporting.test.ts`——登录返回结构化业务变体时 `report_client_error` 不被调用;系统变体带上文只上报一次且 `source=auth`。Rust 侧 `cargo test --locked --manifest-path apps/ai-game-creator-shell/src-tauri/Cargo.toml auth_error` 钉住变体 `type` 与 400/401/429/5xx/网络映射。 - **关联**:`apps/ai-game-creator-shell/src-tauri/src/auth_session.rs`、`apps/ai-game-creator-shell/src/services/errorReporting.ts`、`apps/ai-game-creator-shell/src/app/AuthenticatedClient.tsx`。 ## 2026-09-30 构建期 staging 撞上不装 npm 依赖的 Linux 门禁:AGC 壳 Rust lane 全红 diff --git a/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md b/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md index cd4bf8002..21c23cfe0 100644 --- a/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md +++ b/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md @@ -8,7 +8,7 @@ AI Game Creator Shell 采用 IDEA 风格的当前进程错误报告:错误事 - 诊断 URL 保留可定位的 API 路由路径,隐藏 origin、URL 账号密码、查询参数、fragment 和路径中的敏感标识;普通资源 URL 与本地文件路径继续隐藏。网络错误、HTTP 错误与响应体超时均应带安全路由,不能只剩 ``。历史已经脱敏的归档不推测或补造原路由。 -- 报告池只收**没有任何调用方处理**的错误:React render error、`window.onerror`、`unhandledrejection`,以及调用方判定为真故障后带上下文重抛的错误。Agent Runtime 的终态失败、预算耗尽和启动确认失败由 Rust 失败投影统一入池;Direct Codex 与专业 Agent 的前台裸 Tauri invoke catch 作为补充入口,重复事件由同一 fingerprint 合并,主动取消和“同一 turn 已在运行”不作为错误采集。分层口径见 [`【ADR】AGC命令错误结构化与错误报告口径-2026-10-01`](../adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md)。 +- 报告池只收**没有任何调用方处理**的错误:React render error、`window.onerror`、`unhandledrejection`,以及调用方判定为真故障后带上下文交给错误池(`ClientActionError`)的错误。Agent Runtime 的终态失败、预算耗尽和启动确认失败由 Rust 失败投影统一入池;Direct Codex 与专业 Agent 的前台裸 Tauri invoke catch 作为补充入口,重复事件由同一 fingerprint 合并,主动取消和“同一 turn 已在运行”不作为错误采集。分层口径见 [`【ADR】AGC命令错误结构化与错误报告口径-2026-10-01`](../adr/【ADR】AGC命令错误结构化与错误报告口径-2026-10-01.md)。 - 事件字段包括 eventId、fingerprint、source、message、stack、时间和次数;重复事件合并。不再携带 severity、errorCode、page、action、requestId 等无法稳定关联的字段。 - 指纹计算可使用调用方的 page/action 及脱敏后的首个调用点作为进程内区分输入,但这些上下文不会作为事件字段上传;消息与 stack 在入池前统一脱敏,WebCrypto 失败时降级为稳定可读指纹,采集本身不得产生新的未处理拒绝。 - 命令失败按具体变体建模(Rust 枚举 + ts-rs 导出的 `type` 判别联合),前端按变体分流,**任何地方都不对错误文案做判断**:认得的业务变体(用户输入 / 前置条件 / 预期 4xx)由调用方消化并给用户反馈,永不进池;系统变体、未识别变体和结构化之外的拒绝原样抛出,走上面的兜底入口。