diff --git a/desktop/src/stores/settingsStore.ts b/desktop/src/stores/settingsStore.ts index a08d0fdb..2fa8f56e 100644 --- a/desktop/src/stores/settingsStore.ts +++ b/desktop/src/stores/settingsStore.ts @@ -39,7 +39,7 @@ type SettingsStore = { export const useSettingsStore = create((set, get) => ({ permissionMode: 'default', currentModel: null, - effortLevel: 'high', + effortLevel: 'medium', availableModels: [], activeProviderName: null, locale: getStoredLocale(), diff --git a/src/main.tsx b/src/main.tsx index 0b0ef6f7..7fa988bf 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -2723,16 +2723,30 @@ async function run(): Promise { })); }, configs).catch(err => logForDebugging(`[MCP] ${label} connect error: ${err}`)); }; - // Await all MCP configs — print mode is often single-turn, so - // "late-connecting servers visible next turn" doesn't help. SDK init - // message and turn-1 tool list both need configured MCP tools present. + // Await all MCP configs for regular print-mode sessions — they're + // often single-turn, so "late-connecting servers visible next turn" + // doesn't help. SDK init message and turn-1 tool list should include + // configured MCP tools when running plain `claude -p`. + // + // Desktop/bridge sessions (`--sdk-url`) are different: the user is + // waiting on the first visible assistant token, not a fully populated + // MCP inventory. Blocking startup on slow or failing MCP servers (for + // example a local `chatlog` HTTP endpoint timing out) makes the whole + // chat feel frozen before the first word appears. In that mode we still + // kick off the connections immediately, but let them settle in the + // background so headlessStore picks them up after the session starts. + // // Zero-server case is free via the early return in connectMcpBatch. // Connectors parallelize inside getMcpToolsCommandsAndResources // (processBatched with Promise.all). claude.ai is awaited too — its // fetch was kicked off early (line ~2558) so only residual time blocks // here. --bare skips claude.ai entirely for perf-sensitive scripts. profileCheckpoint('before_connectMcp'); - await connectMcpBatch(regularMcpConfigs, 'regular'); + if (sdkUrl) { + void connectMcpBatch(regularMcpConfigs, 'regular'); + } else { + await connectMcpBatch(regularMcpConfigs, 'regular'); + } profileCheckpoint('after_connectMcp'); // Dedup: suppress plugin MCP servers that duplicate a claude.ai // connector (connector wins), then connect claude.ai servers. diff --git a/src/server/__tests__/conversation-service.test.ts b/src/server/__tests__/conversation-service.test.ts index 249a3439..7e049c06 100644 --- a/src/server/__tests__/conversation-service.test.ts +++ b/src/server/__tests__/conversation-service.test.ts @@ -170,4 +170,18 @@ describe('ConversationService', () => { expect(args[0]).toContain(path.join('bin', 'claude-haha')) } }) + + test('buildSessionCliArgs enables partial assistant messages for desktop streaming', () => { + const service = new ConversationService() as any + const args = service.buildSessionCliArgs( + '123e4567-e89b-12d3-a456-426614174000', + 'ws://127.0.0.1:3456/sdk/test-session?token=test-token', + false, + { permissionMode: 'bypassPermissions' }, + ) as string[] + + expect(args).toContain('--include-partial-messages') + expect(args).toContain('--sdk-url') + expect(args).toContain('--replay-user-messages') + }) }) diff --git a/src/server/api/models.ts b/src/server/api/models.ts index c10d4fe1..f90d530b 100644 --- a/src/server/api/models.ts +++ b/src/server/api/models.ts @@ -38,7 +38,7 @@ const DEFAULT_MODELS = [ const EFFORT_LEVELS = ['low', 'medium', 'high', 'max'] as const const DEFAULT_MODEL = 'claude-opus-4-7' -const DEFAULT_EFFORT = 'max' +const DEFAULT_EFFORT = 'medium' const settingsService = new SettingsService() const providerService = new ProviderService() diff --git a/src/server/services/conversationService.ts b/src/server/services/conversationService.ts index 66542aea..c0706e13 100644 --- a/src/server/services/conversationService.ts +++ b/src/server/services/conversationService.ts @@ -64,6 +64,33 @@ export class ConversationStartupError extends Error { export class ConversationService { private sessions = new Map() + private buildSessionCliArgs( + sessionId: string, + sdkUrl: string, + shouldResume: boolean, + options?: SessionStartOptions, + ): string[] { + const dangerousMode = process.env.CLAUDE_DANGEROUS_MODE === '1' + return this.resolveCliArgs([ + '--print', + '--verbose', + '--sdk-url', + sdkUrl, + '--enable-auth-status', + '--input-format', + 'stream-json', + '--output-format', + 'stream-json', + // Desktop chat depends on partial assistant deltas; without this the + // server only sees the completed assistant message at turn end. + '--include-partial-messages', + ...(shouldResume ? ['--resume', sessionId] : ['--session-id', sessionId]), + '--replay-user-messages', + ...this.getRuntimeArgs(options), + ...this.getPermissionArgs(options?.permissionMode, dangerousMode), + ]) + } + async startSession( sessionId: string, workDir: string, @@ -88,22 +115,12 @@ export class ConversationService { ) } - const dangerousMode = process.env.CLAUDE_DANGEROUS_MODE === '1' - const args = this.resolveCliArgs([ - '--print', - '--verbose', - '--sdk-url', + const args = this.buildSessionCliArgs( + sessionId, sdkUrl, - '--enable-auth-status', - '--input-format', - 'stream-json', - '--output-format', - 'stream-json', - ...(shouldResume ? ['--resume', sessionId] : ['--session-id', sessionId]), - '--replay-user-messages', - ...this.getRuntimeArgs(options), - ...this.getPermissionArgs(options?.permissionMode, dangerousMode), - ]) + shouldResume, + options, + ) console.log( `[ConversationService] Starting CLI for ${sessionId}, cwd: ${workDir} (process.cwd()=${process.cwd()}, CALLER_DIR will be pinned to workDir)`,