Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
66 commits
Select commit Hold shift + click to select a range
4926970
feat(desktop): 模型图片输入能力本地声明与运行期对账
zyjisdog Sep 22, 2026
3b5af90
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Sep 22, 2026
44c6a3f
fix(desktop): 图片能力对账重放路由、覆盖桥接两端 id,并处置 review 意见
zyjisdog Sep 22, 2026
c980987
fix(desktop): 图片能力对账先读回再重放,延后期间不放行图片
zyjisdog Sep 22, 2026
f4e6f9f
fix(desktop): 图片声明读回取运行期有效值并暴露分叉,允许重选修复
zyjisdog Sep 22, 2026
51ee475
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Sep 22, 2026
3dd21e7
fix(desktop): 补读切回的行、把 main 的失败指引带给用户
zyjisdog Sep 22, 2026
03b12a8
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Sep 22, 2026
c864c62
chore(design-inventory): 并入 upstream 后重算 desktop.settings 圆角计数
zyjisdog Sep 22, 2026
c79285e
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Sep 23, 2026
11e7c0f
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Sep 23, 2026
72935c6
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Sep 23, 2026
c06ca76
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Sep 30, 2026
33fdcd1
fix(desktop): 组织托管供应商拒写图片能力,恢复继承后旧会话恢复支持
zyjisdog Sep 30, 2026
6887441
feat(desktop): 模型思考档位的本地目录声明
zyjisdog Sep 30, 2026
acda94e
fix(desktop): 思考档位声明的三态闭环与活会话对齐
zyjisdog Sep 30, 2026
0709a5a
fix(desktop): 思考档位对账的结论语义与落定时机
zyjisdog Sep 30, 2026
ae814bd
Merge remote-tracking branch 'upstream/main' into fix/pi-space-bunny-…
zyjisdog Sep 30, 2026
e499019
fix(pi): 切模预检用对账读到的思考档位
zyjisdog Sep 30, 2026
6d0465d
Merge remote-tracking branch 'upstream/main' into fix/pi-space-bunny-…
zyjisdog Sep 30, 2026
10ebb7d
Merge remote-tracking branch 'upstream/main' into fix/pi-space-bunny-…
zyjisdog Oct 5, 2026
7b9ffbb
Merge upstream/main into fix/model-capacity-undeclared(以 PR head 为基)
zyjisdog Oct 5, 2026
b42dd38
fix(i18n): 思考档位名改用既有 effortLevels 正本
zyjisdog Oct 5, 2026
0c07736
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 8, 2026
f8e32af
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 8, 2026
fe3aa47
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 9, 2026
4d91fca
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 10, 2026
a976a01
test(desktop): 本地声明思考档位能清除 valid: none 新建任务失败(#5762 链路)
zyjisdog Oct 11, 2026
d66c1cc
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 11, 2026
6a6515d
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 16, 2026
5d86b5a
Merge upstream/main into fix/model-capacity-undeclared(解冲突:设计台账取上游最新版…
zyjisdog Oct 17, 2026
3c59a34
docs(design): 重新生成设计台账,使 GENERATED 区块与源码同步
zyjisdog Oct 17, 2026
dba9450
chore: re-trigger client-ci
zyjisdog Oct 17, 2026
50289b6
fix: 切模恢复门改用落定档位快照;思考 hook 空目标周期交还订阅清理
zyjisdog Oct 17, 2026
9cef67f
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 17, 2026
45bbf42
fix: 图片能力重载后恢复会话档位;思考 hook 乐观期保留默认档
zyjisdog Oct 17, 2026
df5c298
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 17, 2026
efceee6
fix: reload 恢复思考时优先还原已确认的关闭状态
zyjisdog Oct 17, 2026
cb55341
fix: setEffort 与切模恢复成功时覆盖思考关闭记录
zyjisdog Oct 17, 2026
c8ec2a4
chore: re-trigger client-ci
zyjisdog Oct 17, 2026
a43dc10
Merge remote-tracking branch 'upstream/main' into fix/model-capacity-…
zyjisdog Oct 18, 2026
c57fc5a
chore(design-inventory): 重新生成台账,补上游新增源文件
zyjisdog Oct 18, 2026
86c6624
fix(desktop): 声明落盘前处理作用范围冲突与条目数超限
zyjisdog Oct 18, 2026
ee4e657
chore: 合并上游 main,解决冲突
zyjisdog Oct 22, 2026
2d3f7c6
Merge remote-tracking branch 'upstream/main' into tmp-4856
zyjisdog Oct 22, 2026
94eca87
fix(settings): 思考档位首次读回前禁止编辑
zyjisdog Oct 22, 2026
1ddc12e
fix(settings): 乐观写入分支补齐 loaded 字段
zyjisdog Oct 22, 2026
bce0d0e
chore: 触发 client-ci
zyjisdog Oct 23, 2026
534da64
chore: 合并上游 main,解决冲突
zyjisdog Oct 23, 2026
a66da01
chore: 合并上游 main
zyjisdog Oct 23, 2026
04f4a8f
Merge remote-tracking branch 'upstream/main' into tmp-4856
zyjisdog Oct 23, 2026
543edfc
test(desktop): dedupe statDirectory fixture after base merge
zyjisdog Oct 23, 2026
210b6af
chore: retrigger CI (Desktop Git integration failed on npm ECONNRESET)
zyjisdog Oct 23, 2026
100c3ad
chore: retrigger CI (Windows shard 2/4 flaked on unrelated fastPolicy…
zyjisdog Oct 23, 2026
9527076
fix(pi): keep thinking tier realignment on catalog conclusions across…
zyjisdog Oct 23, 2026
2eb9efa
chore: retrigger CI (Windows shard 4/4 vitest pool hang after full pass)
zyjisdog Oct 23, 2026
582bd47
chore: retrigger CI (previous empty commit did not trigger workflows)
zyjisdog Oct 23, 2026
5f70755
Merge remote-tracking branch 'upstream/main' into tmp-4856
zyjisdog Oct 23, 2026
a636e6f
chore: retrigger CI (Windows shard 3/4 flaked on unrelated LibraryVau…
zyjisdog Oct 23, 2026
e530c66
fix(pi): dedupe only the redundant RPC, still update confirmed state
zyjisdog Oct 24, 2026
a3992c5
Merge remote-tracking branch 'upstream/main' into tmp-4856
zyjisdog Oct 24, 2026
32a459c
fix(desktop): include defaultEffort in thinking divergence checks
zyjisdog Oct 24, 2026
cea6322
Merge remote-tracking branch 'upstream/main' into tmp-4856
zyjisdog Oct 25, 2026
ee06843
fix(pi): carry the session context budget through catalog rewrites
zyjisdog Oct 25, 2026
5f570a5
chore: retrigger CI (Windows shard 3/4 flaked on unrelated codexAuthI…
zyjisdog Oct 25, 2026
eeab340
Merge remote-tracking branch 'upstream/main' into tmp-4856
zyjisdog Oct 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ import path from 'node:path';

import { describe, expect, it, vi } from 'vitest';

import { MAX_OVERRIDE_ENTRIES_PER_SECTION } from '../model-plane/localCatalogOverrides.js';

const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'model-catalog-overrides-test-'));
const owner = { current: 'owner-a' };

Expand All @@ -21,8 +23,14 @@ vi.mock('../../appSessionState.js', () => ({
ownerScopedUserDataPath: (name: string) => path.join(tmpDir, owner.current, name),
}));

const { MAX_MODEL_CATALOG_OVERRIDE_FILE_BYTES, readModelCatalogOverrides } =
await import('../model-catalog-override-store.js');
const {
MAX_MODEL_CATALOG_OVERRIDE_FILE_BYTES,
ModelCatalogOverrideLossError,
readModelCatalogOverrides,
readModelCatalogImageInput,
setModelCatalogImageInput,
readModelCatalogThinkingDivergence,
} = await import('../model-catalog-override-store.js');

const patchEntry = { base: { name: 'Renamed' } };

Expand All @@ -33,6 +41,167 @@ function writeOwnerFile(value: unknown): string {
return file;
}

describe('model-catalog-override-store / 图片输入能力声明', () => {
const target = { providerId: 'opencode-go', modelId: 'mimo-v2.6-flash' };
const key = 'opencode-go:mimo-v2.6-flash';

it('写入 true/false 落到 base patch,读回 isCustomized 区分「跟随目录」与「显式声明」', async () => {
owner.current = 'owner-image-write';
expect(readModelCatalogImageInput(target)).toEqual({ value: null, isCustomized: false });

await setModelCatalogImageInput(target, true);
expect(readModelCatalogOverrides().patches[key]?.base?.supportsImageInput).toBe(true);
expect(readModelCatalogImageInput(target)).toEqual({ value: true, isCustomized: true });

await setModelCatalogImageInput(target, false);
expect(readModelCatalogImageInput(target)).toEqual({ value: false, isCustomized: true });
});

it('条目限定了 agents 时把目标引擎并进名单,让声明真正生效', async () => {
// 目录合并按 agents 过滤,读回却不检查 —— 不并进 pi 就会「显示已声明、运行期不生效」。
owner.current = 'owner-image-agents';
writeOwnerFile({
version: 1,
patches: { [key]: { agents: ['codex'], base: { name: 'Mimo' } } },
});

await setModelCatalogImageInput({ ...target, agent: 'pi' }, true);
expect(readModelCatalogOverrides().patches[key]?.agents).toEqual(['codex', 'pi']);
});

it('已有条目无 agents 限制时不加名单', async () => {
owner.current = 'owner-image-no-agents';
writeOwnerFile({ version: 1, patches: { [key]: { base: { name: 'Mimo' } } } });

await setModelCatalogImageInput({ ...target, agent: 'pi' }, true);
expect(readModelCatalogOverrides().patches[key]?.agents).toBeUndefined();
});

it('写入后条目数超上限时拒绝保存,不留下会被下次读取丢弃的条目', async () => {
owner.current = 'owner-image-limit';
const full: Record<string, unknown> = {};
for (let i = 0; i < MAX_OVERRIDE_ENTRIES_PER_SECTION; i++) full[`p:model-${i}`] = { base: {} };
writeOwnerFile({ version: 1, patches: full });

await expect(
setModelCatalogImageInput({ providerId: 'p', modelId: 'one-more' }, true),
).rejects.toThrow(/上限/);
// 文件没被改动:原有 1024 条仍完整
expect(Object.keys(readModelCatalogOverrides().patches)).toHaveLength(
MAX_OVERRIDE_ENTRIES_PER_SECTION,
);
});

it('value=null 删除该键回到跟随目录,条目空掉时整条删除', async () => {
owner.current = 'owner-image-reset';
await setModelCatalogImageInput(target, true);
await setModelCatalogImageInput(target, null);
expect(readModelCatalogOverrides().patches[key]).toBeUndefined();
expect(readModelCatalogImageInput(target)).toEqual({ value: null, isCustomized: false });
});

it('只动 supportsImageInput:同条目其它字段与其它条目原样保留', async () => {
owner.current = 'owner-image-preserve';
writeOwnerFile({
version: 1,
patches: {
[key]: {
agents: ['pi'],
base: { name: 'Mimo', contextWindow: 128_000 },
perAgent: { pi: { maxOutput: 8_192 } },
},
'openai:gpt-6': { base: { name: 'Renamed' } },
},
});

await setModelCatalogImageInput(target, true);
const entry = readModelCatalogOverrides().patches[key]!;
expect(entry.base).toEqual({
name: 'Mimo',
contextWindow: 128_000,
supportsImageInput: true,
});
expect(entry.perAgent).toEqual({ pi: { maxOutput: 8_192 } });
expect(entry.agents).toEqual(['pi']);
expect(readModelCatalogOverrides().patches['openai:gpt-6']).toEqual({ base: { name: 'Renamed' } });

// 复位后条目里仍留着用户自己写的字段,不能被一并删掉。
await setModelCatalogImageInput(target, null);
expect(readModelCatalogOverrides().patches[key]).toEqual({
agents: ['pi'],
base: { name: 'Mimo', contextWindow: 128_000 },
perAgent: { pi: { maxOutput: 8_192 } },
});
});

it('providerId 里的冒号经 encodeURIComponent 转义,不与 modelId 的分隔符混淆', async () => {
owner.current = 'owner-image-escape';
await setModelCatalogImageInput({ providerId: 'a:b', modelId: 'm' }, true);
expect(readModelCatalogOverrides().patches['a%3Ab:m']?.base?.supportsImageInput).toBe(true);
});

it('一行多个引擎 id 一起写(桥接两端 id 不同,只写一边运行期读不到)', async () => {
owner.current = 'owner-image-row';
await setModelCatalogImageInput(
[
{ providerId: 'openai', modelId: 'gpt-5.6-sol' },
{ providerId: 'openai', modelId: 'chatgpt/gpt-5.6-sol' },
],
true,
);
const patches = readModelCatalogOverrides().patches;
expect(patches['openai:gpt-5.6-sol']?.base?.supportsImageInput).toBe(true);
expect(patches['openai:chatgpt/gpt-5.6-sol']?.base?.supportsImageInput).toBe(true);
});

it('清掉 perAgent 里残留的 supportsImageInput,并让读取按 agent 算有效值', async () => {
// perAgent 会遮蔽 base:不清的话 UI(读 base)显示已声明、运行期(按合并语义)仍拒收。
owner.current = 'owner-image-peragent';
writeOwnerFile({
version: 1,
patches: {
[key]: { agents: ['pi'], perAgent: { pi: { supportsImageInput: false } } },
},
});
// 未写入前:读 pi 的有效值仍是显式声明(不假装「跟随目录」)。
expect(readModelCatalogImageInput({ ...target, agent: 'pi' })).toEqual({
value: false,
isCustomized: true,
});

await setModelCatalogImageInput({ ...target, agent: 'pi' }, true);
expect(readModelCatalogOverrides().patches[key]?.perAgent).toBeUndefined();
expect(readModelCatalogImageInput({ ...target, agent: 'pi' })).toEqual({
value: true,
isCustomized: true,
});
});

it('手改文件里会被 sanitize 丢弃的 patches 条目:拒绝写入而不是静默删掉', async () => {
owner.current = 'owner-image-lossy';
writeOwnerFile({
version: 1,
patches: {
// 未知字段 → 整条被 sanitize 隔离;写盘会把 patches 整段换成清洗后的快照。
'openai:typo-model': { base: { name: 'Typo', unknownField: 1 } },
},
});

await expect(setModelCatalogImageInput(target, true)).rejects.toThrow(/无法保留/);
// 独立错误类型让 IPC 层能把它映射成可执行的 PRECONDITION_FAILED(而不是笼统的 INTERNAL)。
await expect(setModelCatalogImageInput(target, true)).rejects.toBeInstanceOf(
ModelCatalogOverrideLossError,
);
// 文件没被动过:那条手改还在(修好后下一次写入自动恢复)。
const raw = JSON.parse(
fs.readFileSync(path.join(tmpDir, owner.current, 'model-catalog-overrides.json'), 'utf8'),
) as { patches: Record<string, unknown> };
expect(raw.patches['openai:typo-model']).toEqual({
base: { name: 'Typo', unknownField: 1 },
});
});
});

describe('model-catalog-override-store', () => {
it('读取显式 v1 文件', () => {
owner.current = 'owner-valid';
Expand Down Expand Up @@ -105,4 +274,49 @@ describe('model-catalog-override-store', () => {
expect(fs.statSync(oversized).size).toBeGreaterThan(MAX_MODEL_CATALOG_OVERRIDE_FILE_BYTES);
expect(fs.existsSync(oversized)).toBe(true);
});

it('readModelCatalogThinkingDivergence 把「同集合不同默认档」报成分叉', () => {
// 手改文件留下的分裂:两侧档位集合相同、默认档不同。diverged 不置位的话,
// 抽屉的「重选当前项」会被同值 no-op 挡掉,默认档的分叉永远修不掉。
owner.current = 'owner-thinking-diverged-default';
writeOwnerFile({
version: 1,
patches: {
'prov:model': {
agents: ['pi', 'codex'],
perAgent: {
pi: { efforts: ['low', 'max'], defaultEffort: 'low' },
codex: { efforts: ['low', 'max'], defaultEffort: 'max' },
},
},
},
});
expect(
readModelCatalogThinkingDivergence([
{ providerId: 'prov', modelId: 'model', agent: 'pi' },
{ providerId: 'prov', modelId: 'model', agent: 'codex' },
]),
).toEqual({ diverged: true });

// 默认档一致时不算分叉(集合相同 + 默认档相同)。
owner.current = 'owner-thinking-consistent';
writeOwnerFile({
version: 1,
patches: {
'prov:model': {
agents: ['pi', 'codex'],
perAgent: {
pi: { efforts: ['low', 'max'], defaultEffort: 'low' },
codex: { efforts: ['low', 'max'], defaultEffort: 'low' },
},
},
},
});
expect(
readModelCatalogThinkingDivergence([
{ providerId: 'prov', modelId: 'model', agent: 'pi' },
{ providerId: 'prov', modelId: 'model', agent: 'codex' },
]),
).toEqual({ diverged: false });
});
});
Loading
Loading