Merge pull request #2656 from QwenLM/fix-issue-qwen-code

fix: resolve /clear command and ESC key lag caused by hooks system
This commit is contained in:
顾盼 2026-03-30 15:32:42 +08:00 committed by GitHub
commit cd935a5896
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 297 additions and 25 deletions

View file

@ -140,6 +140,71 @@ describe('clearCommand', () => {
expect(mockContext.ui.clear).toHaveBeenCalledTimes(1);
});
it('should clear UI before resetChat for immediate responsiveness', async () => {
if (!clearCommand.action) {
throw new Error('clearCommand must have an action.');
}
const callOrder: string[] = [];
(mockContext.ui.clear as ReturnType<typeof vi.fn>).mockImplementation(
() => {
callOrder.push('ui.clear');
},
);
mockResetChat.mockImplementation(async () => {
callOrder.push('resetChat');
});
await clearCommand.action(mockContext, '');
// ui.clear should be called before resetChat for immediate UI feedback
const clearIndex = callOrder.indexOf('ui.clear');
const resetIndex = callOrder.indexOf('resetChat');
expect(clearIndex).toBeGreaterThanOrEqual(0);
expect(resetIndex).toBeGreaterThanOrEqual(0);
expect(clearIndex).toBeLessThan(resetIndex);
});
it('should not await hook events (fire-and-forget)', async () => {
if (!clearCommand.action) {
throw new Error('clearCommand must have an action.');
}
// Make hooks take a long time - they should not block
let sessionEndResolved = false;
let sessionStartResolved = false;
mockFireSessionEndEvent.mockImplementation(
() =>
new Promise((resolve) => {
setTimeout(() => {
sessionEndResolved = true;
resolve(undefined);
}, 5000);
}),
);
mockFireSessionStartEvent.mockImplementation(
() =>
new Promise((resolve) => {
setTimeout(() => {
sessionStartResolved = true;
resolve(undefined);
}, 5000);
}),
);
await clearCommand.action(mockContext, '');
// The action should complete immediately without waiting for hooks
expect(mockContext.ui.clear).toHaveBeenCalledTimes(1);
expect(mockResetChat).toHaveBeenCalledTimes(1);
// Hooks should have been called but not necessarily resolved
expect(mockFireSessionEndEvent).toHaveBeenCalled();
expect(mockFireSessionStartEvent).toHaveBeenCalled();
// Hooks should NOT have resolved yet since they have 5s timeouts
expect(sessionEndResolved).toBe(false);
expect(sessionStartResolved).toBe(false);
});
it('should not attempt to reset chat if config service is not available', async () => {
if (!clearCommand.action) {
throw new Error('clearCommand must have an action.');

View file

@ -27,14 +27,13 @@ export const clearCommand: SlashCommand = {
const { config } = context.services;
if (config) {
// Fire SessionEnd event before clearing (current session ends)
try {
await config
.getHookSystem()
?.fireSessionEndEvent(SessionEndReason.Clear);
} catch (err) {
config.getDebugLogger().warn(`SessionEnd hook failed: ${err}`);
}
// Fire SessionEnd event (non-blocking to avoid UI lag)
config
.getHookSystem()
?.fireSessionEndEvent(SessionEndReason.Clear)
.catch((err) => {
config.getDebugLogger().warn(`SessionEnd hook failed: ${err}`);
});
const newSessionId = config.startNewSession();
@ -54,6 +53,9 @@ export const clearCommand: SlashCommand = {
context.session.startNewSession(newSessionId);
}
// Clear UI first for immediate responsiveness
context.ui.clear();
const geminiClient = config.getGeminiClient();
if (geminiClient) {
context.ui.setDebugMessage(
@ -66,22 +68,20 @@ export const clearCommand: SlashCommand = {
context.ui.setDebugMessage(t('Starting a new session and clearing.'));
}
// Fire SessionStart event after clearing (new session starts)
try {
await config
.getHookSystem()
?.fireSessionStartEvent(
SessionStartSource.Clear,
config.getModel() ?? '',
String(config.getApprovalMode()) as PermissionMode,
);
} catch (err) {
config.getDebugLogger().warn(`SessionStart hook failed: ${err}`);
}
// Fire SessionStart event (non-blocking to avoid UI lag)
config
.getHookSystem()
?.fireSessionStartEvent(
SessionStartSource.Clear,
config.getModel() ?? '',
String(config.getApprovalMode()) as PermissionMode,
)
.catch((err) => {
config.getDebugLogger().warn(`SessionStart hook failed: ${err}`);
});
} else {
context.ui.setDebugMessage(t('Starting a new session and clearing.'));
context.ui.clear();
}
context.ui.clear();
},
};