feat(thread-chat): 持久化后台生成,支持刷新恢复与消息版本操作 - #30
Conversation
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
|
Important Review skippedToo many files! This PR contains 555 files, which is 255 over the limit of 300. To get a review, reduce the PR to 300 files or fewer by splitting it into smaller PRs or changing its base branch. Usage-priced reviews support at most 300 files. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Team Run ID: ⛔ Files ignored due to path filters (5)
📒 Files selected for processing (555)
You can disable this status message by setting the Comment |
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
@coderabbitai review |
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
|
Seems you are using me but didn't get OPENAI_API_KEY seted in Variables/Secrets for this repo. you could follow readme for more information |
Before: legacy retry and retryAssistant duplicated source lookup, parent-user resolution, IDs, regenerate-assistant intent, append-only patch preparation, and not-found/not-latest policy in the controller. After: net/regeneration-command.ts owns pure assistant retry preparation and exposes a prepared start consumed by one controller helper. Assistant-retry policy strings/intents in the controller are zero and it shrank from 505 to 457 lines. Automated: Prettier, TypeScript, targeted ESLint, valid sibling patch, missing/wrong-role source, non-latest source, legacy/new controller wiring, and an architecture assertion passed. Manual: blocked before task-space creation because ego-browser could not connect to the ego_cli bootstrap. Per repository policy no alternate browser was used, and this check is not reported as passed.
Before: retryUserTurn created IDs, built retry-orphan-user intent, prepared the domain patch, mapped rejection, detached, and started generation inline in the controller. After: net/regeneration-command.ts owns pure orphan-user retry preparation; the controller supplies IDs and consumes the shared prepared start. Orphan retry intent/error policy in the controller is zero and it shrank from 457 to 439 lines. Automated: Prettier, TypeScript, targeted ESLint, active orphan success, completed-turn rejection, missing-user rejection, assistant-retry regression, controller wiring, and an architecture assertion passed. Manual: blocked because ego-browser remained unavailable after the prior ego_cli bootstrap connection failure. Per repository policy no browser retry or alternate browser tool was used, and this check is not reported as passed.
Before: editAndRegenerate created IDs, built edit-last-user intent, prepared the domain patch, mapped rejection, detached, and started generation inline in the controller. After: net/regeneration-command.ts owns pure user-edit preparation, including trim and quote-preserving append-only messages; the controller supplies IDs and consumes the shared prepared start. Direct domain patch construction/user-edit policy in the controller is zero and it shrank from 439 to 420 lines. Automated: Prettier, TypeScript, targeted ESLint, trim/source/quote preservation, original-state immutability, empty/non-latest rejection, both retry preparation regressions, controller wiring, and an architecture assertion passed. Manual: blocked because ego-browser remained unavailable after the ego_cli bootstrap connection failure. Per repository policy no alternate browser was used, and this check is not reported as passed.
Before: ChatView and CanvasExpand each owned an uncontrolled textarea, auto-grow limit, prefill focus, IME Enter guard, send/stop buttons, and model selector wiring. The same interaction capability could drift across column and canvas surfaces. After: ConversationComposer owns the common behavior and exposes explicit column/canvas variants plus host callbacks. The canvas keeps its prior missing-state behavior: the textarea remains visible, the model selector is absent, and unavailable actions do not clear the draft. Pure submission and keyboard rules live in conversation-composer-logic for isolated verification. ChatView shrinks from 316 to 250 lines; canvas-node shrinks from 430 to 372 lines. Automated verification: pnpm typecheck; targeted ESLint on the changed source/test files; node --import tsx e2e/thread-chat/conversation-composer.test.mjs; node --import tsx e2e/thread-chat/message-actions-controller.test.mjs; static duplicate assertion confirms neither host retains textarea, auto-grow, IME, or composer-root implementation; git diff --check. Manual case: intended to verify column and canvas Enter, Shift+Enter, Chinese IME confirmation, prefill focus, model selector, send and stop parity at localhost:4040. Not executed because the mandated ego-browser bootstrap is unavailable; no alternate browser tool was used.
Before: ChatView and CanvasExpand independently rendered the same message role shell, pending/streaming visibility rules, background status, typing indicator, caret, error retry, editable user action, assistant toolbar, feedback, and turn variants. Fixes could drift between column and canvas surfaces. After: ConversationMessage owns one message-level capability and accepts explicit slots for user fallback, assistant body, and post-message artifacts. Column-only role labels and bubble spacing remain props; canvas keeps its hidden user fallback when actions are unavailable and retains AnchoredMarkdown/artifact ownership. Pure conversation-message-logic centralizes the assistant presentation matrix. ChatView shrinks from 250 to 142 lines; canvas-node shrinks from 372 to 288 lines. Automated verification: pnpm typecheck; targeted ESLint on changed source/test files; node --import tsx e2e/thread-chat/conversation-message.test.mjs covers waiting, streaming text, web research, and artifact-only states; node --import tsx e2e/thread-chat/message-actions-controller.test.mjs; static assertion confirms both hosts no longer import or implement generic message roles, status UI, or action components; git diff --check. Manual case: intended to compare column and canvas for user edit/recovery, empty pending typing, streamed caret, background label, failed retry, copy/feedback/regenerate, variant switching, web research, and artifact cards. Not executed because the mandated ego-browser bootstrap remains unavailable; no alternate browser tool was used.
Before: the composite BranchableChat page module also exported the low-level AnchoredMarkdown renderer, so canvas-node imported an upper-level branching screen merely to reuse Markdown, smooth streaming, anchor painting, and click delegation. This coupled orchestration to a composite UI module and risked a file-level cycle. After: branching/anchored-markdown.tsx owns that atomic presentation capability. BranchableChat and CanvasExpand compose it directly; branchable-chat now contains only branch-column assembly. The MarkdownBody ownership comment follows the new module. BranchableChat shrinks from 480 to 311 lines. Automated verification: pnpm typecheck; targeted ESLint on the three component modules; node --import tsx e2e/thread-chat/text-anchor.test.mjs covers position, exact, fuzzy, threshold, loss, and substring recovery; architecture assertions confirm orchestration no longer imports branchable-chat and branchable-chat no longer owns MarkdownBody, smooth text, or DOM anchor painting; git diff --check. Manual case: intended to verify stable and streaming Markdown in both column and canvas, research panel insertion offset, anchor highlight/footnote restoration, click navigation, and Command/Ctrl-click keep-source behavior. Not executed because the mandated ego-browser bootstrap remains unavailable; no alternate browser tool was used.
Before: BranchableChat and CanvasExpand each rebuilt the same AnchoredMarkdown plus WebResearchPanel composition, including the activity gate, recorded text offset fallback, route/plan projection, and completion state. After: AnchoredAssistantBody exposes that atomic rich-reply capability to both surfaces. webResearchPlacement owns and tests the rule that no panel appears before the first activity and that activity insertion uses the recorded offset or zero fallback. BranchableChat shrinks from 311 to 296 lines; canvas-node shrinks from 288 to 271 lines. Automated verification: pnpm typecheck; targeted ESLint; node --import tsx e2e/thread-chat/web-research-placement.test.mjs; node --import tsx e2e/thread-chat/generation-persistence.test.mjs; architecture assertion confirms neither host directly owns WebResearchPanel, research offsets, or AnchoredMarkdown composition; git diff --check. Manual case: intended to compare no-activity routing, first search/read event insertion, recorded mid-answer insertion offset, streaming research progress, completion copy, and anchor navigation in both column and canvas. Not executed because the mandated ego-browser bootstrap remains unavailable; no alternate browser tool was used.
Before: BranchableChat and CanvasExpand independently selected artifact ids, skipped missing records, resolved source-thread depth, rendered Markdown progress, mapped completed cards, wired open actions, and applied canvas compact mode. After: MessageArtifacts owns that message-level collection capability with compact as the only surface variant. It preserves progress without state/actions and exposes completed cards only when both records and an open action exist. selectMessageArtifacts keeps declared order while omitting unavailable records. BranchableChat shrinks from 296 to 277 lines; canvas-node shrinks from 271 to 248 lines. Automated verification: pnpm typecheck; targeted ESLint; node --import tsx e2e/thread-chat/message-artifacts.test.mjs; node --import tsx e2e/thread-chat/generation-persistence.test.mjs; architecture assertion confirms neither host directly imports artifact card primitives, maps artifact ids, or reads the artifact table; git diff --check. Manual case: intended to compare starting and streaming Markdown progress, compact canvas presentation, multiple completed artifacts in message order, missing artifact omission, source-depth color, and drawer opening in both surfaces. Not executed because the mandated ego-browser bootstrap remains unavailable; no alternate browser tool was used.
- keep bubble open on outside click / scroll / resize while draft exists - ignore new text selections with a draft (copy-paste into the composer) - Esc with a draft opens a confirm dialog and shakes the bubble - entering/shake animations moved off steady-state to avoid tc-pop replay flash - draft-guard styles co-located with the component
- focus the question textarea only after the measured bubble becomes visible - replay the entrance transition for every new selection - compensate textarea borders to avoid phantom overflow - reuse a global slim scrollbar utility with Firefox support
…esign spec(project): 增加 Project 功能
…luation feat: agent observability evaluation
refactor: arch and domain redesign
关联 Issue
Refs #29
合并依赖
Blocked by #34
PR #30 在 Issue #34 完成前不得视为完成,也不得转为 Ready for review 或合并。
计划中的合并链路:
p0-resume-messages为目标分支。p0-resume-messages。p0-resume-messages继续作为 PR feat(thread-chat): 持久化后台生成,支持刷新恢复与消息版本操作 #30 的汇总分支,最后由 PR feat(thread-chat): 持久化后台生成,支持刷新恢复与消息版本操作 #30 整体进入主干合并链路。PR #30 只有在 Issue #34 已完成、相关 OpenSpec/ADR 已收口、迁移和回归验证通过后,才可以重新标记为 Ready for review。
本 PR 解决 Issue #29 中“客户端断连后任务继续执行、刷新恢复最终结果、重试不重复消息/计费”的韧性部分;request/error ID、跨层 trace、结构化 telemetry 与告警仍留在 Issue #29 后续完成。
背景
此前 thread-chat 的回复流由浏览器直接持有:用户刷新、关闭标签页或网络抖动后,前端 SSE 会断开,正在生成的 assistant 回复可能整条丢失;即使模型随后完成,页面也无法恢复最终结果。异常状态还可能留下只有 user message、没有可恢复 assistant 的“孤儿轮次”。
这个 PR 将生成任务从浏览器生命周期中解耦,同时补齐消息编辑、重新生成、版本切换、反馈和失败恢复能力。
主要改动
1. 服务端持有 generation 生命周期
branch_generationssidecar,记录 attempt、current 状态、turn snapshot、heartbeat、结构化结果、usage 和 billing 状态。2. 刷新后恢复后台状态和最终回复
3. 不可变消息 DAG 与消息操作
parentMessageId和activeLeafMessageId表达回复版本。4. Message feedback 与 generation 解耦
messageId,不再要求浏览器持有generationId。branch_message_feedback和 owner-scoped message feedback API。done的 assistant message 必须能在服务端反查到 completed generation。5. 并发、所有权与一致性保护
branch_trees.revision+ CAS 防止旧标签页用整树快照覆盖新节点、版本选择或 Artifact。数据库与 API
新增/调整:
branch_generationsbranch_message_feedbackbranch_trees.user_id/branch_trees.revisionusage_records.app_generation_idGET /api/branch-generations/:generationIdPOST /api/branch-generations/:generationId/stopPATCH /api/branch-trees/:treeId/active-leafPUT /api/branch-trees/:treeId/messages/:messageId/feedback部署与兼容性
pnpm db:migrate,再发布依赖新 schema 的服务端和客户端。详细设计与行为契约:
openspec/changes/persist-thread-chat-generations/openspec/changes/add-thread-chat-message-actions/验证
已覆盖:
执行检查:
pnpm typecheckpnpm lintpnpm openspec:validatenext build --webpackego-browser nodejslocalhost end-to-end verification