ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

DeepChat IM 风格 Steer 消息实现方案:把“转向命令“变成带已读回执的持久用户消息

DeepChat IM 风格 Steer 消息实现方案:把“转向命令“变成带已读回执的持久用户消息 DeepChat IM 风格 Steer 消息实现方案把转向命令变成带已读回执的持久用户消息【免费下载链接】deepchatDeepChat - A smart assistant that connects powerful AI to your personal world项目地址: https://gitcode.com/GitHub_Trending/dee/deepchat本文基于 DeepChat 仓库中的实现计划文档 IM-style Steer Messages Implementation Plan完整解析IM 风格 Steer 消息这一功能的设计决策与落地方案为什么要把运行时中的转向Steer输入从一条后台命令改造成一条带Unread - Read回执的标准用户消息持久层如何用两个 SQLite 事务接受、认领保证消息顺序与原子性DeepChat 与 ACP 两种后端如何在安全边界上完成回合交接渲染器如何用类型化路由响应与批量消息变更事件驱动 UI。读完本文你将掌握该功能从共享数据契约、会话数据生命周期、运行时会话交接到 Vue 组件呈现的完整技术链条并能对照仓库中的真实源码与测试验证每一处设计。配套的功能规格见 IM-style Steer Messages Spec。1. 工程背景问题诊断与目标1.1 现状痛点改造前的 Steer 路径存在两套割裂的表达组合器composer显示一个在途 Steer转圈待处理输入栏pending rail显示一条被锁定的 Steer 行而用户真正发出的内容要等到下一个回合开始才作为普通消息出现。这使得 Steer 更像一条后台命令而不是消息用户无法可靠回答三个基本问题我的消息发出成功了吗运行时接纳它了吗哪一条助手回复是在回应它1.2 诊断Diagnosis计划文档给出了明确的根因判断根因Steer 被表达为一条待处理命令pending command而不是一条持久的用户消息DeepChat 使用的是通用取消generic cancellation而不是消息/回合交接message/turn handoff。正确的所有权层次pending-input 聚合必须原子地拥有接受acceptance与认领claim两个动作消息列表只负责渲染结果产生的 transcript 事实。受影响的消费者DeepChat、ACP、会话数据、路由契约、渲染器消息缓存、组合器、pending 通道、消息组件。关键约束新接受的 Steer 绝不能被排到当前回合的持久用户事实之前也不能被排到为其响应预留的助手行之后。可复用的既有模式现有 Steer 负载合并payload merge与 pending-input 优先级机制。1.3 决策Decision最终选定的方案可概括为四句话只保留一个在途 Steer 批次open batch每一条被接受的提交都链接到它自己的用户消息批次只被认领claim一次认领时同时预留一条独立的助手行。各维度的影响面评估维度影响状态两张表加两个列 一个可选的消息元数据字段DOM在现有固定高度的MessageInfo行内加一个短回执 span渲染不引入独立 Steer 列表、不引入新的全局 store、不引入宽泛的 stream watcherIPC扩展 Steer 路由响应 新增一个批量消息变更事件依赖无新增运行时依赖1.4 上下文地图Context Map计划文档明确列出了各关注点的所有者与约束这是理解后续所有改动的前提Vue 组件链ChatPage-MessageList/MessageListRow-MessageItemUser-MessageInfo渲染链一个权威有序的DisplayMessage[]由既有的消息虚拟化层做窗口化状态源SQLite 的 pending-input 行与 transcript 行派生状态MessageMetadata.inputReceipt即显示回执事件一个类型化的 session-message 变更事件同时更新活动视图与缓存视图布局约束回执变化绝不能改变行高或触发滚动校正性能敏感区高频助手流式更新与虚拟化消息列表无障碍回执行告announcement、焦点保持、reduced motion、准确的 blocker tooltipElectron 边界类型化路由响应 类型化事件Vue 不直接接触原始 IPC。2. 总体架构一次 Steer 的完整时序计划文档用一张时序图描述了整条链路参与者包括用户、渲染器、SessionTurn、会话数据、活动后端、pending-input pump触发路径为ChatInputToolbar-useComposerSubmit.onSteer-chat.steerActiveTurn-SessionTurn- 所选后端的 pending-input 运行时。渲染器侧的steerActiveTurn调用链可在 ChatClient 路由 与 chatService、turn.ts 中找到对应实现。3. 共享数据契约3.1 Pending-input 记录两个新字段不引入新的队列或批次实体只在现有记录上扩展export interface PendingSessionInputRecord { // existing fields messageIds: string[] // 链接到本批次的用户消息 ID 列表 assistantMessageId: string | null // 认领时预留的助手消息 ID }规则来自计划文档Queue 记录在被提升为 Steer 之前保持messageIds []且assistantMessageId null新的 Steer 批次从 1 个消息 ID 开始当该记录仍为pending时另一条 Steer 被接受会追加 1 个消息 ID并按既有appendSteerInput行为合并负载assistantMessageId只在认领时精确赋值一次已消费consumed的 Steer 记录保留两个字段用于恢复与诊断。3.2 用户消息元数据回执字段MessageMetadata增加一个可选字段export interface MessageMetadata { // existing fields inputReceipt?: { mode: steer readAt: number | null } }刻意不引入第二个回执状态枚举。状态的来源映射只有两条readAt null - Unread未读 readAt ! null - Read已读持续到渲染器侧的显示截止时间ChatMessageRecord.status继续充当处理围栏关联的 Steer 批次为 pending 或 claimed 时 - pending 关联的批次被消费consumed后 - sent共享类型定义位于 agent-interface.d.ts。3.3 SQLite 迁移对deepchat_pending_inputs表追加一个迁移ALTER TABLE deepchat_pending_inputs ADD COLUMN message_ids_json TEXT NOT NULL DEFAULT []; ALTER TABLE deepchat_pending_inputs ADD COLUMN assistant_message_id TEXT;不新增表和索引理由查找总是从已知的 pending-input 行出发链接的消息 ID 是一个小而有序列表transcript 行仍按常规消息 ID 索引。仓库中的实际实现与计划完全一致deepchatPendingInputs.ts 中既有表定义包含message_ids_json TEXT NOT NULL DEFAULT []与assistant_message_id TEXT两列迁移语句也按此执行迁移在 schemaCatalog.ts 中注册。4. 原子会话数据生命周期生命周期所有权集中在SessionPendingInputs实现见 pendingInputs.ts。给它注入窄的 transcript 与事务操作而不是新增一个协调器类。4.1 接受AcceptanceacceptSteerMessageacceptSteerMessage( sessionId: string, input: SendMessageInput, mergeItemId?: string | null ): { pendingInput: PendingSessionInputRecord message: ChatMessageRecord }在一个 SQLite 事务内完成校验mergeItemId若存在必须是同一会话下一条pending的 Steer创建一条用户消息新的消息 ID、下一个 transcriptorderSeq、status: pending、metadata.inputReceipt { mode: steer, readAt: null }、既有的结构化UserMessageContent创建新的 pending Steer 行或把负载合并进开放行把用户消息 ID 追加进message_ids_json通过SessionTranscript持久化用户内容投影、搜索文档与初始 Tape 事实。事件只在事务提交之后发布。任何写入失败都会同时回滚 pending 行与 transcript 事实路由上报失败渲染器保留草稿。对照源码pendingInputs.ts 第 101–159 行实现比计划更进一步实际签名还接受preStreamAnchorMessageId选项并在事务内通过materializePreStreamSource先把当前回合的源用户事实物化出来再创建 Steer 消息返回值中附带sourceMessage事务提交后调用events.publishMessagesChanged把源消息与 Steer 消息一并推给渲染器——这正是pre-stream 交接保持持久顺序的落点。4.2 认领ClaimclaimSteerBatch用原子操作替代原先独立的 Steer claimclaimSteerBatch( sessionId: string, itemId: string ): { pendingInput: PendingSessionInputRecord userMessages: ChatMessageRecord[] assistantMessage: ChatMessageRecord }在一个 SQLite 事务内重读并校验 pending 行pending - claimed并打上claimedAt关闭该批次的合并窗口给所有链接的用户消息打上同一个inputReceipt.readAt在最后一条链接用户消息之后创建一条新的空助手消息把它的 ID 写入assistant_message_id。这个事务就是已读边界此后被接受的 Steer 必须观察到 claimed 行并在预留的助手行之后开新批次。源码中对应 claimSteerInput第 274–304 行先校验messageIds非空且assistantMessageId未赋值防止重复认领随后在同一事务内createAssistantMessage、claimSteerInput({ claimedAt: readAt, assistantMessageId })、markSteerMessagesRead事务提交后才发布publishMessagesChanged把全部已读的用户消息 新助手行作为一个批量事件推给渲染器。4.3 结算Settlement对已认领的 Steercompleted、aborted、error三种结果都消费 pending 记录源码中为 consumeSteerInput第 376–389 行事务内先settleSteerMessages再consumeSteerInput所有链接的用户消息标记为sent保留readAt为状态迁移追加 Tape 替换事实助手行保持正常的完成/中断/错误投影。计划文档特别强调Steer 越过已读边界之后不再使用 release-after-rollback。一条用户已经看到已读的消息若被自动重放有造成 provider 侧输入重复的风险。Queue 认领保留既有的 release/retry 语义。源码中releaseClaimedInput对已产生消息或助手行的 Steer 直接抛出Read steer input ... cannot be released.与此决策一一对应。4.4 冷启动对账Legacy Reconciliation在会话/运行时恢复时带空messageIds的 pending Steer用其存储的负载创建一条Unread用户消息并挂接没有消息的 claimed 遗留 Steer先恢复回 pending再创建Unread消息blocked 的遗留 Steer转换为 blocked Queue因为它从未通过附件接受consumed 的遗留 Steer不合成第二条历史用户消息。对账必须幂等。recoverInputsAfterRestart第 391–467 行 完整实现了这套规则并且额外覆盖了 Queue 侧的恢复claimed Queue 若已物化用户事实则直接消费否则释放并进入held集合claimed 的 Steer 若已有消息则结算消费否则把未读 Steer 消息置为失败failPendingSteerMessages并消费——即规格中冷重启前未认领的 Steer 内部终结为error、不再显示回执的行为。5. 类型化路由与事件变更5.1 Steer 路由扩展chat.steerActiveTurn的输出{ accepted: boolean message: ChatMessageRecord | null attachmentPreparation?: AttachmentPreparationSummary }使用既有的ChatMessageRecordSchema校验。契约要点accepted: true时总是附带一条已持久化的用户消息——路由响应就是活动渲染器立即插入的权威路径accepted: false时总是message: null。MessageStartResult可以增加可选的userMessage字段使两个后端实现都能通过SessionTurn与ChatService返回同一类型化的结果不得重载messageId它继续表示助手响应身份。这一契约在 pendingInputAdmissionCoordinator.ts 的 steerActiveTurn 中可见无论是活动生成中、pre-stream 中还是开放批次合并分支返回值都携带userMessage: accepted.message。5.2 消息变更事件sessions.messages.changed新增一个事件sessions.messages.changed { sessionId: string messages: ChatMessageRecord[] version: number }它统一承载接受、认领、结算三类投影。渲染器侧的处理规则活动且已提交committed的会话按updatedAt做单调 upsert非活动缓存会话使最近视图失效过期记录忽略事件丢失下一次消息 restore 仍是权威来源。计划文档明确不复用chat.stream.completed来做用户消息生命周期更新——该事件负责助手流结算其请求代数墓碑会把用户回执更新误判为流完成。6. DeepChat 运行时6.1 准入AdmissionPendingInputAdmissionCoordinator中的改动用acceptSteerMessage替换queueVisibleSteerInput持久化activeSteerPendingInputId仅保留为开放批次身份保留当前负载合并 helper从活动生成中的 Steer 路径移除runLifecycle.cancel(sessionId)——即接受 Steer 不再触发通用用户停止取消仅当没有活动回合拥有该会话时才立即调度 pumppre-stream 阶段在 Steer 接受事务内物化或复用当前 claimed Queue 用户事实然后以pending_input为原因中止准备。pre-stream 事务把源用户事实与 Steer 一起发布即使助手行尚不存在也保持持久顺序当权威源记录到达时渲染器用它替换对应的乐观源气泡optimistic source bubble避免重复。源码中该分支以PENDING_INPUT_ABORT_REASON中止preStreamController并通过accepted.sourceMessage更新 pre-stream 锚点第 247–263 行。6.2 安全让出Safe Yield保留既有的循环钩子shouldYieldForPendingInput: () Boolean(pendingInputCoordinator.getNextSteerInput(sessionId))工具批边界返回{ status: completed, stopReason: pending_input }同时确保纯 provider 完成的常规路径在存在等待中的 Steer 时也会调度 pending-input 排空。6.3 Pump 与回合启动当 Steer 拥有优先级时调用claimSteerBatch清空activeSteerPendingInputId用返回的记录构建 claim handle把messageIds与assistantMessageId传入TurnCoordinator.start。TurnCoordinator的 Steer 分支必须加载并校验所有链接的 pending 用户消息用合并后的 pending 负载作为newUserContent把这些 pending 记录排除在历史上下文之外用最后一条链接用户 ID 作为 Memory 与 ViewManifest 元数据的主回合锚点从不调用createUserMessage从不调用常规createAssistantMessage而是使用预留的助手 ID需要时通过createCompactionMessageAtOrderSeq(..., { shiftExistingMessages: true })在第一条链接用户消息之前插入压缩投影流式输出只写入预留的助手消息。普通发送与 Queue 路径保留当前的用户/助手创建行为。6.4 终结行为Terminal Behaviorpending_input终结旧助手消息且不带错误块pre-stream 的pending_input交接删除空的助手预留、把旧操作转回 idle然后由 pump 认领 Steer已认领的 Steer 一定恰好结算一次认领之后、pre-stream 阶段的错误会向预留助手行写入终结性错误新旧助手的请求 ID 在流生命周期跟踪中保持不同。7. ACP 运行时ACP 复用同一套会话数据接受/认领操作只是边界点不同——ACP 没有同循环内的让出钩子因此使用后端特定边界先持久化并显示 SteerUnread若 ACP 投影尚未开始在同一事务内物化 claimed Queue 输入的用户事实若有活动 prompt调用instance.cancel(pending_input)等待活动操作结算排空并认领 Steer 批次启动新 prompt 与新助手消息。新增一个窄的取消原因类型type AcpCancelCause user_stop | pending_inputpending_input取消下兼容投影终结旧助手消息时不追加通用用户取消错误块即不显示common.error.userCanceledGeneration之类的文案。新 prompt 投影把已认领的 Steer 上下文传入AcpAgentInstance.send与AcpCompatibilityProjectionAdapter.begin此时复用链接的用户消息、复用assistantMessageId、不创建重复的 transcript 行、以合并的 pending 负载作为当前 ACP prompt、ACP 事件只流入预留助手行。直接 ACP 发送与常规 Queue 认领保持原投影创建路径不变。ACP 侧改动入口可见 acpAgentRuntime.ts其内部同样调用了acceptSteerMessage/ 认领操作。跨后端共同不变量两个后端都必须维持旧助手行已存在时 oldAssistant.id ! newAssistant.id oldAssistant.orderSeq every steerUser.orderSeq newAssistant.orderSeq 助手行尚不存在时 sourceUser.orderSeq every steerUser.orderSeq newAssistant.orderSeq且新回合的任何流更新都不得指向oldAssistant.id。8. 渲染器状态与 IPC 绑定8.1 消息 store把既有的内部 upsert 行为暴露为一个聚焦 actionapplyPersistedMessageRecords(records: ChatMessageRecord[]): void实现要求变更可见缓存前必须先有活动的 committed 会话忽略比缓存updatedAt更旧的记录messageIds保持按orderSeq排序只为变更的记录使解析后的元数据失效持久化版本号按批递增一次而不是按条记录。并在既有 store IPC 绑定中订阅sessions.messages.changed含清理逻辑。store 侧回执的派生逻辑分布在 message.ts 与显示模型 displayMessage.ts / useDisplayMessages.ts 中。8.2 组合器ComposeruseComposerSubmit的处理顺序与今天一样捕获草稿等待chatClient.steerActiveTurn接受成功后 upsertresult.message清除对应草稿修订发起一次受保护的滚动到底请求。保留内部steeringSessionIds互斥或改用既有 dispatch token但移除其可见的转圈角色。计划文档明确不加入乐观 Steer 行本地 IPC 同步 SQLite 接受事务能很快返回权威 ID 与顺序避免临时 ID并保证每条可见 Steer 都能活过重载。8.3 Pre-stream 准入Steer 可用性跟随活动回合状态会话处于 generating 且没有交互/准备门控阻止提交首条流式更新之前渲染器走常规路径提交 Steer主进程先持久化当前用户事实Steer 以Unread出现旧的 pre-stream 操作以pending_input结束下一条助手行从 Steer 之下开始。不引入乐观行也不加入仅渲染器侧的顺序规则。9. 渲染器呈现9.1 显示模型在useDisplayMessages中解析metadata.inputReceipt为用户显示消息加一个窄字段type DisplayInputReceipt { mode: steer readAt: number | null }不要把整条 pending-input 记录传给消息行。9.2 用户消息组件MessageItemUser.vue实现见 MessageItemUser.vue只从inputReceipt派生Unread或Read截止时间后不再渲染回执只在最近readAt可见时持有唯一截止时间定时器元数据变化与卸载时清除定时器把message.status pending视为破坏性工具栏操作的只读态把回执传给MessageInfoCopy 通过既有MessageToolbar只读行为保持可用。MessageInfo.vue接受可选回执 prop在既有h-4flex 行中紧邻时间戳渲染仅对Read迁移使用aria-livepolite只用 opacity 过渡 tokenreduced motion 下禁用过渡。无需新组件。时间参数与规格一致Read显示到readAt 1500 ms150 ms 纯透明度淡出源码中READ_RECEIPT_VISIBLE_MS 1500即定义在 MessageItemUser.vue剩余时长按持久化的绝对readAt计算截止时间已过则不渲染回执——超时只是渲染器行为没有延迟数据库写入去清除回执。9.3 Pending 通道Pending Lane重构PendingInputLane.vue为只接受/渲染 Queue 项删除 Steer 头部计数与行模板保留 Queue 控件与 blocked Queue UIshowLane只依赖 Queue 项。pendingInputStore.steerItemsgetter 仅在仍有非视觉门控消费时保留否则在所有调用点迁移后移除。9.4 工具栏ChatInputToolbar.vue移除 Steer 转圈与aria-busy图标与标签保持稳定保留真实附件准备导致的禁用移除仅 pre-stream 阶段的禁用 tooltip。9.5 布局与滚动归属回执留在固定高度的MessageInfo行内出现/消失不改变虚拟化行高、不触发滚动校正一次成功的本地 Steer 提交只通过既有聊天滚动控制器请求一次滚动到底且限定在同一 committed 会话与 restore epoch 内回执变化从不调用scrollToBottom。9.6 无障碍回执是文本而非纯颜色状态Read迁移使用限定在回执上的 polite 活区播报流式渲染期间不重复播报Unread气泡与复制保留既有键盘行为接受后组合器保留焦点真实 blocker附件准备、权限、会话不可用保留既有禁用 tooltip。10. 顺序与竞态处理10.1 接受 vs 认领在既有的每会话 pending-input 边界上串行化accept S1 - 在开放批次中提交消息 S1 accept S2 - 在同一开放批次中提交消息 S2 claim - 关闭批次 S1/S2 置已读 预留助手 B accept S3 - 在助手 B 之后开新批次任何路由都不得向claimed批次追加消息。10.2 流事件助手 A 的迟到更新只在 A 的常规终结事件之前被接受助手 B 使用新的请求/消息身份在messageIpc中开启新的流代数既有请求墓碑在 B 成为当前后拒绝 A 的迟到更新。10.3 会话切换路由结果只在提交会话仍是 committed 时写可见缓存否则使该会话最近视图失效由 restore 加载持久记录回执截止时间使用持久化的readAt从不使用挂载时长。10.4 停止与上一回合错误用户 Stop 先结算当前助手然后允许UnreadSteer 批次排空上一回合的终结性错误同样调度持久 Steer除非仍存在权限/问题 blocker不会因上一个回答失败就静默丢弃 pending 消息。11. 文件级变更地图计划文档给出的变更地图如下它是地图不是必须触碰每个文件的要求——若既有 port 已承载所需数据则不必改动区域主要文件变更共享类型src/shared/types/agent-interface.d.tsPending 链接与回执元数据路由契约src/shared/contracts/routes/chat.routes.ts返回被接受的用户消息事件契约src/shared/contracts/events/sessions.events.ts批量消息变更事件Pending 表src/main/session/data/tables/deepchatPendingInputs.ts新增两列/迁移Schema 目录src/main/data/schemaCatalog.ts注册迁移Pending storesrc/main/session/data/pendingInputStore.ts编解码链接、关闭合并窗口Pending 生命周期src/main/session/data/pendingInputs.ts原子 accept / claim / settle / reconcileTranscriptsrc/main/session/data/transcript.tsPending 用户创建、回执/状态更新会话组合src/main/session/data/index.ts注入窄的 transcript/事务/事件 port会话路由src/main/session/chatService.ts、turn.ts、routes.ts携带被接受消息DeepChat 准入src/main/agent/deepchat/runtime/pendingInputAdmissionCoordinator.ts不带活动取消地持久化DeepChat pumpsrc/main/agent/deepchat/runtime/pendingInputPump.ts认领批次、预留助手DeepChat 回合src/main/agent/deepchat/runtime/turnCoordinator.ts复用可见事实/新助手ACP 运行时src/main/agent/acp/instance/acpAgentRuntime.ts带原因感知的交接与排空ACP 实例src/main/agent/acp/instance/acpAgentInstance.ts复用已认领投影事实ACP 投影src/main/agent/acp/compatibility/adapters.ts无重复行/无取消错误渲染器客户端src/renderer/api/ChatClient.ts、SessionClient.ts新响应/事件绑定消息 storesrc/renderer/src/stores/ui/message.ts、messageIpc.ts单调持久化 upsert组合器src/renderer/src/features/chat-page/composables/useComposerSubmit.ts插入被接受消息显示模型displayMessage.ts、useDisplayMessages.ts派生回执消息 UIMessageItemUser.vue、MessageInfo.vue渲染回执、锁定操作Queue UIPendingInputLane.vue、ChatPage.vue移除 Steer 栏工具栏ChatInputToolbar.vue移除可见 Steer 加载i18nsrc/renderer/src/i18n/*/chat.json回执文案未读/已读12. 测试策略计划文档按层规划了聚焦测试这里保留其完整清单作为验收基线12.1 会话数据新 Steer 批次 用户消息的原子创建两条不同链接消息 ID 下的负载合并事务回滚后两个行都不存在认领对全部消息打同一个readAt且只创建一个助手认领后的接受在该助手之后开新批次消费把所有链接用户消息置sentpre-stream 接受先物化并链接 claimed 源用户再接纳 Steer遗留 pending/claimed/blocked 对账的幂等性迁移默认值与 schema catalog。12.2 DeepChat活动中的 Steer 不调用通用 run 取消纯内容回合在 provider 完成后排空工具循环在当前工具批后以pending_input让出新旧助手 ID 不同合并负载只供给一次链接的 pending 用户消息被排除在历史之外压缩分隔线插在第一条 Steer 消息之前pre-stream Steer 以pending_input取消准备、保持源/Steer 顺序、不留空助手行认领后的 pre-stream 失败保留用户事实并写入助手错误Stop 与上一回合错误仍会排空UnreadSteer。12.3 ACPSteer 消息在取消前持久化pre-projection Steer 在取消前物化 claimed 源用户pending_input取消结算且无用户取消错误文案认领等待活动 ACP 操作新投影复用链接用户消息与预留助手无重复 transcript 行取消失败时 Steer 保持Unread。12.4 渲染器 store 与 composables路由接受消息按真实 ID 与orderSeq插入过期会话结果不变更活动视图消息变更事件 upsert 较新记录、忽略过期记录非活动会话的缓存失效被接受 Steer 清除匹配草稿并请求一次滚动失败时保留草稿无可见 Steer 转圈会话 generating 期间 pre-stream Steer 始终可用。12.5 组件Unread回执Read迁移1.5 秒截止时间与 150 ms 淡出类过期恢复的回执不渲染reduced-motion 样式回执不增加行高pending Steer 工具栏仅暴露 CopyQueue 栏不再渲染 Steer 行且 Queue 控件完整。12.6 端到端使用确定性测试 provider启动助手 A 并挂起其流以 Steer 提交 S1 与 S2断言可见顺序助手 A, S1, S2释放安全边界断言两条回执都变为Read断言助手 B 拥有不同 ID 且位于 S2 之下发出助手 A 的迟到更新断言其被忽略推进定时器断言回执在无滚动跳动的情况下消失。同一顺序断言需通过一个 ACP fixture 再跑一遍。13. 交付切分与验证门禁实现按四个可评审切片推进切片 1持久化与契约迁移、共享类型、transcript 操作、原子 pending 生命周期、路由结果与消息变更事件、会话数据测试。建议提交feat(chat): persist steer messages切片 2运行时交接DeepChat 安全让出与已认领消息复用、ACP 带原因取消与投影复用、主进程测试。建议提交feat(chat): split steer response turns切片 3渲染器交互消息 store 事件/upsert、组合器结果处理、Queue-only 通道、回执 UI、操作锁定、i18n、pre-stream 准入、渲染器测试。建议提交feat(chat): render steer receipts切片 4回归收尾端到端顺序与重启覆盖、移除死掉的 Steer 栏/转圈代码、行为落地后更新任务状态与保留的架构文档。建议提交test(chat): cover steer lifecycle。交接前需运行最小相关套件完整验证门禁为pnpm run format pnpm run i18n pnpm run lint pnpm run typecheck pnpm exec vitest run test/main/session pnpm exec vitest run test/main/agent pnpm exec vitest run test/renderer/stores/messageStore.test.ts pnpm exec vitest run test/renderer/features/chat-page/composables/useComposerSubmit.test.ts pnpm exec vitest run test/renderer/components/ChatPage.test.ts并手动验证DeepChat 与 ACP 两条后端文本、文件、提及、活动 Skills快速连续 SteerQueue 提升Stop、错误、会话切换、恢复、重启明暗主题键盘焦点与读屏播报reduced motion窄窗口与调整大小的聊天视图滚动位置与虚拟行稳定性。14. 小结这套方案的技术核心可以浓缩为三个设计不变量接受事务保证每次提交恰好一条可见用户消息且持久于一切 UI 之前认领事务保证已读回执、批次闭合、助手预留三件事原子发生且不可逆渲染器只做缓存与呈现权威永远在 SQLite。配合oldAssistant.id ! newAssistant.id与orderSeq单调序这两条跨后端不变量Steer 从一条模糊的后台命令变成了与即时通讯体验一致、可重载、可恢复、可测试的持久会话事实。仓库中 pendingInputs.ts 的acceptSteerMessage/claimSteerInput/consumeSteerInput/recoverInputsAfterRestart与 pendingInputAdmissionCoordinator.ts 的steerActiveTurn分支是核对上述每一项设计的最直接入口。【免费下载链接】deepchatDeepChat - A smart assistant that connects powerful AI to your personal world项目地址: https://gitcode.com/GitHub_Trending/dee/deepchat创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表