# Debug EML 页面前端对接指南 ## 1. 文档定位 本文给前端 Debug 页面开发使用,说明 `.eml` 邮件上传到 SuperAgent 调试链路应该做什么、调用哪个接口、如何组织请求、如何展示返回结果,以及哪些安全边界不能越过。 相关背景文档: - `docs/project/requirements/M004-debug-eml-superagent-upload-v1.md` - `docs/project/frontend-backend/backend-to-frontend-notes.md` ## 2. 页面目标 Debug EML 页面第一版只做一件事: 上传单封 `.eml` 邮件,后端解析邮件、上传原始邮件 / 内联图片 / 附件到本系统阿里云 OSS,替换 HTML 中的 `cid:` 图片,写入 SourceMessage Inbox,然后调用 SuperAgent Open API,并把 SuperAgent 返回结果展示给调试人员。 第一版明确不做: - 不创建 Reservation 订单。 - 不创建 Reservation 任务。 - 不调用 SuperAgent 任务结果通知接口。 - 不触发 AgentBus 实时链路。 - 不做批量上传。 - 不提供 Debug run 历史列表。 - 仅提供按 `debug_run_id` 查询单次运行状态和安全结果,用于 SSE 断流后的前端兜底轮询。 - 不在生产普通业务页面开放。 ## 3. 页面建议结构 建议页面分为以下区域: | 区域 | 展示 / 操作 | 说明 | | --- | --- | --- | | 上传配置区 | 可选酒店覆盖、Debug 上传口令、`run_label`、`.eml` 文件选择、提交按钮 | Debug 上传口令只能由调试人员临时输入,不能写进前端源码、环境变量、localStorage 或 URL;单酒店阶段默认不需要手填酒店。 | | 执行状态区 | loading、成功、失败、耗时、本次 `debug_run_id` | 提交后禁用按钮,避免重复点击;失败时展示安全错误摘要。 | | SourceMessage 追溯区 | `source_message_id`、`source_provider`、`external_message_id`、`external_conversation_id` | 用于确认已写入 SourceMessage Inbox。 | | 邮件内容预览区 | `html_body_sanitized`、纯文本、附件列表、内联图片列表 | HTML 展示必须优先使用 `html_body_sanitized`。 | | SuperAgent 结果区 | `superagent_parsed_json`、`superagent_raw_answer`、`warnings[]` | JSON 可以格式化展示;旧 S000/S999 和新 S10/S99 入口通知都不应被前端当成普通解析失败;raw answer 用于排查其他非 JSON 输出。 | | 调试 Payload 区 | `agentbus_like_payload` | 只用于调试展示,不让用户编辑后重新提交。 | ## 4. 接口 实时 Trace 页面优先使用流式接口: ```text POST /api/system/debug/eml-superagent-runs/stream Content-Type: multipart/form-data Accept: text/event-stream Header: X-TH-Hotel-Debug-Upload-Key: <调试上传口令> ``` 同步调试接口仍保留: ```text POST /api/system/debug/eml-superagent-runs Content-Type: multipart/form-data Accept: application/json Header: X-TH-Hotel-Debug-Upload-Key: <调试上传口令> ``` SSE 断流兜底查询接口: ```text GET /api/system/debug/eml-superagent-runs/{debug_run_id} Accept: application/json Header: X-TH-Hotel-Debug-Upload-Key: <调试上传口令> ``` 中文说明:前端不直接调用 SuperAgent。上传后如果 SSE 已收到 `debug_run_id`,但浏览器或反向代理提前关闭了流,前端可以用同一个 Debug 上传口令轮询该 GET 接口,直到状态变成 `SUPERAGENT_SUCCEEDED`、`SUPERAGENT_FAILED` 或 `FAILED`。该接口不是历史列表接口,也不允许绕过 Debug 上传口令。 注意: - 该接口只有在后端 `debug.eml-upload.enabled=true` 时存在;如果后端未开启,前端可能收到 `404`。 - 请求必须使用 `FormData`。 - 前端不要手动设置 `Content-Type`,让浏览器自动生成 multipart boundary。 - `X-TH-Hotel-Debug-Upload-Key` 不能进入 `VITE_*`、源码、构建产物、localStorage、URL query、错误上报或普通日志。 请求参数: | 参数 | 类型 | 必填 | 说明 | | --- | --- | --- | --- | | `file` | File | 是 | `.eml` 邮件文件。前端文件选择器建议 `accept=".eml,message/rfc822"`。 | | `hotel_id` | string | 否 | 可选酒店上下文覆盖;缺省由后端按当前登录用户上下文或平台酒店表唯一 `ACTIVE` 酒店解析。 | | `run_label` | string | 否 | 调试标签,例如 `frontend-debug-smoke`,方便后端日志和数据库排查。 | 前端调用示例: ```ts export async function uploadDebugEml(input: { baseUrl: string hotelId?: string | null debugUploadKey: string file: File runLabel?: string }) { const form = new FormData() form.append('file', input.file) if (input.hotelId?.trim()) { form.append('hotel_id', input.hotelId.trim()) } if (input.runLabel?.trim()) { form.append('run_label', input.runLabel.trim()) } const response = await fetch(`${input.baseUrl}/api/system/debug/eml-superagent-runs`, { method: 'POST', headers: { Accept: 'application/json', 'X-TH-Hotel-Debug-Upload-Key': input.debugUploadKey, }, body: form, }) const data = await response.json() if (!response.ok) { throw data } return data } ``` ## 5. 成功响应 成功状态码:`201 Created` 核心字段: | 字段 | 类型 | 前端用途 | | --- | --- | --- | | `debug_run_id` | string | 展示本次调试运行 ID。 | | `source_message_id` | string | 展示 SourceMessage 内部 ID;可用于后续排查。 | | `source_provider` | string | 固定为 `DEBUG_EML_UPLOAD`。 | | `external_message_id` | string | Debug 独立外部消息 ID,格式类似 `debug-eml-run-{debugRunId}-{sha256前缀}`。 | | `external_conversation_id` | string | 邮件会话 ID,可能来自 `References` / `In-Reply-To` / `Thread-Index`。 | | `original_eml_oss_url` | string | 原始 `.eml` OSS URL;只在受控调试页面展示。 | | `original_eml_sha256` | string | 原始 `.eml` SHA-256。 | | `uploaded_media[]` | array | 原始邮件、内联图片和附件列表。 | | `html_body_with_oss_urls` | string | 替换 `cid:` 后的 HTML,作为调试原始处理结果展示,不建议直接渲染。 | | `html_body_sanitized` | string | 后端清洗后的 HTML,前端预览邮件正文时优先使用。 | | `html_sanitize_required` | boolean | 当前为 `true`,提醒前端不要直接信任原始 HTML。 | | `html_render_mode` | string | 当前可能为 `SANITIZED_HTML` 或 `TEXT_ONLY`。 | | `agentbus_like_payload` | object | 后端发送给 SuperAgent 的 AgentBus Outlook-like 主 payload;只读展示。 | | `superagent_session_id` | string | SuperAgent session ID。 | | `superagent_run_id` | string | SuperAgent run ID。 | | `superagent_raw_answer` | string | SuperAgent 最终原始文本回答。 | | `superagent_parsed_json` | object/null | 后端尝试解析出的 JSON;旧 S000/S999 会返回识别后的对象,0711 P0 后续结构化 S10/S99 会直接作为 JSON 展示,其他解析失败时可能为空。 | | `warnings[]` | string[] | 安全或解析警告,可在页面顶部或结果区展示。 | | `status` | string | Debug run 状态。 | `uploaded_media[]` 字段: | 字段 | 类型 | 说明 | | --- | --- | --- | | `media_type` | string | `ORIGINAL_EMAIL`、`INLINE_IMAGE`、`ATTACHMENT`。 | | `file_name` | string | 文件名。 | | `content_type` | string | MIME 类型。 | | `size_bytes` | number | 文件大小。 | | `external_url` | string | OSS URL。 | | `external_media_id` | string | 调试链路媒体 ID,例如 `cid:inline-001`。 | | `object_key` | string | OSS 对象路径。 | 关键展示规则: - 邮件正文 iframe / 富文本预览优先使用 `html_body_sanitized`。 - `html_body_with_oss_urls` 可以放在“原始处理 HTML”折叠面板中,不作为默认渲染内容。 - `agentbus_like_payload` 不包含 Debug 专属字段,不再包含 `schema_version`、`source.provider=DEBUG_EML_UPLOAD`、`source.original_message_id`、`debug_context` 或旧的 Debug 专属 `reply_policy.mode=debug_only`;当前会包含普通 `reply_policy.mode=manual`、`reply_policy.final_only=true`。 - 原始邮件 `Message-ID` 不作为 Debug 外部消息 ID,也不进入 SuperAgent 主 payload;需要排查时看原始 EML OSS 文件和后端 Debug run / SuperAgent metadata。 - `external_message_id` 是 Debug 链路生成的独立 ID,不等同于原始 `Message-ID`。 - `superagent_parsed_json` 有值时优先展示格式化 JSON;没有值时展示 `superagent_raw_answer`。 - 如果 SuperAgent 返回旧 `S000,source_message_id` 或 `S999,source_message_id`,后端会把它识别为特殊入口结果,不会作为 JSON 解析失败处理。前端可在结果区展示 `entry_result_code`、`entry_result_source_message_id`、`entry_result_meaning` 和 `entry_result_description`。 - 如果 SuperAgent 返回 0711 P0 新结构化 `S10/S99`,前端应按 JSON 展示 `route_code`、`result_type=source_message_review_notification`、`agent_assessment`、`notification` 和 S99 的入口 `manual_review`。 旧 S000/S999 解析示例: ```json { "entry_result_code": "S000", "entry_result_source_message_id": "mail-20260708-0001", "entry_result_meaning": "PURE_INFORMATION", "entry_result_description": "纯信息类邮件" } ``` V3 S10 结构化示例: ```json { "route_code": "S10", "result_type": "source_message_review_notification", "agent_assessment": { "status": "no_booking_action_detected", "reason_code": "no_booking_action_detected" }, "notification": { "notification_type": "source_message_review", "show_source_message": true, "requires_user_decision": true }, "manual_review": null } ``` ## 6. 错误响应 错误响应结构: ```json { "error_code": "DEBUG_UPLOAD_KEY_INVALID", "message": "Debug 上传访问口令缺失或错误。" } ``` 常见错误: | HTTP 状态 | `error_code` | 前端建议 | | --- | --- | --- | | `400` | `REQUEST_FIELD_REQUIRED` | 提示缺少必填字段,检查文件或请求参数。 | | `400` | `EML_FILE_REQUIRED` | 提示请选择 `.eml` 文件。 | | `400` | `EML_FILE_TOO_LARGE` | 提示文件超过后端限制;当前默认上限通常为 10MB,以环境配置为准。 | | `400` | `INVALID_FILE_TYPE` | 提示只支持 `.eml`。 | | `401` | `DEBUG_UPLOAD_KEY_INVALID` | 提示调试上传口令缺失或错误。 | | `404` | 无固定结构 | 后端未开启 Debug EML 接口或路径错误。 | | `422` | `EML_PARSE_FAILED` | 提示邮件文件无法解析。 | | `502` | `OSS_UPLOAD_FAILED` | 提示 OSS 上传失败,调试人员联系后端排查。 | | `502` | `SUPERAGENT_OPEN_API_FAILED` | 提示 SuperAgent 调用失败;SourceMessage 可能已经写入。 | | `500` | `DEBUG_EML_RUN_FAILED` | 提示 Debug 链路内部失败。 | 错误展示原则: - 页面只展示 `message` 和 `error_code`。 - 不展示请求 header、上传口令、完整邮件正文、完整 HTML、OSS 签名 URL 或 Provider 原始错误。 - `SUPERAGENT_OPEN_API_FAILED` 时,不要把页面理解为“邮件上传没有发生”;后端可能已经完成 OSS 上传和 SourceMessage 写入。 ## 7. 前端状态流转 建议页面状态: ```text idle → validating → uploading → waiting_superagent → succeeded 或 → failed ``` 实现建议: - 文件未选择或 Debug 上传口令为空时禁用提交按钮;`hotel_id` 为空是允许的,表示使用后端系统酒店。 - 提交后禁用文件选择和提交按钮,避免重复上传。 - SuperAgent 调用可能耗时较长,页面 loading 文案不要只写“上传中”,建议写“正在解析邮件并等待 SuperAgent 返回”。 - 成功后保留本次响应在页面内存中;当前没有 Debug run 历史列表,刷新页面后通常需要重新上传或手动使用已知 `debug_run_id` 排查。 - 如果 SSE 正常返回 `superagent_result`,以前端收到的最终结果为准;如果 SSE 在最终结果前关闭但已经收到 `debug_run_id`,前端应轮询 GET 兜底接口。 - 再次上传同一封 `.eml` 会生成新的 `external_message_id` 和新的 SourceMessage,前端不要按原始 `Message-ID` 去重。 ## 8. 安全与日志 前端必须遵守: - 不把 Debug 上传口令写入源码、`.env`、`VITE_*`、localStorage、sessionStorage、URL query、错误上报或普通日志。 - 不把 `uploaded_media[].external_url`、`original_eml_oss_url`、邮件 HTML、附件 URL 写入普通日志或埋点。 - 不在生产普通业务菜单暴露该页面。 - 不允许前端直接调用 SuperAgent、AgentBus、OSS、数据库或 OPERA。 - 不使用 Debug EML 页面结果改变订单、任务或 OPERA 状态。 ## 9. 联调前置条件 后端环境需要具备: - `DEBUG_EML_UPLOAD_*_ENABLED=true`。 - `DEBUG_EML_UPLOAD_*_ACCESS_KEY` 已配置。 - 阿里云 OSS endpoint、bucket、access key、public base URL 已配置。 - `SUPERAGENT_*_OPEN_API_ENABLED=true`。 - SuperAgent / DeerFlow Open API base URL 和 API key 已配置。 前端需要具备: - 可配置后端 `BASE_URL`。 - 可配置或输入可选 `hotel_id`;单酒店联调默认可留空。 - 调试人员临时输入 Debug 上传口令。 - 准备一封 `.eml` 样例邮件。 ## 10. 与其他页面的关系 - Debug EML 页面是调试工具,不是 Message Notification 页面。 - Debug EML 页面写入的 SourceMessage `provider=DEBUG_EML_UPLOAD`,用于和 AgentBus 来源区分。 - Debug 页面发给 SuperAgent 的 `agentbus_like_payload` 会尽量贴近实时 AgentBus Outlook payload;前端不要用 `DEBUG_EML_UPLOAD` 或 `schema_version` 判断该对象内容。 - Debug EML 页面第一版不创建订单和任务,所以上传成功后任务列表和订单列表不会因为这次上传自动新增业务数据。 - AgentBus 实时收到邮件后自动推 SuperAgent 已由 M007 后端 V1 实现,但默认关闭且不走 Debug 页面;不能用 Debug EML 页面代表生产实时链路。 ## 11. 当前后置事项 - Debug run 历史列表未做。 - Debug run 取消接口未做。 - 批量上传未做。 - 前端白名单元数据独立接口未做。 - 用户身份 / 权限体系未接入;当前依赖调试上传口令保护。