feat: session 模型/推理强度对持久化(#879) - #1139
Draft
qqqqqf-q wants to merge 16 commits into
Draft
Conversation
qqqqqf-q
force-pushed
the
feat/session-model-preference
branch
from
September 2, 2026 18:16
efc5eea to
e6a9f11
Compare
设计定稿(v2,2026-09-02):(模型,effort)成对单值;显式选择即记忆——来源为 用户/记忆的对才随消息携带并持久化,默认来源不携带不记忆(没选过的人继续跟随 bot 默认);welcome=草稿+种子链;多 tab 同浏览器共享 view 同步、跨浏览器最后发送者赢; 渠道 /model//reasoning 清空 session 对;ACP 对存 runtime_metadata。 附录 A 含 v1→v2 修订记录与代码证据。
- 迁移 0146:bot_sessions 加 preferred_chat_model_id/preferred_reasoning_effort (成对写、成对清,NULL=无记忆;写不碰 updated_at) - 解析链:请求 > session 记忆 > subagent pin > bot 默认 > 历史;schedule 轮跳过记忆; effort 记忆 shadow stored(不进 ReasoningRequestedEffort,不串子代理) - 写点(全经 reconcile,DB 不存非法对):picker PATCH;首发 createSession body 随 INSERT; 每轮 resolve() 内请求携带且与记忆不同才写(门=数据,渠道结构不携带); 渠道 /model//reasoning 成功后将 session 对清空(P11') - welcome 种子端点:该 bot 该用户最近一条有记忆的 native session - ACP:对存 runtime_metadata(acp_model_id/acp_reasoning_effort),PATCH 双写 agent 自报值,spawn 后回放覆盖 profile 默认(治 effort 回退) - fork 继承源对;sqlc/swagger/sdk 重生;机制测试:解析链顺序/记忆级/写回门/渠道清空
- composer 对搬入 ChatViewEntry(pairModelId/pairEffort/pairSource): 同 session 多 tab 共享同步;ref 改 computed 桥接;promoteDraft 迁移(首发不闪回) - 来源状态机:user/session 才随 send/retry/edit/createSession 携带; default/unset 省略——服务端据此区分'没选过'(跟随 bot 默认)与'选过'(记忆) - 播种:repoint 重播种不覆盖 user;welcome=本机草稿>种子端点>bot 默认; pin/运行时切换按转移表标来源;换模型落新模型默认档(P6') - picker 纯乐观,PATCH best-effort 失败静默;删 store 层 override 死代码 未经人工 QA:spec §1.4 九幕走查待做
- 触发器 spinner 延迟:加载 3 秒内不出 spinner(用户反馈:快速加载不该闪); popover 菜单内等待行保持即时(用户开着菜单主动等) - spec:P8′ 同步范围明确为 dockview 面板级;两个浏览器标签页=跨设备语义 (最后发送者赢,刷新收敛),消除'同一浏览器内'歧义
- 0146 迁移的 preferred_chat_model_id 外键改用团队期后的复合写法: (team_id, col) REFERENCES models(team_id, id) ON DELETE SET NULL (col)。 裸 SET NULL 会让 confdelsetcols 为 NULL,被 TestTeamChainReversible 守卫判为"可清 team_id 的不安全 FK"(CI Migrations 红)。0001 无需改: 其团队期通用块会把单列 FK 重写成同一形状,全新/增量两条路径收敛。 - golangci 三处:contract.go 与 service_model_preference_test.go 的 gofmt 对齐(rebase 合入遗留),service_model_selection_test.go 的 unused-receiver。 - 另两个 CI 失败与本分支无关(分支不碰 native/docker): TestSpawnAdapterGenerateWithWatchdog* 为预存竞态(本地 -race x20 全过), server-entrypoint 的 alpine:3.23 拉取失败为 runner 网络问题。
rebase 到 94d468e 后四处适配(main 侧变化引入,非行为变更): - currentBotId 变为 string|null(felinics#1131),persistComposerPair/seedWelcomePair 补 botId 空守卫; - vue-tsc 对 pairSource.value 链收窄到新不含 'user',post-await 重查改走 paneView 链(该重查是防种子竞速的必要逻辑,不是冗余); - useDelayedTrue 补 type Ref 导入,onUnmounted 改用文件已导入的 onBeforeUnmount。 chat-pane 剩余两处 TS6133(textareaEl/composerEl)为 main 自带基线, 与本分支无关。
qqqqqf-q
force-pushed
the
feat/session-model-preference
branch
from
September 3, 2026 07:02
e6a9f11 to
8e4502b
Compare
… SQL 补 team_id - sessionModelPreference:模型列为 NULL 时 effort 一并置空(半对只可能由 ON DELETE SET NULL 单列清出;否则强度会被贴到回落模型上,spec §3.7 禁止) - createWSChatSession:携带的对先经 ReconcileSessionModelPreference 再 INSERT, 与 REST 首发一致(此前已删模型 UUID=FK 违反 500、slug 退化成半对、非法 effort 原样入库) - UpdateSessionModelPreference/GetLatestSessionModelPreference 补 team_id = memoh_current_team_id()(sessions.sql 全文惯例,FORCE RLS 之下的 纵深防御),Update 加 deleted_at IS NULL 守卫
- clearPairDraft 从通用 repoint watcher 挪到 handleSend 成功路径: 此前 welcome 未发草稿仅因点开历史会话/硬刷新就被清掉,直接违反 spec P2′;现在草稿只在 welcome 首发成功(对已落库)时消费,发送失败保留 - seedWelcomePair await 后补 bot/view 同一性守卫:切 bot 后迟到的种子 不再写进另一个 bot 的 welcome(此前会跨 bot 串染并随首发携带) - 携带/省略门与草稿助手提取进 chat-pane-send.ts(carriedPairForSource/ *ComposerPairDraft/welcomeSendConsumedDraft),补 6 组机制测试——此前 该状态机零测试
- 5 个分散的重置/刷新 watcher 合并为一个:以 runtime 身份为键,同 view 身份变化即重置并重载,view 切换不重置 - 修复已有空会话切换 Agent/runtime 后残留旧模型对(旧 watcher 遇 sessionId 直接返回) - 修复 welcome seed 在默认外部 Agent stage 期间返回,把 native UUID 塞进 direct 草稿的竞态 - pair-sync 新增 invalidate(),重置时作废在途读写 - chat-pane.vue 净删约 190 行;新增 5 个状态转换测试
Claude Code 的 ModelCatalog 每次新起 CLI 等 initialize,Codex 调 model/list; 已记忆的 Web 会话每次发送都携带同一对,只在与已存对不同时才重新校验。 revision 仍每次推进,旧 picker PATCH 依旧被拦。
- 删除手写假表 + 字符串切 sqlc 源码的夹具,改用 freshMigratedDB/migrateTo/ applyCanonicalInitOnly:链可逆、0146 幂等、fence/换 runtime 清空/FK SET NULL 在真 schema 上验证 - PatchSessionModelPreference 的 expected revision 改为必填,删掉无调用方的 无条件写分支;picker 写永远是 CAS - 0146 up 注释精简并指向 0141,down 合并为一条 ALTER
Member
Author
|
本轮 review 与产品讨论结论(基于 head 用户路径与范围现有用户路径整体合理,维持当前设计,不新增“跟随 Bot 默认/固定选择”的标签、开关或设置。用户只需要看见当前模型和推理强度;未明确选择时使用 Bot 默认,明确选择后记住该会话的选择,这些语义由内部实现保证。 保留 welcome seed:用户在一段对话使用某个模型和强度后,新建对话自动带出最近使用的选择,减少重复操作。它只带模型和强度,不带聊天内容;本机未发送草稿仍优先。用户明确认可这一体验,它与“记住我的选择”属于连贯的用户路径,不应仅为了缩小 PR 而移除或拆出。 跨浏览器/设备不实时同步、“最后发送者赢”是当前明确接受的设计取舍,本轮不将其作为缺陷,也不改成另一套长期偏好规则。 持久化需要覆盖首次发送、恢复、retry/edit、多面板、native/direct/generic ACP 和迁移,相关跨层改动有必要。范围已到应当冻结的边界:不再借本 PR 扩展产品规则或引入额外状态管理体系。 建议修复的两个 P2两个问题已通过独立回归用例复现,属于实现与既定用户路径不一致,不是要求调整产品设计。尚无证据表明它们高频,但建议合并前修复。
修复优先沿用现有同步机制,不新增用户操作、新存储或另一套状态源;若需要持续叠加标志位和特判,应先检查现有操作顺序的表达是否合理。 收尾边界与验证建议停止条件:两个复现用例转绿、相关检查通过、完成既定核心 QA 后进入合并判断。不以“证明没有任何 bug”为目标,不将所有推测或可选体验改进升级成阻塞项;新的数据安全、权限或核心路径失败仍需单独判断。 本次 AI review 实际验证:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
解决什么问题
聊天框顶上选的模型和推理强度此前只活在 pane 组件内存里:刷新、关标签、换设备都会悄悄回到 bot 默认;welcome 页选完没发,选择还会串进老对话(issue #879)。
本 PR 把这个选择变成 session 的持久属性,且只对明确选过的会话生效:
设计稿(含用户路径、语义、推导、取舍)在
docs/spark/2026-08-31-session-model-preference-design.md,附录 A 有 v1→v2 修订记录与全部代码证据。实现要点
bot_sessions加preferred_chat_model_id/preferred_reasoning_effort(迁移 0146,成对写清,NULL=无记忆,写入不碰updated_at)。ACP 对存runtime_metadata(ACP 模型 id 是 agent 命名空间字符串,进不了 FK 列)。resolve()内"请求携带且与记忆不同"才写 / 渠道/model、/reasoning清空。ChatViewEntry(pairModelId/pairEffort/pairSource),同 session 多 tab 共享同步;仅user/session来源随消息携带,default/unset省略——服务端据此区分"没选过"与"选过"。人工 QA 进展(2026-09-02,独立测试栈 memoh-dev-b :19082)
已走路径(全部符合预期):
QA 中修掉的问题:composer 触发器 spinner 改为加载超 3 秒才显示(此前每次加载都闪)。另确认 welcome 出现的"flash Medium"=本机草稿(设计内行为,非 bug)。
未走路径:P6′(换模型强度落新默认档)、P5′(ACP max 不回退)、P11′(Telegram /model 清空,需真渠道)、P8′(dockview 面板级实时同步,浏览器双 tab 已覆盖其降级语义)、P7′(弱网)。
遗留决策(不阻塞本 PR,待拍):welcome 草稿永存到首发是否软化(TTL/首发即清);Cloud fork 的平台默认模型在种子链中的层级(合入后 sync 时对齐)。
测试
go build/go vet/go test ./internal/...全绿;前端vue-tsc/eslint/vitest绿(预存基线红与 flaky 测试均与本分支零 diff);迁移经migrate up→down→up全链路 + 团队守卫测试验证(0146 复合 FK + NOT VALID,与 0141/0142 同款)。(上方 QA 进展为部分路径的人工验证;P6′/P5′/P11′ 走完后此行才可移除。)