设置存储
这一页讲 Covel 的设置是怎样保存的:存在哪里、几个窗口同时改会怎样、API 密钥为什么走另一条通道,以及模型设置页的保存规则。维护设置界面、写设置后端或排查“设置没存上”的人需要它。
SettingsStore 是 @covel/settings 包提供的设置存储。它负责四件事:登记每个设置的 schema、在内存里保存当前值、通知订阅者、把值写到持久存储。
三条基本事实:
- 存在哪由当前设备决定。 有 Electron IPC 时用个人文件
settings.json,否则始终用浏览器的 localStorage。 - API 密钥走单独的密钥通道,不和普通设置放在一起。
- 少数设置由服务端保存。 它们标记为
scope: "server",见服务端执行的设置。这是唯一的例外。
服务端的 /api/config/info 只用来发现管理能力。COVEL_HOME 和 isDesktop 都不会把浏览器的设置切换成服务端的共享文件。管理探测失败,不阻止本地设置初始化。
持久化格式与用不了的数据
普通设置的持久化格式只接受 schemaVersion: 2,并且必须包含 revision、savedAt 和 entries。localStorage、REST 和桌面 IPC 共用这一个校验。
这个版本用不了的数据不做迁移。应用先留一份副本,再让设置恢复可用,并在应用内提示一次。分两种情况:
整份数据用不了。 包括更早的格式(v1)、没有版本号(unversioned)、不是合法数据(damaged)。
- 加载时把它原样移到一旁,设置从默认值开始。
- 浏览器把它存为
covel:settings.<标签>.bak;桌面端在启动时把文件改名为settings.<标签>.bak.json。 - 更新的版本写的数据不属于这种情况:读取和覆盖写入都会被拒绝,原内容保留,SettingsStore 只读。
个别值用不了。 存储的值不被这个键当前登记的 schema 接受,例如升级后某个选项被移除了,或者插件的设置改了取值范围。
- 先留一份完整的副本(
covel:settings.conflict.bak/settings.conflict.bak.json)。 - 再从存储里去掉这些键。它们读回默认值,其余设置不变。
- 加载之后才登记的键(插件设置)同样处理。处理完成之前发起的写入会排在它后面。
关于副本。
- 副本从不覆盖已有的副本。重名时加时间戳;同一毫秒仍然冲突时再加序号。
- 副本写不进去,或者后端没有
backupBundle时,什么都不丢弃,SettingsStore 保持只读。 - 提示通过
subscribeRepairs和SettingsBackendAdapter.takeArchivedBundle()给出。 - 副本可以在“设置 → 数据”里查看和下载(
listBackups/readBackup;桌面 IPC 是covel:settings:backup、covel:settings:backups、covel:settings:read-backup)。 - 密钥通道不受影响,副本里没有 API 密钥。
设置导入 / 导出用的 SettingsExportBundle.schemaVersion: 1 是另一份独立的、当前有效的格式,不受上面这些限制影响。
服务端执行的设置
每个设置有一个作用域:
| 作用域 | 是什么 | 由谁保存 |
|---|---|---|
client(默认) | 某一台设备的偏好:主题、布局、语言、这个浏览器的模型和密钥 | 这台设备 |
server | 要由服务端自己去执行的设置 | 服务端;同一个安装的每个浏览器读到同一个值 |
一个 server 设置要在两处声明:注册表里的条目写 SettingEntry.scope: "server",同一个键在 SERVER_SETTINGS(packages/shared/src/env/server-settings.ts:schema、默认值和能覆盖它的环境变量)里有定义。两边不一致时测试失败。
目前有 diagnostics.traceRetention(设置 → 通用 →“保留诊断记录”:7 / 30 / 90 / keep,默认 30),以及 trust.sources(设置 → 安装与管理 →“可信来源”:GitHub 所有者或仓库名称数组,默认 [],由 COVEL_TRUSTED_SOURCES 覆盖)。来源语法和审批条件见插件安装。
- 存在哪里。 服务端的数据库里,DataStore 的
server_settings表(listServerSettings/setServerSetting/deleteServerSetting,由存储契约测试覆盖)。- 桌面应用和自托管的 Web 部署是同一套机制。
- home 目录只读或被替换时,它仍然保留。
- 同一个 PostgreSQL 数据库上的多个服务端进程读到同一个值。
- 它不会写进
settings.json或 localStorage。 STORE_BACKEND=memory时只保留到进程退出。
- 谁可以写。 只有 personal 形态:唯一的玩家就是服务器的所有者。桌面壳设置了 token 时,
PUT /api/config/server-settings还要求带上它。browser-private 形态和demo/commercial层级下,这些设置归运维方,通过环境变量设定;写入返回403,控件显示为锁定。 - 优先级。 运维方的环境变量,其次是保存的值,最后是默认值。被环境变量固定的键不可设置,写入返回
409。 - 校验。 一次写入只能包含已知的键,每个值都要通过定义里的 schema。有一项不合法就整体拒绝,什么都不写。
- 生效。 服务端从内存里的一份副本读取。这份副本在第一次清理之前填好,每次写入后替换。所以修改在下一次提交或清理时生效,不需要重启。同一个数据库上的其他服务端进程在 30 秒内跟上。
在 Web 应用里,SettingsStore 把这些键交给服务端(ServerSettingsChannel,GET / PUT /api/config/server-settings)。桌面和 Web 相同:
- 读写接口。
store.get(key)返回服务端报告的生效值。它可能是运维方设定的、选项里没有的值。store.has(key)在生效的是保存值时为真。store.set/store.clear写到服务端。store.serverSetting(key)返回{ status, value, source, settable }。服务端应答之前status是pending,问不到时是unavailable。
- 控件什么时候禁用。 在
pending、unavailable或settable为假时,这类设置的控件禁用,并用一行文字说明原因。所以它不会先给出一个服务端随后会拒绝的选择。所有通用控件都遵守这一点,不需要为某个设置单独写控件。 - 写入的反馈。 写入后立刻显示新值。服务端拒绝或连不上时恢复原值,并用“设置保存失败”的提示显示服务端给出的原因。
- 其他。 窗口重新获得焦点时重新读取。它们不阻塞本设备设置的加载,不进入设置导出、原始 JSON 编辑器和“重置”。本设备的存储里出现这个作用域的键时,会被忽略。
优先级和 trace 清理的规则见 env-registry.md;安全边界见 security.md。
Schema 归一化
看到的值是 schema 解析后的结果。 已登记的非密钥设置,在这些时刻都用 schema 的解析结果作为可见的值:加载、动态登记、set()、setMany()、导入、刷新,以及写入失败后的回滚。
- 嵌套的
.default()和字符串的首尾修整,会体现在get()、导出和订阅通知里。 - 明确的写入保存的是解析后的结果。
- 没有登记的键保留原始值。
补齐默认值不算修改。 加载或刷新时补齐默认值,不会单独触发一次保存。revision 冲突检测仍然比较后端确认过的原始快照,所以不会把补齐默认值当成远端的修改。
对设置 schema 的要求:它必须接受自己保存出去的输出,并且归一化多做几次结果不变。这样重新加载、重复登记和同步才不会出问题。
订阅者之间互不影响。 键订阅、全局订阅和持久化错误订阅,逐个隔离同步回调里的异常。某个订阅者抛错:
- 不会阻止其他订阅者;
- 不会把成功的保存改报成失败;
- 不会替换掉真正的 I/O 错误;
- 只留下一条固定的诊断,里面没有键、值和原始异常。
模型用途绑定只选一个目标。 modelRef 表示本地服务商的模型,presetId 表示服务端配置的预设。同时带着两个字段的绑定会被拒绝。读取绑定不会改写配置。
导入时怎样处理不合格的内容。
- 设置备份里无效的自定义主题逐条跳过,合法的主题继续加载。
- 模型配置导入只接受当前的导出格式
{ version: 2, providers: [...] },并逐条清理当前的配置。不受支持的文件,或者有内容但没有任何可用配置的文件,显示错误并保留当前配置。 - 设置的数据导入预览用当前登记的 schema 检查每个条目。不兼容的条目,以及误放在普通
entries里的密钥,不能勾选导入。没有登记的普通键仍然可以保留。 - 只包含独立
keys的备份也可以导入。 - 导入或重置只有在持久化成功之后才提示完成。保存失败时保留导入预览,并显示错误。
主题库和外观工作室等的是真实结果。 它们的串行流程直接等待 SettingsStoreApi.set() 真正成功或被拒绝。控件的 setter 在处理完错误后也会正常返回,不能把它当成持久化成功。
- 应用失败时不清覆盖,不清主题名称,也不提示完整导入成功。
- 逐 token 的微调和重置失败时报错,并撤销这一次失败的草稿和预览,不撤销之后才发起的编辑。
- 全局
useSetting的“发出去就不管”的错误处理方式不变。
通用设置控件
widget: custom 由专用界面编辑,不在通用页生成占位控件。桌面维护、服务商导入和扩展包操作使用统一的结果提示;新提示会取消旧提示的关闭计时器,刷新失败与空安装结果也会显示。
没有专用面板的登记项由通用控件渲染(apps/web/src/settings/widgets/)。通用页和每个插件的设置页都是这样。
什么时候写入。 文本、数字、滑块和密钥控件保留一份本地草稿,失焦或按 Enter 时写入。弹窗关闭时还没提交的草稿也会写入。不逐键写入的原因是:那样会校验每个输入到一半的值,数字就没法重新输入。
数字。 超出声明的
min/max的数字取最近的允许值。因为带小数而被 schema 拒绝的值四舍五入。空的草稿或不是数字的草稿,恢复成已保存的值。同时声明了上下限时,范围显示在说明旁边。恢复默认。 玩家设置过的键(
store.has(key))显示“恢复默认”,点击调用store.clear(key)。插件设置只有这样才会重新跟随世界的pluginSettings或 manifest 的默认值。一个和默认值相同、但明确设置过的值,仍然会覆盖世界。说明文字。 框架键的说明取自 Web 文案目录(
settings.frameworkEntries.*),插件键的说明取自 manifest。登记项有没有重复填写说明,不影响显示。世界给的值。 插件设置页显示当前世界
pluginSettings里的值:玩家没有设置过的键以它为准,并注明来自哪个世界。这个值不符合登记的 schema 时不采用,服务端同样不采用。模型页输入。 生成参数和模型价格同样保留草稿,数字框失焦或按 Enter 时提交;滑杆在松手、松开按键或失焦时提交。保存期间输入控件保持可用,连续提交依次合并到最新已保存的设置。
破坏性操作。 删除服务商或模型配置、卸载插件、删除主题和全部重置均通过
requestConfirm确认。删除服务商会清除它的密钥及模型角色绑定;删除模型配置只清除相关绑定,不清密钥。只读模式。 设置未加载成功时,只禁用会写设置的控件。查看、导出和备份下载仍可用,其他维护操作继续遵守各自的权限限制。导出只包含当前可读的设置,可能不完整,不等同于原始备份;导出密钥仍须明确勾选。
Toast。 Toast 渲染在 document.body 下,位于所有弹窗之上。点击 toast 不会关闭它下面的弹窗。
“配置文件”页。 它把原始配置当作文本来编辑:这台设备的设置(JSON,不含密钥),以及服务端提供的 llm.toml、桌面模式下的 config.toml(见 API 的 /api/config/raw)。保存设置的 JSON 时:
- 逐项用登记的 schema 检查。
- 通过
replaceEntries(entries, expectedEntries)一次替换完整的普通设置快照。被删掉的键恢复默认。 - 编辑器加载之后,别处发生的任何新增、删除或修改都会造成冲突。必须重新加载再保存,这样不会覆盖其他窗口的修改。
- 保存之前留一份副本(
covel:settings.edit.bak/settings.edit.bak.json)。之前的副本也可以载入编辑框后再保存。
“运维访问”页。 它只在需要它的地方出现:
/api/health的operatorTokenRequired为false,并且这个浏览器没有保存令牌时,它隐藏。- 服务端还没应答时它保留。这样托管部署的运维人员才有地方填写令牌。
语言列表用各种语言自己的名称。
多实例写入与同步
几个窗口或几个进程同时改设置时,靠 revision(一个只增不减的版本号)来发现冲突。
支持 loadWithRevision / saveWithRevision 的后端必须检查单调递增的 revision。每一次本地修改都记录三样东西:明确的目标键、当时捕获的目标值、最后一次确认过的基值。修改按顺序持久化。相邻两次 set 的值相同,也不能当作已经保存过。
- 冲突之后。 先读取并验证远端快照,再比较这次修改涉及的键。只有远端仍然等于修改前的值,或者已经等于要保存的值,才可以在新的 revision 下重试。
- 不同的键互不妨碍。 改不同设置键的修改可以安全地重放。对象和数组整体算一个设置键,不会暗中合并它们的子字段。删除和“键不存在”也参与比较。
- 基值什么时候前进。 只有前一次本地写入成功,才推进已经排队的同键修改的基值。普通的 I/O 失败不算远端修改,之后最新的意图仍然可以从确认过的基值尝试保存。远端的同键冲突,不会让之后的同值调用假装成功。
- 三份数据互相隔离。 确认过的快照、排队的目标、公开的内存值各自独立。可以修改
get()返回的对象,再调用set()。调用之后再改这个对象,不会改变已经排队的目标,需要再调用一次set()。 - 同键冲突。 拒绝当前的修改,抛出
SettingsRevisionConflictError。conflictingKeys只包含键名,不包含设置值。内存里换成最新确认的值,同时保留队列里还没完成的、互不相干的修改。这种冲突不会永久禁用之后的写入。 - 重试次数。 每次修改最多尝试三次 CAS 写入(先比较、相同才写)。一直有竞争时仍然拒绝保存,不会降级成无条件覆盖。
- 读取失败。 初始化读取失败时保持只读。远端数据通不过 schema 验证时,不重试写入,也不把不合法的快照发给订阅者。
refresh()。 SettingsStoreApi.refresh(): Promise<void> 按队列顺序读取非密钥设置,并通知实际变了的键。
- 刷新和待保存的修改同时发生时,之后的修改仍然按它原来的基值检查冲突。
- 没有声明 revision 能力的自定义后端使用串行的快照写入,
refresh()不读取这类后端。这只是后端能力的差异,不代表支持旧的持久化格式。
Web 什么时候刷新。
- 普通设置:
covel:settings的 storage 事件、窗口获得焦点、页面恢复可见时。 - 配置里的地址、价格倍率、输出额度这几个草稿,遇到远端更新时保留玩家的输入,阻止自动覆盖,并提供“加载已保存值”操作。
- API 密钥用独立的
refreshSecrets()通道:在covel:keys的 storage 事件、获得焦点和恢复可见时重新读取。待保存的 provider patch 保留。密钥不进入普通设置和 revision 的广播。
密钥通道边界
API 密钥不和普通设置存在一起,写入方式也不同。
增量写入(patch)。 SettingsBackendAdapter.saveSecrets(patch)、桌面的 covel:keys:save 和 REST 的 /api/config/keys,都接受一个“provider → string | null”的增量映射:
- 没提到的 provider 保持不变;
null表示明确删除。 - 不同实例写不同的 provider,不会互相覆盖。
- 同一个 provider 按后端收到的顺序生效。明确地重复设置旧值,或者删除一个本地缓存里没有的 key,仍然会提交。
- 这条通道不提供同键的 CAS。
clearAll()只删除当前实例知道的 provider,不删除其他实例后来新增的、它不知道的 provider。
各个后端怎样保证写入完整。
- localStorage 后端必须在一个 Web Lock 里完成“读取最新密钥、应用 patch、写回”。没有 Web Locks 时拒绝保存密钥,需要在支持它的 HTTPS 或 localhost 环境下使用。
- REST 由服务端应用 patch。
- 桌面 IPC 在侧车不可用时,同步读取并合并本地文件。
- 自定义 adapter 必须实现同样的原子 patch 约定,不接收完整的密钥快照。
- localStorage 读取密钥映射时,按对象自己的数据属性保留 provider 名称,包括
__proto__这类和对象原型同名的键。之后的增量保存不会因为普通对象的赋值语义而悄悄丢掉这些凭据。
密钥不出现在哪里。 密钥不进入普通设置、revision 广播和默认的导出。
哪些条目算密钥。 keys.* 命名空间,以及登记为 backend: "keys" 或 secret: true 的条目,使用独立的密钥通道。把后端声明成普通后端,也改变不了 keys.* 的含义。
- 导入时选中的普通
entries里如果有这类条目,会在任何写入之前明确拒绝。 - 密钥必须放在
bundle.keys里,并且明确设置includeSecrets: true。 - 普通的序列化和导出始终过滤掉已知的密钥条目。
密钥放错了地方时。
- 初始化或刷新时发现普通设置快照里带着密钥条目,会拒绝发布或重放这份快照。
- 初始化时,这项检查排在普通 schema 修复的备份和写入之前。失败时保持只读,并保留原文件。
- 动态登记时发现一个已有的普通条目其实是密钥,也会禁用写入,把它从普通导出里排除,并取消还没开始的普通修复。
- 系统不会自动迁移、删除或复制这类放错位置的密钥。
有效模型与能力来源
这一节讲模型设置页显示什么、什么时候保存。
页面行为。
- 模型目录信息第一次返回或手动刷新时,只重新查询能力数据,不重建模型设置卡片。没提交的输入和生成参数的展开状态保持不变。目录更新之后,能力展示和生成参数使用刷新后的查询结果。
- 模型角色列表保留
default角色:它已配置、已保存,或者处在未配置时的默认目录里。角色目录为空时禁用生成参数的输入,不会创建空的角色键。 - 世界列表上的模型配置入口直接打开服务商页。窄屏先显示服务商列表,选中之后显示完整的详情,并且可以返回。服务商详情里,每个模型只有一个连通测试入口。
- 当前服务商入口和
keys.*设置指向服务商页;其他复合设置键映射到各自的页面。引导版本号是内部状态,不作为常规设置显示。
用途目录。 模型用途和生成参数共用一份实时的用途目录。它合并四个来源:服务端配置、插件 runtime 和 type: slot 的声明、用户设置、已保存的绑定和参数覆盖。这样自定义的用途在两个页面都能继续编辑。插件设置里的用途选项也随当前的模型配置更新。
新手引导。 它等“已完成”的标记持久化之后才关闭。保存期间禁止重复关闭。保存失败时保留引导,显示错误,并允许重试。桌面端重置引导,也只有在重置持久化成功之后才提示完成。
世界准备页与会话侧栏。
- 世界准备页显示所选插件的全部运行阶段,并为每个 agent runtime 分别提供文本模型的绑定。
- function runtime 的提供商用途,按全部
type: slot设置逐项显示。默认值的优先级是:用户的明确覆盖、世界的pluginSettings、插件的声明。取消用户覆盖之后,恢复成当前世界的默认值。 - 会话左侧栏读取当前会话的插件元数据,逐个 runtime 编辑会话的模型覆盖。缺失或不兼容的绑定会明确提示。
模型上限从哪来。 模型用途和生成参数以当前绑定的 provider、model、protocol 为目标。切换绑定之后,旧的服务端 slot 的 token 额度不再作为新模型的限制。服务商详情查询能力时,也带上这个连接的协议。
- 可以当作模型额度的。 来源是
known或model-database、并且found: true的查找结果。当前没有被覆盖的服务端目标可以保留配置的额度。用户明确的能力覆盖优先。 - 不能当作模型上限的。
protocol-default只表示对协议支持情况的估计,它的数字证明不了模型的上限。这时页面显示“模型上限未知”,不把协议默认的 context / output 数字当成模型的硬上限。 - 来源信息不全时。
/api/llm-config的 capability 没有带完整的来源信息。遇到未知模型时,无法据此区分“明确的模型配置”和“协议回退”,所以界面不据此宣称知道模型上限。 - 最大输出 token。 在失焦或按 Enter 时保存,只接受正整数。已知当前额度时,还必须不超过它。留空表示用服务商的默认值。上限未知时,不会凭空造一个 HTML 的
max。
这些都是显示上的规则,不改变模型请求的协议,也不改变服务端解析能力的方式。实际请求的 provider 默认值、执行额度和计费,仍然应该对照请求轨迹和服务商的响应来核对。
热重载。 模型配置热重载期间,自定义模型的请求映射仍然按注册表隔离。被重载清掉的映射会在下一次使用时重新登记,并在最后一个请求结束后清理。新登记的服务端同名预设优先于旧的请求映射。
调试刷新
调试页在三种时候读取会话数据:第一次选中会话、手动刷新、自动刷新。每次都同步侧栏里的 phase、已完成回合数和 setup runtimes。
- 数据页显示最近一次成功读取的时间。读取失败时保留上一次成功的结果,并标注它可能过期。
- 跨会话返回的旧响应不会覆盖当前的数据。
- 自动刷新只合并最新的一页 trace,保留已经加载的更早的页和分页游标。
JSON 文件后端的传输方式。 createJsonFileBackend({ ipc }) 必须由宿主明确提供 IPC transport,它不会自己去探测 globalThis.covelIpc。
- Web 应用只在存在
getCovelIpc()时选用这个 adapter;浏览器使用 localStorage。 - 这个 adapter 只通过 IPC 读写设置和密钥,不提供 REST 传输。
- 设置写入带预期版本号,密钥走独立通道,读取失败会向调用方报告。
当前服务商配置
llm.providers 是唯一的模型配置存储。
- 启动和读取时,不从
llm.customPresets迁移,也不回退到它。旧的导航别名,以及没有生产调用方的预设写入 API,都已经不存在。 - 受影响的开发模型配置和凭证,需要通过当前的服务商界面重新建立。旧条目不会被自动删除。
密钥的归属:
- 每个连接使用
keys.<profile.id>。 - 模型配置和展开后的请求配置里都不包含 API key。
- 一个连接不借用别的连接或同一服务商家族的密钥。
- 服务端预设继续使用它所属服务商的密钥。
- 请求里的
customPresets展开结构仍然是当前服务端路由约定的一部分。
模型配置保存与导入时序
唯一性。 连接 ID 和模型的 ref,在全部连接里必须各自唯一。普通设置的登记和模型文件的导入共用同一个校验。有冲突时整批拒绝,不会悄悄合并或改写引用。同一个模型 ID 可以有多个 ref 不同的配置。
setMany:几个键一起保存。 SettingsStoreApi.setMany(entries) 先验证全部普通设置,再用一次持久化操作保存。
- 任何字段不合法,或者混进了密钥,都会在写入之前拒绝。
- 相关的键一起参与 revision 冲突检查。失败时不会只提交其中一部分。
- 通用的设置导入也是这样:在写入任何一条通道之前,先验证全部选中的普通键。有一个选中的键不合法,就整批拒绝,不会跳过它继续写其他键。
- 密钥仍然用独立的通道,不在这项“要么全成功、要么全不写”的保证之内。
replaceEntries:替换整份快照。 SettingsStoreApi.replaceEntries(entries, expectedEntries) 一次保存完整的普通设置快照:删除没有传入的键,保留传入的、没有登记的普通键。
expectedEntries是编辑器加载时export().entries的快照。- 调用时,以及每次 CAS 尝试之前,都按完整的归一化快照检查。远端新增、删除或修改了任何一个键,都以
SettingsRevisionConflictError拒绝。 - 所有新值在写入之前校验;密钥条目被拒绝;I/O 失败时整包回滚。
- 普通的
setMany仍然只合并涉及的键,import仍然只合并所选的键。 - 没有 revision 能力的自定义后端,只能检查同一个实例里的编辑基值。
setProviderProfiles:保存连接和模型。 setProviderProfiles(profiles, slotConfig?) 返回 Promise,把连接 / 模型和槽位绑定一起保存。
- 删除最后一个模型,只移除指向它的本地绑定,保留空的连接和它的密钥。空连接可以继续编辑、添加模型、导出和导入。
- 明确删除一个连接时,先等普通设置保存成功,再清理事先捕获的连接密钥。同一个 store 里之后发生的连接重建或密钥变更,会阻止这次过期的清理。
- 清理失败时单独报告:返回值里的
unclearedProviderIds、不含密钥值的日志和界面提示。已经成功保存的普通设置不会假装被回滚。 - 独立的密钥通道目前不提供跨窗口的 CAS,也不提供和普通设置一起的跨通道事务。
界面上的时序。
- 新增连接和模型时,弹窗等保存成功才关闭。保存期间禁止重复提交。失败时保留输入,供重试。
- 已有连接的地址和模型名称,也只在保存成功之后才更新确认过的值。失焦时去掉首尾空格,避免把这次保存误判成其他窗口的修改。保存失败时保留可以继续编辑的草稿。
- 导入读取有自己的请求归属:选了新文件,旧的读取就失效;面板卸载之后不再应用读取结果。
- 读取期间发生的、和它无关的连接修改会被合并。同一个连接上发生冲突时,保留当前的编辑,并提示重新选择文件。
modelRef 与 presetId 始终分开。 用途绑定在设置、请求和界面选择里,都保留 modelRef / presetId 的区别。两者可以同名。
- 在同名的本地模型和服务端预设之间切换,会重置和模型相关的思考参数覆盖。
- 请求字段是
slotBindings,本地定义必须随请求一起提供。旧的字符串映射、重复的引用或缺失的引用,会在请求执行之前被拒绝。 - 普通设置的存储格式不变。不为旧客户端增加第二种读取协议。
- 连接测试和它的缓存,也区分本地模型、服务端预设和用途。
切换和重置用途。 模型用途的切换和重置,通过一次 setMany 同时保存绑定和相关思考参数的清理。保存期间暂停用途卡片上的操作。成功之后更新确认过的选择;失败时保留原配置,并显示保存错误。
会话的自动推进设置
“自动推进轮数”保存到会话 metadata 的 autoAdvanceLimit,不是 SettingsStore 的用户偏好。通过会话 API读取和修改;快照、分支与浏览器 checkpoint 保留它。保存后从下一次输入生效,正在运行的请求保留原上限。