# fire-safety-ymd 测试对话页面 v1 Spec | 项 | 内容 | | --- | --- | | 状态 | Implemented locally;测试服务器公网部署待验收 | | 日期 | 2026-09-06 | | 负责人 | fire-safety-ymd 后端 | | 关联接口 | `POST /api/v1/apps/{app_id}/completion` | | 关联配置 | `FIRE_SAFETY_CHAT_PAGE_ENABLED` | ## 1. 背景 项目已有 DashScope 风格的兼容对话接口。SuperAgent、数据库和公网 MCP 联调时,使用者需要 一个无需另建前端工程的最小浏览器页面,用来输入问题、观察 SSE 结果并验证同一会话的后续轮次。 这个页面只服务于测试和受控联调,不改变既有第三方请求契约,也不提供真实用户登录或授权。 ## 2. 目标 - 由 Go 服务可选地提供 `GET /chat` 和 `GET /chat/` 两个页面入口,并提供同源的 CSS/JavaScript 资源。 - `FIRE_SAFETY_CHAT_PAGE_ENABLED` 默认值为 `false`;关闭时两个入口都返回 HTTP 404。 - 开启时页面使用与第三方完全相同的兼容对话请求: `POST /api/v1/apps/{FIRE_SAFETY_CHAT_COMPAT_APP_ID}/completion`。 - 页面让用户手动输入 `xtoken`,在当前页面内发起 SSE 请求,不把凭证写入页面源码、Cookie、 URL、localStorage、sessionStorage 或服务端配置。 - 页面保存上一轮成功响应的 `output.session_id` 仅在 JavaScript 内存中,并将它用于同一页面的 后续轮次。 - 页面能区分 SSE 的中间会话事件、最终 `finish_reason=stop`、上游错误和 HTTP 错误。 - Nginx 公网入口固定反代 `/chat`、`/chat/`、`/chat/app.css`、`/chat/app.js` 和兼容 `completion` 路径;是否实际提供页面由 Go 配置开关决定。 ## 3. 非目标 - 不实现注册、登录、JWT、SSO、角色、租户或最终用户级动态授权。 - 不把静态测试 `xtoken` 写入 HTML、JavaScript 常量、Nginx、镜像或 Git。 - 不持久化 Token、问题、答案、聊天历史或会话映射;刷新页面后不自动恢复会话。 - 不新增一套页面专用的聊天协议、Provider Session 或数据库访问接口。 - 不实现逐字流式动画、文件上传、图像、地图选点、后台任务或多实例共享会话。 - 不把页面公开等同于公网生产能力;页面和当前 Chat 兼容 API 都只适用于受控联调。 ## 4. 路由与开关 | 路由 | 开关关闭 | 开关开启 | 说明 | | --- | --- | --- | --- | | `GET /chat` | 404 | 308 到 `/chat/` | 页面规范化入口 | | `GET /chat/` | 404 | 200 `text/html` | 页面入口 | | `GET /chat/app.css` | 404 | 200 `text/css` | 页面样式资源 | | `GET /chat/app.js` | 404 | 200 `text/javascript` | 页面脚本资源 | | `POST /api/v1/apps/{app_id}/completion` | 按 Chat/Compat 配置 | 按 Chat/Compat 配置 | 页面和第三方共用 | 页面开关为 Go 进程启动时读取的配置。修改 `.env` 后必须重新创建 Compose 容器;仅执行 `docker compose restart` 不会把新的环境值加载进已有容器。Nginx 可以始终保留四个页面/资源的 精确反代 location,但 Go 关闭页面时必须返回 404。 当页面开启时,以下依赖也必须满足: - `FIRE_SAFETY_CHAT_ENABLED=true`; - `FIRE_SAFETY_CHAT_COMPAT_APP_ID` 非空且与 Nginx 的精确 completion location 一致; - `FIRE_SAFETY_CHAT_AUTH_TOKEN` 已配置为 Chat 方向的测试凭证; - `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 包含精确值 `https://agent.nianxx.com`; - SuperAgent 的服务端配置和 MCP 联调配置按对应 Spec 已完成。 页面请求不向 Go 发送身份、角色、区域、租户、Provider Session 或 metadata。页面中出现的 App ID 是公开路由标识,不是 Secret;Chat Token、MCP Bearer 和 SuperAgent Open API Key 必须分别使用不同值。 ## 5. 页面行为 页面至少包含:Token 输入框、问题输入框、发送按钮、清空当前会话按钮、运行状态和结果区域。 Token 输入框默认为空,推荐使用密码输入类型;页面不得从 URL、Cookie 或浏览器存储预填。 用户发送问题时,页面使用浏览器 `fetch`(不能使用无法设置自定义 Header 的 `EventSource`)向 同源兼容路径发送: ```http POST /api/v1/apps//completion Origin: https://agent.nianxx.com xtoken: <用户本次手动输入的测试 Token> Content-Type: application/json Accept: text/event-stream ``` 首轮请求体: ```json { "input": { "prompt": "观水镇附近有哪些地点候选?" }, "parameters": {} } ``` 后续请求体把同一页面内最近一次成功返回的本地会话 ID 放入 `input.session_id`: ```json { "input": { "prompt": "选择第 2 个,再查询附近水源。", "session_id": "conv_" }, "parameters": {} } ``` 页面必须逐个读取 `event: result` 的 SSE 数据: 1. 首个结果一般只包含 `output.session_id` 和 `finish_reason="null"`;页面保存会话 ID,但不把 该事件显示为最终答案。 2. 只有收到同一会话的 `finish_reason="stop"` 且带 `output.text` 时,页面才显示为成功。 3. `event: error`、非 2xx HTTP 响应、解析失败或没有收到 `stop` 时,页面显示失败,不把部分 文本当成可信答案,也不继续复用一个结果未知的会话。 4. 用户点击清空、刷新页面、关闭页面或新开页面时,页面内存中的 Token 和 `session_id` 都丢弃。 页面不应把工具名、工具参数、Provider Session、内部 Trace 或数据库敏感字段直接作为调试 信息展示。最终回答仍是 SuperAgent 辅助内容,不能替代报警、人员撤离、现场核验或现场指挥。 ## 6. Origin、Nginx 与安全边界 - 页面与 API 均从 `https://agent.nianxx.com` 提供,因此浏览器的 `Origin` 必须命中 Go 的精确 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 项;不得用 `*` 代替。 - Nginx 只负责 TLS、精确路径、限流、SSE 传输和反代,不比较、注入、记录或保存 `xtoken`。 - `/chat` 和 `/chat/` 页面 GET 本身不依赖 Token;真正的兼容 completion 请求仍由 Go 校验 `xtoken`。页面公开可访问不代表 API 无鉴权。 - 页面不会把 Token 放入 Referer、查询参数、片段、日志或错误消息。输入框可被浏览器用户读取, 所以 Token 只能是受控测试凭证,不能作为最终用户身份。 - 页面表单显式使用 POST,CSP 使用 `form-action 'none'`;JavaScript 不可用时页面显示警告, 不允许浏览器把 Token 或问题退化提交到 URL。 - 当前页面和兼容 API 的会话是单进程内存映射;Docker 重建、进程重启或多实例切换会使旧 `session_id` 失效。 - 不通过页面对外暴露 `/api/chat`、数据库、迁移命令或其他 Go 路由;Nginx 未列出的路径仍返回 404。 ## 7. 验收标准 ### 配置与路由 - Given 未设置 `FIRE_SAFETY_CHAT_PAGE_ENABLED`,When 请求 `/chat`,Then 返回 404;When 请求 `/chat/` 或两个资源路径,Then 均返回 404,且不创建 SuperAgent Run。 - Given `FIRE_SAFETY_CHAT_PAGE_ENABLED=true` 且依赖配置完整,When 请求 `/chat`,Then 返回 308 到 `/chat/`;When 请求 `/chat/`、`/chat/app.css` 和 `/chat/app.js`,Then 分别返回 200 HTML、 CSS 和 JavaScript。 - Given 页面开启但 `FIRE_SAFETY_CHAT_ALLOWED_ORIGINS` 不含 `https://agent.nianxx.com`,Then 配置校验失败或浏览器 completion 请求被精确 Origin 拒绝;不得放行通配符。 - Given 页面关闭,When Nginx 仍保留 `/chat`、`/chat/` location,Then Go 返回 404,Nginx 不应 把它们改成 DashScope 或其他 upstream。 ### 浏览器与兼容接口 - Given 页面打开且用户手动输入合法测试 Token,When 发送首轮问题,Then 请求体和 Header 与 第三方 completion 请求相同,并收到 `event: result`。 - Given 首轮最终事件为 `finish_reason="stop"`,When 用户发送第二个问题,Then 请求使用同一页面 内存中的 `input.session_id`,服务端复用会话。 - Given 用户刷新页面,When 再次发送问题,Then 页面不提交旧的 `session_id` 或 Token。 - Given 错误或空 Token,When 发送问题,Then 服务返回 401;页面不展示或记录 Token。 - Given SSE 只有初始 `null` 事件、发生断流或最终不是 `stop`,Then 页面不显示为成功。 ### 公网部署 - Given 服务器 Compose 已重建且 Nginx 已通过 `nginx -t` 并 reload,When `curl -i` 请求公网页面, Then 开关关闭时四个页面路径均返回 404;开启时 `/chat` 返回 308,`/chat/` 返回 200 `text/html`, 两个资源路径返回 200。 - Given 页面开启,When 浏览器从 `https://agent.nianxx.com/chat/` 发起 completion,Then 服务端 日志可按现有脱敏规则确认 Chat 请求,且不含 Token、问题、答案或会话内部 ID。 - Given 已知公网 MCP 地点搜索曾记录 `operation=fire_safety_search_place_candidates result=success`,Then 该证据只证明地点搜索 已到达并成功;`tools/list` 之外的完整多工具链仍需单独验收。 ## 8. 运行与回滚 页面开关变更只修改服务器未提交的 `.env`,不修改 Nginx 中的 Token: ```text # 默认关闭 FIRE_SAFETY_CHAT_PAGE_ENABLED=false # 测试开启时 FIRE_SAFETY_CHAT_PAGE_ENABLED=true FIRE_SAFETY_CHAT_ALLOWED_ORIGINS=https://agent.nianxx.com ``` 重新加载运行环境: ```bash cd /home/firee-safety-ymd docker compose config --quiet docker compose up -d --force-recreate --no-build docker compose ps curl --fail http://127.0.0.1:16587/health ``` 页面验收: ```bash curl -i https://agent.nianxx.com/chat/ curl -i https://agent.nianxx.com/chat curl -i https://agent.nianxx.com/chat/app.css curl -i https://agent.nianxx.com/chat/app.js ``` 关闭页面时预期 `/chat`、`/chat/` 以及两个资源均直接为 HTTP 404;开启页面时 预期 `/chat` 为 HTTP 308、跟随后 `/chat/` 为 HTTP 200 `text/html`,两个资源也为 HTTP 200。 使用 `curl -iL https://agent.nianxx.com/chat` 可直接跟随规范化重定向。浏览器 打开 `https://agent.nianxx.com/chat/`,手动输入受控测试 Token,发送不含敏感信息的问题, 再发送第二轮问题确认会话复用。 回滚优先使用配置回滚:把 `FIRE_SAFETY_CHAT_PAGE_ENABLED` 改回 `false`,执行 `docker compose up -d --force-recreate --no-build`,确认 `/chat`、`/chat/` 和两个资源路径都直接 返回 404,之后按需继续保留 兼容 completion 或将其一并关闭。若是版本回滚,切回维护者指定的已验证 revision 后重建;若是 Nginx 配置回滚,恢复 root-only 备份,先执行 `nginx -t` 再 reload。不得通过恢复全路径 `/`、 关闭 Go 鉴权或把 Token 写进 Nginx 解决问题。 ## 9. 相关文档 - [`../architecture/public-chat-entry-v1.md`](../architecture/public-chat-entry-v1.md) - [`../workflows/user-chat.md`](../workflows/user-chat.md) - [`../project/operations/docker-test-deployment.md`](../project/operations/docker-test-deployment.md) - [`../project/operations/nginx-public-entry.md`](../project/operations/nginx-public-entry.md) - [`fire-safety-ymd-dashscope-compatible-chat-v1.md`](fire-safety-ymd-dashscope-compatible-chat-v1.md)