Files
Genarrative/docs/technical/【技术方案】AGC错误报告与诊断上传-2026-08-31.md
k88936 964290c641
Project CI / Native shell tests (push) Successful in 21m3s
Project CI / Repository checks (push) Successful in 2m37s
Project CI / Frontend tests (push) Successful in 2m41s
Project CI / Backend tests (push) Successful in 7m2s
优化错误报告 (#286)
样式

before:
![shotmd-1788605870.jpg](/attachments/0470de00-944f-466e-a339-cf9e6cc0b780)
![shotmd-1788605755.jpg](/attachments/3e7be68e-31c5-41ba-9f8b-6dc98ec08868)

after:
![shotmd-1788607435.jpg](/attachments/165c878e-5c1c-4517-b7d5-74ce97a1a99b)
![shotmd-1788608364.jpg](/attachments/4dd20baf-879c-4aa8-ab51-bcc588d48dbc)

逻辑:
每次打开报告modal会再次查询队列中的错误
<video src="attachments/6d9d9bd7-5b7b-4a05-a7c3-ca64b6fb77f3" title="2026-09-05 19-46-02.mp4" controls></video>

---------

Co-authored-by: 段舒康 <kdletters@qq.com>
Reviewed-on: http://192.168.35.82/git/GenarrativeAI/Genarrative/pulls/286
Reviewed-by: 段舒康 <kdletters@qq.com>
Co-authored-by: 王德宇 <kvtodev@outlook.com>
Co-committed-by: 王德宇 <kvtodev@outlook.com>
2026-09-07 15:17:21 +08:00

8.6 KiB
Raw Permalink Blame History

AGC 错误报告与诊断上传

范围

AI Game Creator Shell 采用 IDEA 风格的当前进程错误报告:错误事件在内存池中按 fingerprint 合并;应用级诊断日志持续写入 Tauri AppData 的滚动文件;用户打开“报告问题”面板并确认后,一次提交当前进程选中的错误事件和脱敏应用日志。项目目录 .agent/logs/*、源码、prompt、配置、项目产物和截图不进入报告包。

客户端

  • 捕获 React render error、window.onerrorunhandledrejection 以及显式标记的 Tauri/API/Agent 错误。Agent Runtime 的终态失败、预算耗尽和启动确认失败由 Rust 失败投影统一入池;Direct Codex 与专业 Agent 的前台裸 Tauri invoke catch 作为补充入口,重复事件由同一 fingerprint 合并,主动取消和“同一 turn 已在运行”不作为错误采集。
  • 事件字段包括 eventId、fingerprint、source、message、stack、时间和次数;重复事件合并。不再携带 severity、errorCode、page、action、requestId 等无法稳定关联的字段。
  • 指纹计算可使用调用方的 page/action 及脱敏后的首个调用点作为进程内区分输入,但这些上下文不会作为事件字段上传;消息与 stack 在入池前统一脱敏,WebCrypto 失败时降级为稳定可读指纹,采集本身不得产生新的未处理拒绝。
  • 客户端 API 自动采集只覆盖网络错误、408 和 5xx;预期的 4xx 登录/鉴权失败不进入错误报告池。
  • Rust 侧通过 app_log! 将普通文本日志同时输出到 stderr 和 AppData diagnostics/application.log,超出 256 KiB 滚动到 application.previous.logWebView 的 console 输出通过 append_application_log 镜像到同一 raw log,并在客户端桥接处再次脱敏;read_diagnostic_logs 只读取应用级日志。
  • 报告面板只由自动诊断通知中的“查看并报告”打开,不提供聊天命令、崩溃页按钮或其他手动入口;默认选中当前快照中的全部事件,用户可取消不想提交的事件。允许填写最多 2,000 字中文描述并取消日志附件;本版本不支持截图或任意文件附件。
  • 报告面板读取当前错误快照失败时,必须明确显示“错误事件暂不可用,请关闭后重试”,不能把失败误显示为“当前没有待报告的错误”。
  • 通知中的“查看并报告”打开面板时必须保留该次通知快照;最新快照读取瞬时失败时使用这份 fallback 继续展示和提交,不能因先清空通知而丢失用户刚看到的事件。
  • Rust 是结构化错误队列的唯一真相源:src-tauri/src/error_report/ 负责脱敏、调用点指纹、eventId、计数、100 条上限、5 秒聚合和 ack 生命周期;WebView 仅通过 Tauri bridge 上报、读取快照并保存短暂 React 展示状态,不维护第二份事件 Map。Runtime 错误上报统一使用 source=agent-runtimeaction=agent-runtime 和 Agent ID 作为 page;包含 kind=codex-app-server-*codex-app-server-error:* 的消息在入池前归一为稳定类别(例如 codex-app-server-error:other),不把 public summary 中的动态 fingerprint/长度作为分桶输入。Rust emit 只作为无状态唤醒,携带单调递增的 generation,不携带错误正文或事件 ID。
  • 错误事件先在当前进程内存池按 fingerprint 合并,经过 5 秒聚合后只发出一次非阻塞存在性唤醒;WebView 挂载、收到唤醒、重新获得焦点或恢复可见时都查询完整未 ack 快照,并在桥接暂时失败时做有限退避重试。通知支持“查看并报告”和“忽略”,同一 fingerprint 仅在新增时唤醒一次。通知不直接打开阻塞式报告面板;用户打开报告面板后,面板再读取一次完整未 ack 快照,确保提交使用打开时的最新事件。忽略只关闭当前 UI,不删除事件;提交成功后由 bridge ack/delete 选中事件。
  • 上传失败只在当前进程显示失败并允许用户再次提交,不跨重启恢复事件池,不后台自动重试。

HTTP 与存储

  • 登录态客户端使用 POST /api/error-reports,请求 DTO 位于 shared-contracts::error_reports
  • api-server 对请求体设置 24 MiB 上限,并校验 schemaVersion、submissionId、事件/日志数量和 20 MiB 压缩包上限;事件字段、用户说明和日志名/内容均做长度限制与凭据脱敏(匹配归一化覆盖 Unicode 空白字符),归档使用 events.jsonl(每行一个事件)。结构化事件只保存在当前进程内,用户提交时才生成 events.jsonl,不在磁盘单独持久化。submissionId 提供重放幂等;客户端对同一批事件复用稳定 submissionId,服务端提交成功后立即反馈成功,本地事件 ack 独立重试,不因 ack 失败误报提交失败;SpacetimeDB procedure 使用服务 identity 门禁,user_id 由 api-server 注入并在 module 校验,时间戳仍使用 ctx.timestamp。创建 procedure 额外限制单 identity 每小时 100 次提交。
  • 归档构建只在请求生命周期内使用受 20 MiB 上限约束的内存 Vec<u8>,随后直接 PUT 到私有 OSS;服务端不写本地报告文件,也不保留本地索引。OSS 上传失败不写入数据库,调用方可稍后重新提交。
  • 归档对象使用固定私有 OSS keyagc/error-reports/v1/{batchId}.zip;key 只由报告 UUID 决定,不包含时间戳。上传成功后才写入 SpacetimeDB error_report 元数据表;userId + submissionId 由唯一幂等键保证重放返回已有记录。完整事件、说明和日志只存在 OSS ZIP。
  • agc 是服务端专用私有前缀;公共直传票据、通用 object-key 规范化和 legacy 公开路径均拒绝该前缀。归档内同名日志会自动加数字后缀,读取本机诊断日志时拒绝符号链接/非普通文件。
  • 后台接口:GET/PATCH /admin/api/error-reports/{batchId}GET /admin/api/error-reports 和受保护的 /download。列表支持 limit/offset 分页并返回 totalhasMoreOSS 读取先检查 Content-Length 并在流式累计超过上限时立即中止,不把超限对象完整缓存在内存中。
  • 这些是 api-server 内部登录/管理员路由,不属于 /api/external/v1,不纳入 External OpenAPI;管理员详情对不存在返回 404,对归档/元数据损坏返回 500。
  • admin viewer 仅接受 error-reports Tab 权限,支持列表筛选、分页、详情、状态 new/in-progress/resolved、处理备注和受控下载;不存在的更新目标返回 404,存储损坏返回 500。列表行支持键盘 Enter/Space 打开详情,详情事件预览最多显示 20 条,完整内容通过诊断包下载获取。
  • 管理员列表、筛选、状态和备注全部读取/更新 SpacetimeDB;错误报告的 list/get/update procedure 要求 require_editor_generation_runtime_service_identity,详情先读表再从 OSS 下载并解析 ZIP,下载接口直接从 OSS 返回 ZIP。PATCH 更新直接返回元数据,不重新下载归档;详情弹窗提供备注编辑器。无需新增管理员 DELETE HTTP 接口。每日清理任务按分页扫描全部报告,删除过期 OSS 对象后再删 DB;任一 DB 删除失败会保留错误并在后续周期重试。详情解析对解压后的 events.jsonl 设置 24 MiB(与请求体上限一致)与 100 条事件上限。管理员备注超过 2,000 字会被明确拒绝;module 同时校验固定 OSS key、SHA-256、归档大小和事件/日志计数上限;OSS 读取仅将明确不存在映射为 404,其余错误按请求无效或上游故障返回。内部 OSS 读写只允许 agc/error-reports/v1/。旧本地报告不迁移。

SpacetimeDB error_report 表字段:batch_id 主键、user_idsubmission_ididempotency_key 唯一键、object_keyarchive_sha256archive_size_bytesevent_countlog_count、首个 fingerprint/source、review_statusadmin_notecreated_atupdated_at;索引为 (user_id, submission_id)created_atreview_status

验收

npm run typecheck --workspace @genarrative/ai-game-creator-shell
npx tsc -p apps/admin-web/tsconfig.json --noEmit
npx vitest run apps/ai-game-creator-shell/tests/errorReporting.test.ts apps/ai-game-creator-shell/tests/clientRuntimeErrorBoundary.test.ts apps/admin-web/src/app/adminRoutes.test.ts
cargo test -p api-server --bin api-server error_reports -- --nocapture
cargo fmt -p api-server -p shared-contracts -- --check
npm run check:encoding
git diff --check

Rust 全量检查还受现有 platform-llm ProviderReasoningEffort::Max 编译错误阻塞;本变更未修改该历史问题。