mirror of
https://github.com/NanmiCoder/cc-haha
synced 2026-07-18 13:23:33 +08:00
Reduce desktop first-token latency in sdk-url sessions
Desktop-managed sessions were blocking the first visible token on slow or failing regular MCP connections, which made the UI feel much slower than direct bin runs. This keeps partial assistant streaming enabled for desktop sessions and moves regular MCP connection work off the critical path only for sdk-url startup. Constraint: Desktop sdk-url sessions must preserve chat responsiveness even when local MCP endpoints fail or hang Rejected: Leave MCP startup fully blocking in sdk-url mode | first visible token stayed several seconds behind direct CLI runs Confidence: high Scope-risk: moderate Reversibility: clean Directive: Keep blocking MCP startup for plain -p sessions unless you re-measure first-turn latency and tool availability tradeoffs Tested: bun test src/server/__tests__/conversation-service.test.ts src/server/__tests__/conversations.test.ts; same-prompt timing comparison for direct bin vs sdk-url session path Not-tested: Immediate first-turn availability of slow regular MCP tools in desktop sdk-url sessions
This commit is contained in:
parent
e78b973e8c
commit
7651b24a50
@ -39,7 +39,7 @@ type SettingsStore = {
|
||||
export const useSettingsStore = create<SettingsStore>((set, get) => ({
|
||||
permissionMode: 'default',
|
||||
currentModel: null,
|
||||
effortLevel: 'high',
|
||||
effortLevel: 'medium',
|
||||
availableModels: [],
|
||||
activeProviderName: null,
|
||||
locale: getStoredLocale(),
|
||||
|
||||
22
src/main.tsx
22
src/main.tsx
@ -2723,16 +2723,30 @@ async function run(): Promise<CommanderCommand> {
|
||||
}));
|
||||
}, 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.
|
||||
|
||||
@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
@ -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()
|
||||
|
||||
@ -64,6 +64,33 @@ export class ConversationStartupError extends Error {
|
||||
export class ConversationService {
|
||||
private sessions = new Map<string, SessionProcess>()
|
||||
|
||||
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)`,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user