fix: unblock input after ESC cancel, suppress abort errors, add hint

- Use Promise.race in handleSlashCommand so ESC abort immediately
  unblocks the submitQuery await chain (fixes /compress blocking input)
- Suppress abort error messages in /compress and /summary when
  cancelled via ESC (cancelSlashCommand already shows "Command cancelled")
- Add "(esc to cancel)" hint below pending slash command items
- Add i18n translations for the new hint in all 6 locales
This commit is contained in:
DragonnZhang 2026-02-11 11:10:40 +08:00
parent 66f754e203
commit 5376ca5873
13 changed files with 106 additions and 7 deletions

View file

@ -20,6 +20,7 @@ export const compressCommand: SlashCommand = {
action: async (context) => {
const { ui } = context;
const executionMode = context.executionMode ?? 'interactive';
const abortSignal = context.abortSignal;
if (executionMode === 'interactive' && ui.pendingItem) {
ui.addItem(
@ -96,6 +97,10 @@ export const compressCommand: SlashCommand = {
const compressed = await doCompress();
if (abortSignal?.aborted) {
return;
}
if (!compressed) {
if (executionMode === 'interactive') {
ui.addItem(
@ -137,6 +142,10 @@ export const compressCommand: SlashCommand = {
content: `Context compressed (${compressed.originalTokenCount} -> ${compressed.newTokenCount}).`,
};
} catch (e) {
// If cancelled via ESC, don't show error — cancelSlashCommand already handled UI
if (abortSignal?.aborted) {
return;
}
if (executionMode === 'interactive') {
ui.addItem(
{

View file

@ -104,6 +104,15 @@ export const setupGithubCommand: SlashCommand = {
): Promise<SlashCommandActionReturn> => {
const abortController = new AbortController();
// If we have a context abort signal (from ESC cancellation), link it to our controller
if (context.abortSignal) {
context.abortSignal.addEventListener(
'abort',
() => abortController.abort(),
{ once: true },
);
}
if (!isGitHubRepository()) {
throw new Error(
'Unable to determine the GitHub repository. /setup-github must be run from a git repository.',

View file

@ -27,6 +27,7 @@ export const summaryCommand: SlashCommand = {
const { config } = context.services;
const { ui } = context;
const executionMode = context.executionMode ?? 'interactive';
const abortSignal = context.abortSignal;
if (!config) {
return {
@ -101,7 +102,7 @@ export const summaryCommand: SlashCommand = {
},
],
{},
new AbortController().signal,
abortSignal ?? new AbortController().signal,
config.getModel(),
);
@ -197,6 +198,10 @@ export const summaryCommand: SlashCommand = {
if (executionMode !== 'interactive') {
return;
}
// If cancelled via ESC, don't show error — cancelSlashCommand already handled UI
if (abortSignal?.aborted) {
return;
}
ui.setPendingItem(null);
ui.addItem(
{
@ -241,6 +246,9 @@ export const summaryCommand: SlashCommand = {
}> => {
emitInteractivePending('generating');
const markdownSummary = await generateSummaryMarkdown(history);
if (abortSignal?.aborted) {
throw new DOMException('Summary generation cancelled.', 'AbortError');
}
emitInteractivePending('saving');
const { filePathForDisplay } = await saveSummaryToDisk(markdownSummary);
completeInteractive(filePathForDisplay);

View file

@ -89,6 +89,8 @@ export interface CommandContext {
};
// Flag to indicate if an overwrite has been confirmed
overwriteConfirmed?: boolean;
/** Abort signal for cancelling long-running slash command operations via ESC. */
abortSignal?: AbortSignal;
}
/**