diff --git a/desktop/src/__tests__/agentsSettings.test.tsx b/desktop/src/__tests__/agentsSettings.test.tsx index 68d1c979..72a39672 100644 --- a/desktop/src/__tests__/agentsSettings.test.tsx +++ b/desktop/src/__tests__/agentsSettings.test.tsx @@ -57,8 +57,8 @@ const MOCK_AGENTS = [ { agentType: 'doc-writer', description: 'Writes technical documentation', - model: 'claude-haiku-4-5-20251001', - modelDisplay: 'claude-haiku-4-5-20251001', + model: 'claude-haiku-4-5', + modelDisplay: 'claude-haiku-4-5', tools: ['Read'], systemPrompt: 'You write clear and concise docs.', color: 'green', diff --git a/src/constants/prompts.ts b/src/constants/prompts.ts index 9eb49b36..bb7240e6 100644 --- a/src/constants/prompts.ts +++ b/src/constants/prompts.ts @@ -121,7 +121,7 @@ const FRONTIER_MODEL_NAME = 'Claude Opus 4.6' const CLAUDE_4_5_OR_4_6_MODEL_IDS = { opus: 'claude-opus-4-6', sonnet: 'claude-sonnet-4-6', - haiku: 'claude-haiku-4-5-20251001', + haiku: 'claude-haiku-4-5', } function getHooksSection(): string { diff --git a/src/main.tsx b/src/main.tsx index aa88a150..2846ad35 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1605,7 +1605,7 @@ async function run(): Promise { // `type: 'stdio'`. An enterprise-config ant with the GB gate on would // otherwise process.exit(1). Chrome has the same latent issue but has // shipped without incident; chicago places itself correctly. - if (getPlatform() === 'macos' && !getIsNonInteractiveSession()) { + if (getPlatform() === 'macos') { try { const { getChicagoEnabled diff --git a/src/server/__tests__/e2e/business-flow.test.ts b/src/server/__tests__/e2e/business-flow.test.ts index 9fe98196..f033f23a 100644 --- a/src/server/__tests__/e2e/business-flow.test.ts +++ b/src/server/__tests__/e2e/business-flow.test.ts @@ -63,7 +63,7 @@ describe('Business Flow: Scheduled Tasks', () => { prompt: 'Look at git log from yesterday, summarize changes, list blockers', recurring: true, permissionMode: 'default', - model: 'claude-sonnet-4-6-20250514', + model: 'claude-sonnet-4-6', folderPath: '/Users/dev/project', useWorktree: true, }) @@ -75,7 +75,7 @@ describe('Business Flow: Scheduled Tasks', () => { expect(data.task.prompt).toContain('git log') expect(data.task.recurring).toBe(true) expect(data.task.permissionMode).toBe('default') - expect(data.task.model).toBe('claude-sonnet-4-6-20250514') + expect(data.task.model).toBe('claude-sonnet-4-6') expect(data.task.createdAt).toBeGreaterThan(0) }) @@ -223,7 +223,7 @@ describe('Business Flow: Agent Management', () => { const { status, data } = await api('POST', '/api/agents', { name: 'security-auditor', description: 'Audits code for security vulnerabilities', - model: 'claude-opus-4-6-20250610', + model: 'claude-opus-4-6', tools: ['Read', 'Grep', 'Glob', 'Bash'], systemPrompt: 'You are a security expert. Focus on OWASP top 10.', color: 'red', @@ -235,7 +235,7 @@ describe('Business Flow: Agent Management', () => { const { status } = await api('POST', '/api/agents', { name: 'test-writer', description: 'Writes unit tests', - model: 'claude-sonnet-4-6-20250514', + model: 'claude-sonnet-4-6', tools: ['Read', 'Write', 'Bash'], }) expect(status).toBe(201) @@ -258,7 +258,7 @@ describe('Business Flow: Agent Management', () => { const { data } = await api('GET', '/api/agents/security-auditor') expect(data.agent.name).toBe('security-auditor') expect(data.agent.description).toContain('security') - expect(data.agent.model).toBe('claude-opus-4-6-20250610') + expect(data.agent.model).toBe('claude-opus-4-6') expect(data.agent.systemPrompt).toContain('OWASP') }) @@ -331,22 +331,22 @@ describe('Business Flow: Models & Effort', () => { it('should default to Sonnet model', async () => { const { data } = await api('GET', '/api/models/current') - expect(data.model.id).toBe('claude-sonnet-4-6-20250514') + expect(data.model.id).toBe('claude-sonnet-4-6') }) it('should switch to Opus 4.6', async () => { const { status } = await api('PUT', '/api/models/current', { - modelId: 'claude-opus-4-6-20250610', + modelId: 'claude-opus-4-6', }) expect(status).toBe(200) const { data } = await api('GET', '/api/models/current') - expect(data.model.id).toBe('claude-opus-4-6-20250610') + expect(data.model.id).toBe('claude-opus-4-6') expect(data.model.name).toBe('Opus 4.6') }) it('should switch to Haiku 4.5', async () => { - await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5-20251001' }) + await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5' }) const { data } = await api('GET', '/api/models/current') expect(data.model.name).toBe('Haiku 4.5') }) @@ -389,13 +389,13 @@ describe('Business Flow: Models & Effort', () => { }) it('should persist model and effort to settings file', async () => { - await api('PUT', '/api/models/current', { modelId: 'claude-opus-4-6-20250610' }) + await api('PUT', '/api/models/current', { modelId: 'claude-opus-4-6' }) await api('PUT', '/api/effort', { level: 'high' }) const settingsPath = path.join(tmpDir, 'settings.json') const raw = await fs.readFile(settingsPath, 'utf-8') const settings = JSON.parse(raw) - expect(settings.model).toBe('claude-opus-4-6-20250610') + expect(settings.model).toBe('claude-opus-4-6') expect(settings.effort).toBe('high') }) }) @@ -716,7 +716,7 @@ describe('Business Flow: Settings Persistence', () => { it('should write and read complex settings', async () => { const settings = { theme: 'dark', - model: 'claude-opus-4-6-20250610', + model: 'claude-opus-4-6', effort: 'high', outputStyle: 'verbose', permissions: { @@ -729,7 +729,7 @@ describe('Business Flow: Settings Persistence', () => { const { data } = await api('GET', '/api/settings/user') expect(data.theme).toBe('dark') - expect(data.model).toBe('claude-opus-4-6-20250610') + expect(data.model).toBe('claude-opus-4-6') expect(data.permissions.allow).toContain('Read') expect(data.permissions.deny).toContain('Bash(rm -rf /)') }) @@ -758,7 +758,7 @@ describe('Business Flow: Settings Persistence', () => { }) it('should merge user and project settings', async () => { - await api('PUT', '/api/settings/user', { theme: 'light', model: 'claude-sonnet-4-6-20250514' }) + await api('PUT', '/api/settings/user', { theme: 'light', model: 'claude-sonnet-4-6' }) const { data } = await api('GET', '/api/settings') expect(data.theme).toBeDefined() diff --git a/src/server/__tests__/e2e/full-flow.test.ts b/src/server/__tests__/e2e/full-flow.test.ts index 2d00ed49..4f175179 100644 --- a/src/server/__tests__/e2e/full-flow.test.ts +++ b/src/server/__tests__/e2e/full-flow.test.ts @@ -133,11 +133,11 @@ describe('E2E: Full Flow', () => { }) it('should update and read user settings', async () => { - await api('PUT', '/api/settings/user', { theme: 'dark', model: 'claude-sonnet-4-6-20250514' }) + await api('PUT', '/api/settings/user', { theme: 'dark', model: 'claude-sonnet-4-6' }) const { data } = await api('GET', '/api/settings/user') expect(data.theme).toBe('dark') - expect(data.model).toBe('claude-sonnet-4-6-20250514') + expect(data.model).toBe('claude-sonnet-4-6') }) it('should get and set permission mode', async () => { @@ -163,10 +163,10 @@ describe('E2E: Full Flow', () => { }) it('should switch model', async () => { - await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5-20251001' }) + await api('PUT', '/api/models/current', { modelId: 'claude-haiku-4-5' }) const { data } = await api('GET', '/api/models/current') - expect(data.model.id).toBe('claude-haiku-4-5-20251001') + expect(data.model.id).toBe('claude-haiku-4-5') }) it('should get and set effort level', async () => { @@ -256,7 +256,7 @@ describe('E2E: Full Flow', () => { const { status } = await api('POST', '/api/agents', { name: 'test-agent', description: 'A test agent', - model: 'claude-sonnet-4-6-20250514', + model: 'claude-sonnet-4-6', }) expect(status).toBe(201) }) diff --git a/src/server/__tests__/providers-real.test.ts b/src/server/__tests__/providers-real.test.ts index 31655950..ad7e8b76 100644 --- a/src/server/__tests__/providers-real.test.ts +++ b/src/server/__tests__/providers-real.test.ts @@ -1,6 +1,7 @@ /** * 用真实的 Provider 配置测试 ProviderService - * 验证添加、激活、settings.json 同步是否正确 + * 验证添加、激活、cc-haha/settings.json 同步是否正确 + * (provider env 写到 ~/.claude/cc-haha/settings.json,不污染原版 settings.json) */ import { describe, test, expect, beforeEach, afterEach } from 'bun:test' @@ -9,6 +10,13 @@ import * as path from 'path' import * as os from 'os' import { ProviderService } from '../services/providerService.js' +const MODEL_MAPPING = { + main: 'MiniMax-M2.7-highspeed', + haiku: 'MiniMax-M2.7-highspeed', + sonnet: 'MiniMax-M2.7-highspeed', + opus: 'MiniMax-M2.7-highspeed', +} + describe('Real Provider Configs', () => { let tmpDir: string let service: ProviderService @@ -24,204 +32,213 @@ describe('Real Provider Configs', () => { await fs.rm(tmpDir, { recursive: true, force: true }) }) - test('添加 MiniMax Provider 并激活', async () => { + // Helper: read the Haha-specific settings file + async function readCcHahaSettings(): Promise> { + const raw = await fs.readFile(path.join(tmpDir, 'cc-haha', 'settings.json'), 'utf-8') + return JSON.parse(raw) + } + + // Helper: check original settings.json is NOT modified + async function originalSettingsExists(): Promise { + try { + await fs.access(path.join(tmpDir, 'settings.json')) + return true + } catch { + return false + } + } + + test('添加 MiniMax Provider 并激活 — 写入 cc-haha/settings.json', async () => { const minimax = await service.addProvider({ + presetId: 'minimax', name: 'MiniMax', baseUrl: 'https://api.minimaxi.com/anthropic', apiKey: 'sk-fake-test-key-for-testing-only', - models: [ - { id: 'MiniMax-M2.7-highspeed', name: 'MiniMax M2.7 Highspeed', description: 'MiniMax 高速模型' }, - ], + models: MODEL_MAPPING, notes: 'MiniMax 官方 Anthropic 兼容接口', }) - // 第一个 provider 应该自动激活 - expect(minimax.isActive).toBe(true) expect(minimax.name).toBe('MiniMax') - // 验证 settings.json 写入 - const settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8')) - expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic') - expect(settings.env.ANTHROPIC_AUTH_TOKEN).toBe(minimax.apiKey) - expect(settings.model).toBe('MiniMax-M2.7-highspeed') + // 激活 provider + await service.activateProvider(minimax.id) - console.log('✅ MiniMax Provider 添加并自动激活成功') - console.log(' settings.json env:', JSON.stringify(settings.env, null, 2)) - console.log(' settings.json model:', settings.model) + // 验证写入 cc-haha/settings.json + const settings = await readCcHahaSettings() + expect((settings.env as Record).ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic') + expect((settings.env as Record).ANTHROPIC_AUTH_TOKEN).toBe('sk-fake-test-key-for-testing-only') + expect((settings.env as Record).ANTHROPIC_MODEL).toBe('MiniMax-M2.7-highspeed') + + // 验证原版 settings.json 没有被创建 + expect(await originalSettingsExists()).toBe(false) + + console.log('✅ Provider 写入 cc-haha/settings.json,原版 settings.json 未被污染') }) - test('添加接口AI中转站 Provider,切换激活', async () => { - // 先添加 MiniMax(自动激活) + test('切换 Provider — 更新 cc-haha/settings.json', async () => { const minimax = await service.addProvider({ + presetId: 'minimax', name: 'MiniMax', baseUrl: 'https://api.minimaxi.com/anthropic', apiKey: 'sk-api-test-minimax', - models: [ - { id: 'MiniMax-M2.7-highspeed', name: 'MiniMax M2.7 Highspeed' }, - ], + models: MODEL_MAPPING, }) - expect(minimax.isActive).toBe(true) - // 添加接口AI中转站(不应自动激活) const jiekou = await service.addProvider({ + presetId: 'custom', name: '接口AI中转站', baseUrl: 'https://api.jiekou.ai/anthropic', apiKey: 'sk-fake-test-key-for-testing-only', - models: [ - { id: 'claude-opus-4-6', name: 'Opus 4.6', description: 'Most capable', context: '200k' }, - { id: 'claude-sonnet-4-6', name: 'Sonnet 4.6', description: 'Most efficient', context: '200k' }, - { id: 'claude-haiku-4-5-20251001', name: 'Haiku 4.5', description: 'Fastest', context: '200k' }, - ], - notes: '接口AI中转站 — 支持多个 Claude 模型', - }) - expect(jiekou.isActive).toBe(false) - - // 激活接口AI中转站,选择 Opus 4.6 - await service.activateProvider(jiekou.id, 'claude-opus-4-6') - - // 验证 providers.json - const providers = await service.listProviders() - const activeMinimax = providers.find(p => p.id === minimax.id)! - const activeJiekou = providers.find(p => p.id === jiekou.id)! - expect(activeMinimax.isActive).toBe(false) - expect(activeJiekou.isActive).toBe(true) - - // 验证 settings.json - const settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8')) - expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic') - expect(settings.env.ANTHROPIC_AUTH_TOKEN).toBe(jiekou.apiKey) - expect(settings.model).toBe('claude-opus-4-6') - - console.log('✅ 接口AI中转站激活成功') - console.log(' settings.json env:', JSON.stringify(settings.env, null, 2)) - console.log(' settings.json model:', settings.model) - }) - - test('切换模型 — 从 Opus 切到 Sonnet', async () => { - const jiekou = await service.addProvider({ - name: '接口AI中转站', - baseUrl: 'https://api.jiekou.ai/anthropic', - apiKey: 'sk_test_jiekou', - models: [ - { id: 'claude-opus-4-6', name: 'Opus 4.6' }, - { id: 'claude-sonnet-4-6', name: 'Sonnet 4.6' }, - { id: 'claude-haiku-4-5-20251001', name: 'Haiku 4.5' }, - ], + models: { + main: 'claude-opus-4-6', + haiku: 'claude-haiku-4-5', + sonnet: 'claude-sonnet-4-6', + opus: 'claude-opus-4-6', + }, }) - // 自动激活了第一个模型 opus - let settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8')) - expect(settings.model).toBe('claude-opus-4-6') + // 先激活 MiniMax + await service.activateProvider(minimax.id) + let settings = await readCcHahaSettings() + expect((settings.env as Record).ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic') - // 切换到 Sonnet - await service.activateProvider(jiekou.id, 'claude-sonnet-4-6') - settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8')) - expect(settings.model).toBe('claude-sonnet-4-6') + // 切换到接口AI中转站 + await service.activateProvider(jiekou.id) + settings = await readCcHahaSettings() + expect((settings.env as Record).ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic') + expect((settings.env as Record).ANTHROPIC_AUTH_TOKEN).toBe('sk-fake-test-key-for-testing-only') + expect((settings.env as Record).ANTHROPIC_MODEL).toBe('claude-opus-4-6') - console.log('✅ 模型切换成功: opus → sonnet') + // 验证 activeId 正确 + const list = await service.listProviders() + expect(list.activeId).toBe(jiekou.id) + + // 原版 settings.json 依然不存在 + expect(await originalSettingsExists()).toBe(false) + + console.log('✅ 切换 Provider 成功,cc-haha/settings.json 更新正确') }) - test('settings.json 保留已有字段', async () => { - // 预写一个有内容的 settings.json(模拟用户已有配置) + test('cc-haha/settings.json 保留已有字段', async () => { + // 预写一个有内容的 cc-haha/settings.json(模拟用户已有配置) + await fs.mkdir(path.join(tmpDir, 'cc-haha'), { recursive: true }) await fs.writeFile( - path.join(tmpDir, 'settings.json'), + path.join(tmpDir, 'cc-haha', 'settings.json'), JSON.stringify({ - effortLevel: 'high', - language: '中文', - skipDangerousModePermissionPrompt: true, + customField: 'should_be_preserved', env: { - CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1', EXISTING_VAR: 'should_be_preserved', }, - hooks: { - Stop: [{ hooks: [{ command: 'echo done', type: 'command' }], matcher: '' }], - }, }, null, 2), ) // 添加并激活 provider - await service.addProvider({ + const provider = await service.addProvider({ + presetId: 'custom', name: '接口AI中转站', baseUrl: 'https://api.jiekou.ai/anthropic', apiKey: 'sk_test', - models: [{ id: 'claude-opus-4-6', name: 'Opus 4.6' }], + models: { + main: 'claude-opus-4-6', + haiku: 'claude-haiku-4-5', + sonnet: 'claude-sonnet-4-6', + opus: 'claude-opus-4-6', + }, }) + await service.activateProvider(provider.id) - const settings = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8')) + const settings = await readCcHahaSettings() // 验证新字段写入 - expect(settings.env.ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic') - expect(settings.env.ANTHROPIC_AUTH_TOKEN).toBe('sk_test') - expect(settings.model).toBe('claude-opus-4-6') + expect((settings.env as Record).ANTHROPIC_BASE_URL).toBe('https://api.jiekou.ai/anthropic') + expect((settings.env as Record).ANTHROPIC_AUTH_TOKEN).toBe('sk_test') // 验证已有字段保留 - expect(settings.effortLevel).toBe('high') - expect(settings.language).toBe('中文') - expect(settings.skipDangerousModePermissionPrompt).toBe(true) - expect(settings.env.CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS).toBe('1') - expect(settings.env.EXISTING_VAR).toBe('should_be_preserved') - expect(settings.hooks).toBeDefined() - expect(settings.hooks.Stop).toHaveLength(1) + expect(settings.customField).toBe('should_be_preserved') + expect((settings.env as Record).EXISTING_VAR).toBe('should_be_preserved') - console.log('✅ settings.json 已有字段全部保留') - console.log(' 完整 settings:', JSON.stringify(settings, null, 2)) + console.log('✅ cc-haha/settings.json 已有字段全部保留') }) - test('连通性测试 — MiniMax(预期能连但可能 401)', async () => { - // 使用假 key 测试连通性机制本身 + test('activateOfficial 清除 provider env', async () => { + const provider = await service.addProvider({ + presetId: 'minimax', + name: 'MiniMax', + baseUrl: 'https://api.minimaxi.com/anthropic', + apiKey: 'sk-test', + models: MODEL_MAPPING, + }) + + await service.activateProvider(provider.id) + + // 确认写入了 + let settings = await readCcHahaSettings() + expect((settings.env as Record).ANTHROPIC_BASE_URL).toBeDefined() + + // 切换到 official + await service.activateOfficial() + + settings = await readCcHahaSettings() + const env = settings.env as Record | undefined + expect(env?.ANTHROPIC_BASE_URL).toBeUndefined() + expect(env?.ANTHROPIC_AUTH_TOKEN).toBeUndefined() + expect(env?.ANTHROPIC_MODEL).toBeUndefined() + + console.log('✅ activateOfficial 正确清除了 provider env') + }) + + test('连通性测试 — 返回结构正确', async () => { const result = await service.testProviderConfig({ baseUrl: 'https://api.minimaxi.com/anthropic', apiKey: 'sk-fake-test-key', modelId: 'MiniMax-M2.7-highspeed', }) - console.log('🔌 MiniMax 连通性测试结果:') - console.log(' success:', result.success) - console.log(' latencyMs:', result.latencyMs) - console.log(' httpStatus:', result.httpStatus) - console.log(' error:', result.error) - console.log(' modelUsed:', result.modelUsed) + // testProviderConfig 返回 { connectivity: { ... }, proxy?: { ... } } + expect(result.connectivity).toBeDefined() + expect(result.connectivity.latencyMs).toBeGreaterThanOrEqual(0) + expect(result.connectivity.modelUsed).toBe('MiniMax-M2.7-highspeed') - // 不断言成功/失败,因为假 key 肯定失败,但验证机制正常工作 - expect(result.latencyMs).toBeGreaterThanOrEqual(0) - expect(result.modelUsed).toBe('MiniMax-M2.7-highspeed') + console.log('🔌 MiniMax 连通性测试结果:') + console.log(' success:', result.connectivity.success) + console.log(' latencyMs:', result.connectivity.latencyMs) + console.log(' error:', result.connectivity.error) }) - test('GAP 分析 — 用户配置中我们未覆盖的字段', () => { - // 用户的 MiniMax 配置包含我们未处理的字段: - const minimaxConfig = { - env: { - ANTHROPIC_AUTH_TOKEN: 'sk-...', - ANTHROPIC_BASE_URL: 'https://api.minimaxi.com/anthropic', - ANTHROPIC_DEFAULT_HAIKU_MODEL: 'MiniMax-M2.7-highspeed', // ❌ 未支持 - ANTHROPIC_DEFAULT_OPUS_MODEL: 'MiniMax-M2.7-highspeed', // ❌ 未支持 - ANTHROPIC_DEFAULT_SONNET_MODEL: 'MiniMax-M2.7-highspeed', // ❌ 未支持 - ANTHROPIC_MODEL: 'MiniMax-M2.7-highspeed', - API_TIMEOUT_MS: '3000000', // ❌ 未支持 - CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1', // ❌ 未支持 - }, - skipDangerousModePermissionPrompt: true, // ❌ 未支持 - } + test('providers.json 和 cc-haha/settings.json 独立于 settings.json', async () => { + // 模拟原版 Claude Code 的 settings.json 已存在 + await fs.writeFile( + path.join(tmpDir, 'settings.json'), + JSON.stringify({ + effortLevel: 'high', + env: { + ANTHROPIC_BASE_URL: 'https://original-claude-code.api.com', + ANTHROPIC_AUTH_TOKEN: 'original-key', + }, + }, null, 2), + ) - // 用户的接口AI配置包含更多未覆盖字段: - const jiekouConfig = { - effortLevel: 'high', // ❌ 未同步 - enabledPlugins: { /* ... */ }, // ❌ 未同步 - env: { - CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1', // ❌ 未支持 - }, - hooks: { /* ... */ }, // ❌ 未同步 - } + // Haha 添加并激活自己的 provider + const provider = await service.addProvider({ + presetId: 'minimax', + name: 'MiniMax', + baseUrl: 'https://api.minimaxi.com/anthropic', + apiKey: 'sk-haha-key', + models: MODEL_MAPPING, + }) + await service.activateProvider(provider.id) - // 打印 GAP 分析 - console.log('\n📋 GAP 分析 — 需要在 syncToSettings 中额外支持的字段:') - console.log(' 1. ANTHROPIC_DEFAULT_HAIKU_MODEL — 各模型的 tier 默认值') - console.log(' 2. ANTHROPIC_DEFAULT_SONNET_MODEL') - console.log(' 3. ANTHROPIC_DEFAULT_OPUS_MODEL') - console.log(' 4. API_TIMEOUT_MS — 超时配置') - console.log(' 5. 其他自定义 env vars(应支持 extraEnv 字段)') - console.log(' 6. skipDangerousModePermissionPrompt — 非 env 的额外 settings 字段') + // 验证原版 settings.json 没被修改 + const original = JSON.parse(await fs.readFile(path.join(tmpDir, 'settings.json'), 'utf-8')) + expect((original.env as Record).ANTHROPIC_BASE_URL).toBe('https://original-claude-code.api.com') + expect((original.env as Record).ANTHROPIC_AUTH_TOKEN).toBe('original-key') + expect(original.effortLevel).toBe('high') - expect(true).toBe(true) // GAP 分析通过 + // 验证 cc-haha/settings.json 是 Haha 自己的 + const haha = await readCcHahaSettings() + expect((haha.env as Record).ANTHROPIC_BASE_URL).toBe('https://api.minimaxi.com/anthropic') + expect((haha.env as Record).ANTHROPIC_AUTH_TOKEN).toBe('sk-haha-key') + + console.log('✅ 原版 settings.json 完好无损,Haha 配置独立存储') }) }) diff --git a/src/server/__tests__/settings.test.ts b/src/server/__tests__/settings.test.ts index 0517af6b..3310369c 100644 --- a/src/server/__tests__/settings.test.ts +++ b/src/server/__tests__/settings.test.ts @@ -64,21 +64,21 @@ describe('SettingsService', () => { it('should write and read user settings', async () => { const svc = new SettingsService() - await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6-20250610' }) + await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6' }) const settings = await svc.getUserSettings() expect(settings.theme).toBe('dark') - expect(settings.model).toBe('claude-opus-4-6-20250610') + expect(settings.model).toBe('claude-opus-4-6') }) it('should merge settings on update (shallow merge)', async () => { const svc = new SettingsService() await svc.updateUserSettings({ theme: 'dark' }) - await svc.updateUserSettings({ model: 'claude-haiku-4-5-20251001' }) + await svc.updateUserSettings({ model: 'claude-haiku-4-5' }) const settings = await svc.getUserSettings() expect(settings.theme).toBe('dark') - expect(settings.model).toBe('claude-haiku-4-5-20251001') + expect(settings.model).toBe('claude-haiku-4-5') }) it('should read and write project settings', async () => { @@ -97,14 +97,14 @@ describe('SettingsService', () => { await fs.mkdir(path.join(projectRoot, '.claude'), { recursive: true }) const svc = new SettingsService(projectRoot) - await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6-20250610' }) + await svc.updateUserSettings({ theme: 'dark', model: 'claude-opus-4-6' }) await svc.updateProjectSettings({ theme: 'light' }) const merged = await svc.getSettings() // project overrides user expect(merged.theme).toBe('light') // user value preserved when not overridden - expect(merged.model).toBe('claude-opus-4-6-20250610') + expect(merged.model).toBe('claude-opus-4-6') }) it('should get default permission mode', async () => { @@ -168,7 +168,7 @@ describe('Settings API', () => { it('PUT /api/settings/user should update user settings', async () => { const { req, url, segments } = makeRequest('PUT', '/api/settings/user', { - model: 'claude-opus-4-6-20250610', + model: 'claude-opus-4-6', }) const res = await handleSettingsApi(req, url, segments) @@ -180,7 +180,7 @@ describe('Settings API', () => { const { req: r2, url: u2, segments: s2 } = makeRequest('GET', '/api/settings/user') const res2 = await handleSettingsApi(r2, u2, s2) const body2 = await res2.json() - expect(body2.model).toBe('claude-opus-4-6-20250610') + expect(body2.model).toBe('claude-opus-4-6') }) it('GET /api/permissions/mode should return default mode', async () => { @@ -245,25 +245,25 @@ describe('Models API', () => { expect(res.status).toBe(200) const body = await res.json() - expect(body.model.id).toBe('claude-sonnet-4-6-20250514') + expect(body.model.id).toBe('claude-sonnet-4-6') }) it('PUT /api/models/current should switch model', async () => { const { req, url, segments } = makeRequest('PUT', '/api/models/current', { - modelId: 'claude-opus-4-6-20250610', + modelId: 'claude-opus-4-6', }) const res = await handleModelsApi(req, url, segments) expect(res.status).toBe(200) const body = await res.json() expect(body.ok).toBe(true) - expect(body.model).toBe('claude-opus-4-6-20250610') + expect(body.model).toBe('claude-opus-4-6') // Verify persisted const { req: r2, url: u2, segments: s2 } = makeRequest('GET', '/api/models/current') const res2 = await handleModelsApi(r2, u2, s2) const body2 = await res2.json() - expect(body2.model.id).toBe('claude-opus-4-6-20250610') + expect(body2.model.id).toBe('claude-opus-4-6') }) it('PUT /api/models/current should reject missing modelId', async () => { diff --git a/src/server/api/models.ts b/src/server/api/models.ts index edbb3ec5..28cf170a 100644 --- a/src/server/api/models.ts +++ b/src/server/api/models.ts @@ -16,25 +16,19 @@ import { ApiError, errorResponse } from '../middleware/errorHandler.js' const DEFAULT_MODELS = [ { - id: 'claude-opus-4-6-20250610', + id: 'claude-opus-4-6', name: 'Opus 4.6', description: 'Most capable for ambitious work', - context: '200k', - }, - { - id: 'claude-opus-4-6-20250610:1m', - name: 'Opus 4.6 1M', - description: 'Most capable for ambitious work', context: '1m', }, { - id: 'claude-sonnet-4-6-20250514', + id: 'claude-sonnet-4-6', name: 'Sonnet 4.6', description: 'Most efficient for everyday tasks', context: '200k', }, { - id: 'claude-haiku-4-5-20251001', + id: 'claude-haiku-4-5', name: 'Haiku 4.5', description: 'Fastest for quick answers', context: '200k', @@ -43,8 +37,8 @@ const DEFAULT_MODELS = [ const EFFORT_LEVELS = ['low', 'medium', 'high', 'max'] as const -const DEFAULT_MODEL = 'claude-sonnet-4-6-20250514' -const DEFAULT_EFFORT = 'medium' +const DEFAULT_MODEL = 'claude-opus-4-6' +const DEFAULT_EFFORT = 'max' const settingsService = new SettingsService() const providerService = new ProviderService()