Skip to content

Commit 94112fd

Browse files
committed
fix: 默认模型种子改按作用域一次性标记,重启不再顶回「跟随对话」
原先 seed() 按「值为空串」判没配过,而空串(跟随对话)是设置页的合法选项, 每次启动都重种,表现为保存了、重启又变回 deepseek。改为 MODEL_DEFAULTS_SEEDED_KEY 一次性标记,随配置作用域走(登记进 PROFILE_KV_KEYS),每个作用域只种一次。
1 parent ae56b4c commit 94112fd

3 files changed

Lines changed: 62 additions & 13 deletions

File tree

‎src/main/db/config-profile.ts‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,17 @@ const PROFILE_TABLES = [
110110
'scheduled_tasks'
111111
] as const
112112

113+
/**
114+
* `runtime.ts` 的 `seed()` 种默认模型 / 默认子代理的一次性标记。
115+
*
116+
* 需求:这两项只在「这个作用域从没种过」时种一次,之后用户选什么(包括空串
117+
* 「跟随对话」)都原样保留 —— 以前按「值为空」判,重启就把「跟随对话」顶回 deepseek。
118+
*
119+
* ★ 必须随作用域走(所以登记在下面的 `PROFILE_KV_KEYS`):留在全局的话,
120+
* 切到一个新账户时标记还在,新账户的空作用域拿不到种子,首屏模型选择器是空的。
121+
*/
122+
export const MODEL_DEFAULTS_SEEDED_KEY = 'settings.model-defaults-seeded'
123+
113124
/**
114125
* 随作用域走的 kv 键 —— **白名单,不是黑名单**。
115126
*
@@ -128,7 +139,8 @@ const PROFILE_KV_KEYS = [
128139
// client 供应商的一次性迁移标记。跟着供应商走,否则切回账户时
129140
// 那个「把出厂 openai-chat 抬到 Responses」的一次性动作会重跑一次并覆盖用户的选择。
130141
'client-auth.responses-default',
131-
'client-auth.deepseek-zhipu-xiaomi-qwen-anthropic-override-v3'
142+
'client-auth.deepseek-zhipu-xiaomi-qwen-anthropic-override-v3',
143+
MODEL_DEFAULTS_SEEDED_KEY
132144
] as const
133145

134146
const PROFILE_KV_PREFIXES = ['tabs.outer.', 'tabs.inner.', 'session.input.'] as const

‎src/main/ipc/__tests__/agent-run.test.ts‎

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ import {
5151
findPreset
5252
} from '../../../shared/domain/presets'
5353
import { pluginToolId } from '../../plugin/tools'
54+
import { MODEL_DEFAULTS_SEEDED_KEY } from '../../db/config-profile'
5455
import { DEFAULT_WORKSPACE_SETTINGS } from '../../../shared/domain/workspace'
5556
import {
5657
getRouter,
@@ -618,8 +619,33 @@ describe('运行时自播种', () => {
618619

619620
expect(store.getSettings().defaultModel).toBe('用户自己选的')
620621

621-
// 还原成全新安装的样子 —— store 是模块级单例,不还原会影响后面的用例
622+
// 还原成全新安装的样子 —— store 是模块级单例,不还原会影响后面的用例。
623+
// 种子现在按一次性标记判(见 runtime.ts `seed()`),所以标记也要一起撤掉
622624
store.updateSettings({ defaultModel: '' })
625+
store.setKv(MODEL_DEFAULTS_SEEDED_KEY, false)
626+
})
627+
628+
it('★ 选了「跟随对话」(空串)之后重启不再被顶回内置模型 —— 否则表现为保存了、重启又变回 deepseek', async () => {
629+
const { store } = await import('../../state/store')
630+
getRouter() // 第一次启动:种下内置默认
631+
expect(store.getSettings().defaultModel).toBe('deepseek-v4-pro')
632+
633+
store.updateSettings({
634+
defaultModel: '',
635+
defaultModelProviderId: undefined,
636+
subagent: { model: '', modelProviderId: undefined }
637+
})
638+
639+
// 模拟重启:进程内的 `seeded` 清掉,库还是那个库
640+
resetRuntimeForTest()
641+
installHost(withDemo(nodeHost(), { chunkDelayMs: 0 }))
642+
getRouter()
643+
644+
expect(store.getSettings().defaultModel, '重启把「跟随对话」顶回去了').toBe('')
645+
expect(store.getSettings().subagent.model, '重启把子代理的「跟随对话」顶回去了').toBe('')
646+
647+
// 还原成全新安装的样子(同上一条)
648+
store.setKv(MODEL_DEFAULTS_SEEDED_KEY, false)
623649
})
624650

625651
it('★ 换宿主之后路由器必须重建,否则新装的宿主完全不起作用', async () => {

‎src/main/runtime.ts‎

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ import type { McpServerConfig, McpServerStatus } from '../shared/domain/mcp'
7070
import { installSearchConfig } from './search/service'
7171
import { withDemo } from './kernel/upstream/demo'
7272
import { opencodeGoProtocolFor } from './kernel/upstream/opencode-protocol'
73-
import { currentConfigScope, defaultWorkspaceIdForScope } from './db/config-profile'
73+
import { currentConfigScope, defaultWorkspaceIdForScope, MODEL_DEFAULTS_SEEDED_KEY } from './db/config-profile'
7474
import { ensureProviderAccountsSeeded } from './db/provider-accounts'
7575
import type { ProviderConfigSource } from './kernel/upstream/router'
7676
import { UpstreamRouter } from './kernel/upstream/router'
@@ -339,17 +339,28 @@ function seed(): void {
339339
*
340340
* 常量留在 main 侧而不是写进 `DEFAULT_SETTINGS`:`src/shared/` 不能
341341
* 反向 import `src/main/`,而上游是 main 的东西。
342+
*
343+
* ★★ 「没配过」靠 `MODEL_DEFAULTS_SEEDED_KEY` 判,**不靠值是不是空串**。
344+
* 原先写的是 `=== ''` 就种,但空串在设置页是一个可选项(「跟随对话」),
345+
* 而 `seed()` 每次进程启动 / 每次切账户都会重跑 —— 于是用户选了「跟随对话」,
346+
* 重启后又被顶回 deepseek,表现为「保存了,重启就变回去」,且零报错。
347+
* 现在每个配置作用域只种一次;标记跟着作用域走(登记在
348+
* `db/config-profile.ts` 的 `PROFILE_KV_KEYS`),所以新账户的空作用域照样会种。
349+
* 代价:升级后第一次启动时还没有标记,已经选了「跟随对话」的人会被最后顶回一次。
342350
*/
343-
const builtin = findPreset(BUILTIN_PROVIDER_ID)?.suggestedModels ?? []
344-
const settings = store.getSettings()
345-
const [defaultModel, subagentModel] = [builtin[0], builtin[1]]
346-
347-
if (defaultModel !== undefined && settings.defaultModel === '') {
348-
// 种子时把供应商一并写死是对的:这一刻只有内置上游一家,不存在「按优先级择优」的余地。
349-
store.updateSettings({ defaultModel, defaultModelProviderId: BUILTIN_PROVIDER_ID })
350-
}
351-
if (subagentModel !== undefined && settings.subagent.model === '') {
352-
store.updateSettings({ subagent: { model: subagentModel, modelProviderId: BUILTIN_PROVIDER_ID } })
351+
if (store.getKv<unknown>(MODEL_DEFAULTS_SEEDED_KEY, false) !== true) {
352+
store.setKv(MODEL_DEFAULTS_SEEDED_KEY, true)
353+
const builtin = findPreset(BUILTIN_PROVIDER_ID)?.suggestedModels ?? []
354+
const settings = store.getSettings()
355+
const [defaultModel, subagentModel] = [builtin[0], builtin[1]]
356+
357+
if (defaultModel !== undefined && settings.defaultModel === '') {
358+
// 种子时把供应商一并写死是对的:这一刻只有内置上游一家,不存在「按优先级择优」的余地。
359+
store.updateSettings({ defaultModel, defaultModelProviderId: BUILTIN_PROVIDER_ID })
360+
}
361+
if (subagentModel !== undefined && settings.subagent.model === '') {
362+
store.updateSettings({ subagent: { model: subagentModel, modelProviderId: BUILTIN_PROVIDER_ID } })
363+
}
353364
}
354365

355366
seedDefaultWorkspace()

0 commit comments

Comments
 (0)