Files
SuperBizAgent-java/openspec/changes/archive/2026-07-22-single-react-chat-sse-cutover/design.md
T

8.7 KiB
Raw Blame History

Context

公开 Chat 仍有同步 /api/chat 和伪流式 /api/chat_stream 两条旧 ChatService 链路;Controller 直接读取 Session 历史、获取模型/Tools、选择策略并维护 cached thread pool。阶段 6A 已完成 protocol-neutral ChatApplicationUseCase、observer 和 ChatRunControl,但阶段 2-6A 的 plain Java components 尚未形成生产 Bean graph。前端同时保留 quick JSON 与 stream SSE 两套消费者。

本 change 是 L4 破坏性协议迁移。ISS-014 已冻结 event schema、无兼容分支和阶段 7 清理边界;本阶段必须原子切换 Controller、生产装配与前端,不能出现旧路径绕过 release boundary。

Goals / Non-Goals

Goals:

  • 唯一 POST /api/chat 返回 named SSE events,固定顺序和互斥规则。
  • Controller 只负责校验、SSE/HTTP 和连接生命周期,业务执行只调用 ChatApplicationUseCase。
  • 使用同一个 Run control 处理断开/timeout/send failure,并禁止断开后的终态发送。
  • 生产装配阶段 2-6A 的 Core、Tools、Agent、Guards、executors、store 和 use case。
  • 使用集中配置与 Spring-managed bounded executors。
  • 前端唯一 consumer 严格消费新契约并保存 exact session/run IDs。

Non-Goals:

  • 不做 Token streaming 或最终内容切片。
  • 不修改 /api/ai_ops 的 URL、请求或事件协议。
  • 不物理删除旧多 Agent、ChatService、Hooks 或 ThreadLocal;阶段 7 处理。
  • 不做断线续传、event replay、WebSocket、轮询或 live E2E。

Decisions

1. Named SSE event 是唯一协议层类型

新增 immutable SSE payload:metadata(session_id,run_id)、status(code,message)、content(content_type,payload)、failure(code,message)、done(outcome)。SseEmitter.event().name(type).data(payload) 直接使用业务 event name,不再发送旧 message + {type,data} 包装。

替代继续用统一 message event;拒绝,因为它允许 type 漂移并让浏览器保留旧兼容分支。content.payload 直接引用阶段 6A sealed typed content,禁止 Map/Object 临时协议。

2. 每个连接使用显式 first-terminal-wins SSE session

新增 protocol adapter 持有 emitter、AtomicReference<ChatRunControl>、disconnect flag、metadata flag 和 terminal flag。onStarted 固定发送 metadata;若 disconnect 已先发生则立即取消新得到的 Run control。status 只能在 metadata 后、terminal 前发送。success 原子发送 content + done 后 complete;failure 原子发送 failure + done 后 complete。

onTimeout、onError、异常 send 和非正常 onCompletion 只标记 disconnected 并调用 cancelClientDisconnect();不尝试补发 failure/done。正常 complete 在回调前标记 terminal,避免把成功误判为断开。替代仅在 IOException catch 取消;拒绝,因为 timeout 和 disconnect-before-onStarted 会漏掉。

3. 请求接纳失败不进入 SSE 事件序列

空 query 在创建 emitter/Run 前返回 HTTP 400;bounded worker rejection 在创建 Run 前返回 HTTP 503。五事件顺序只适用于已接纳并成功持久化 Run 的请求,因而 metadata 始终是第一个 SSE event。Session ID 格式错误由 use case 形成有 Run 的稳定 failure,除非它在 startRun 前失败;Controller 只做 query shape 校验。

4. Chat Controller 不拥有模型、Tools、Session 或业务执行

删除同步 Chat 方法、/chat_stream、ChatResponse、历史 helper、内容切片和 Chat 使用的 ChatService/SessionManager/ChatModel/ToolCallbackProvider。Controller 构造注入 ChatApplicationUseCase 和 qualifier Chat TaskExecutor。

同一类中的 /api/ai_ops 仍需旧模型/Tools。为保持其公开协议不变又清理 Controller 依赖,向 AiOpsService 增加内部 overload,由 service 注入 ChatModel/ToolCallbackProvider 并调用既有执行方法;Controller 只传 request/session/run。替代本阶段迁移 AiOps 到新 use case;拒绝,因为超出阶段 6B。

5. Production assembly 使用一个显式 Bean graph

新增 HarnessChatConfiguration,按依赖顺序装配:

properties/Clock/executors
  -> DiagnosisHarnessCore + HarnessRetryExecutor + GuardModelCall
  -> RedisCanonicalInvocationStore + ToolBoundary
  -> RAG/log/MySQL adapters + HarnessEvidenceTools
  -> DiagnosisAgentFactory/UseCase
  -> EvidenceGuard/Repair + SemanticGuard + ReleaseUseCase
  -> Router + System/Knowledge/Diagnosis executors
  -> ChatApplicationUseCase(JpaChatRunStore)

所有模型入口复用同一 Spring ChatModel 和 model executor。RAG/log adapters 复用现有 LookupKnowledgeTool/QueryLogsTools legacy executors,但所有 Agent-facing output 仍经过 ToolBoundary projection。MySQL 从 MysqlToolProperties 生成独立 datasource map;空配置时 Tool 仍注册但 fail closed 为 unknown datasource。

6. 并发与预算全部集中配置

新增 harness.chat configuration properties,覆盖 Chat worker/model pool、Run deadline/budget、canonical TTL/bytes、Agent/Router/Guard/single-turn/Knowledge limits 和 key prefix。worker 与 model executor 均使用 bounded ThreadPoolExecutor + AbortPolicy,由 Spring 负责 shutdown;Controller 不持有或创建 executor。

Retry attempt audit 首版写结构化 debug log,不新增持久化表。替代硬编码构造参数;拒绝,因为阶段 0 已要求上限集中可校准。

7. 前端删除 quick/stream 双轨

移除 mode selector DOM、currentMode、sendQuickMessage 和旧 /chat_stream parser。sendMessage 只调用 sendChatMessage,POST /api/chat 并按空行分隔完整 SSE frame,解析 named event 与 JSON payload。metadata 更新 trace target;status 更新等待说明;content 一次渲染 typed payload;failure 显示安全消息;done 校验 outcome 并结束。

typed payload 使用专用 renderer 转换 SYSTEM_CHAT、KNOWLEDGE、DIAGNOSIS_REPORT、FALLBACK,不把对象隐式拼接为字符串。unknown event、重复 metadata/content/failure/done、content/failure 同时出现或流结束前无 done 均 fail closed。

Module and Ownership Audit

Browser fetch POST /api/chat
  -> ChatController (validation + connection admission)
  -> ChatSseSession (event order + connection cancellation)
  -> bounded chat worker
  -> ChatApplicationUseCase (session/run/routing/dispatch/terminal persistence)
  -> Harness Core / Agent / Tools / Guards
  -> typed content or stable failure
  -> ChatSseSession (single content|failure + done)
  • Controller owns HTTP/SSE only;SSE session owns event state;Application owns business Run;Core owns cancellation/budget;Store owns MySQL;Harness configuration owns infrastructure graph。
  • 数据真理源仍为 diagnosis_run;SSE metadata 只透出同一 use case 生成的 IDs。
  • 最大生命周期风险是 disconnect 与 onStarted 竞态;atomic control + pending disconnect 解决。
  • 最大部署风险是 L4 前后端不同步;Controller/前端必须同一 commit 原子部署,回滚也必须成对。
  • 无 ADR 冲突;ISS-013 的 token-like “incremental content”被更新的 ISS-014 明确收敛为实时 status + 单次安全 content。

Interface Impact

  • Level: L4 breaking HTTP/frontend contract。
  • Removed: synchronous JSON POST /api/chat、POST /api/chat_stream、旧 ChatResponse 和 message-wrapper Chat SSE。
  • Added: SSE POST /api/chat with five named events and exact snake_case payload fields。
  • Consumers: bundled static/app.js、外部 Chat API callers、Controller tests/MockMvc;Trace/feedback continue using metadata IDs。

Risks / Trade-offs

  • [客户端仍调用旧 endpoint] -> 同 commit 迁移 bundled frontend;外部消费者必须按 L4 文档迁移,不提供兼容层。
  • [发送中断后 worker 继续] -> every send failure/timeout/error cancels exact Run control;late result blocked by Core。
  • [executor 饱和] -> bounded queue + HTTP 503 before Run;不 caller-runs、不无界排队。
  • [Spring graph 缺 Bean/歧义] -> focused configuration context test + Maven compile。
  • [legacy Tool side effects] -> Agent-facing output 仍经新 boundary;legacy classes stage 7 physically removed。
  • [typed diagnosis payload frontend rendering复杂] -> deterministic renderer and JS contract tests/static assertions。

Migration Plan

  1. 部署前确保 V012 已迁移,Redis 与模型配置可用。
  2. 同一版本注册 Harness Bean graph、切换 /api/chat SSE、删除 /chat_stream 并更新 bundled frontend。
  3. smoke/focused tests 验证 metadata/status/content|failure/done、same IDs 和 cancellation;live E2E 延后阶段 7。
  4. 回滚必须同时回滚 Controller 与 frontend 到阶段 6A commit;数据库 V012 nullable additive 可保留。

Open Questions

  • None。初始数值为可配置默认值,阶段 7 根据 live trace 校准。