diff --git a/src/resources/extensions/ask-user-questions.ts b/src/resources/extensions/ask-user-questions.ts index ab7ff280a..3cb7e2ae1 100644 --- a/src/resources/extensions/ask-user-questions.ts +++ b/src/resources/extensions/ask-user-questions.ts @@ -254,7 +254,7 @@ export default function AskUserQuestions(pi: ExtensionAPI) { const raceResult = await raceRemoteAndLocal( () => tryRemoteQuestions(params.questions, raceSignal), - () => showInterviewRound(params.questions, {}, ctx as any), + () => showInterviewRound(params.questions, { signal: raceSignal }, ctx as any), raceController, params.questions, ); diff --git a/src/resources/extensions/shared/interview-ui.ts b/src/resources/extensions/shared/interview-ui.ts index c38833761..66771bc84 100644 --- a/src/resources/extensions/shared/interview-ui.ts +++ b/src/resources/extensions/shared/interview-ui.ts @@ -80,6 +80,12 @@ export interface InterviewRoundOptions { * Label for the Esc-confirm overlay header. Defaults to "End interview?". */ exitHeadline?: string; + /** + * Optional AbortSignal to cancel the interview externally (e.g. when racing + * against a remote question channel). When aborted, the TUI closes and the + * promise resolves with an empty answers object. + */ + signal?: AbortSignal; /** * Text for the "exit" hint shown in the review screen footer and exit confirm overlay. * Defaults to "end interview". @@ -207,6 +213,13 @@ export async function showInterviewRound( let exitCursor = 0; // 0 = keep going (default), 1 = end interview let cachedLines: string[] | undefined; + // External cancellation (e.g. remote channel won the race) + if (opts.signal) { + const onAbort = () => done({ endInterview: false, answers: {} }); + if (opts.signal.aborted) { onAbort(); } + else { opts.signal.addEventListener("abort", onAbort, { once: true }); } + } + // Editor is created once; editorTheme comes from the design system const editorRef = { current: null as Editor | null };