mirror of
https://github.com/getcompanion-ai/co-mono.git
synced 2026-04-15 07:04:45 +00:00
fix(agent): Properly handle ESC interrupt in TUI with centralized event emission
Fixed the interrupt mechanism to show "[Interrupted by user]" message when ESC is pressed: - Removed duplicate UI cleanup from ESC key handler that interfered with event processing - Added centralized interrupted event emission in exception handler when abort signal is detected - Removed duplicate event emissions from API call methods to prevent multiple messages - Added abort signal support to preflight reasoning check for proper cancellation - Simplified abort detection to only check signal state, not error messages
This commit is contained in:
parent
1f9d10cab0
commit
1d9b77298c
5 changed files with 218 additions and 22 deletions
|
|
@ -178,7 +178,13 @@ async function checkReasoningSupport(
|
|||
model: string,
|
||||
api: "completions" | "responses",
|
||||
baseURL?: string,
|
||||
signal?: AbortSignal,
|
||||
): Promise<boolean> {
|
||||
// Check if already aborted
|
||||
if (signal?.aborted) {
|
||||
throw new Error("Interrupted");
|
||||
}
|
||||
|
||||
// Check cache first
|
||||
const cacheKey = model;
|
||||
const cached = modelReasoningSupport.get(cacheKey);
|
||||
|
|
@ -200,7 +206,7 @@ async function checkReasoningSupport(
|
|||
effort: "low", // Use low instead of minimal to ensure we get summaries
|
||||
},
|
||||
};
|
||||
await client.responses.create(testRequest);
|
||||
await client.responses.create(testRequest, { signal });
|
||||
supportsReasoning = true;
|
||||
} catch (error) {
|
||||
supportsReasoning = false;
|
||||
|
|
@ -234,7 +240,7 @@ async function checkReasoningSupport(
|
|||
testRequest.reasoning_effort = "minimal";
|
||||
}
|
||||
|
||||
await client.chat.completions.create(testRequest);
|
||||
await client.chat.completions.create(testRequest, { signal });
|
||||
supportsReasoning = true;
|
||||
} catch (error) {
|
||||
supportsReasoning = false;
|
||||
|
|
@ -263,7 +269,6 @@ export async function callModelResponsesApi(
|
|||
while (!conversationDone) {
|
||||
// Check if we've been interrupted
|
||||
if (signal?.aborted) {
|
||||
await eventReceiver?.on({ type: "interrupted" });
|
||||
throw new Error("Interrupted");
|
||||
}
|
||||
|
||||
|
|
@ -340,7 +345,6 @@ export async function callModelResponsesApi(
|
|||
|
||||
case "function_call": {
|
||||
if (signal?.aborted) {
|
||||
await eventReceiver?.on({ type: "interrupted" });
|
||||
throw new Error("Interrupted");
|
||||
}
|
||||
|
||||
|
|
@ -406,7 +410,6 @@ export async function callModelChatCompletionsApi(
|
|||
|
||||
while (!assistantResponded) {
|
||||
if (signal?.aborted) {
|
||||
await eventReceiver?.on({ type: "interrupted" });
|
||||
throw new Error("Interrupted");
|
||||
}
|
||||
|
||||
|
|
@ -456,7 +459,6 @@ export async function callModelChatCompletionsApi(
|
|||
for (const toolCall of message.tool_calls) {
|
||||
// Check if interrupted before executing tool
|
||||
if (signal?.aborted) {
|
||||
await eventReceiver?.on({ type: "interrupted" });
|
||||
throw new Error("Interrupted");
|
||||
}
|
||||
|
||||
|
|
@ -576,6 +578,7 @@ export class Agent {
|
|||
this.config.model,
|
||||
this.config.api,
|
||||
this.config.baseURL,
|
||||
this.abortController.signal,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -601,9 +604,10 @@ export class Agent {
|
|||
);
|
||||
}
|
||||
} catch (e) {
|
||||
// Check if this was an interruption
|
||||
const errorMessage = e instanceof Error ? e.message : String(e);
|
||||
if (errorMessage === "Interrupted" || this.abortController.signal.aborted) {
|
||||
// Check if this was an interruption by checking the abort signal
|
||||
if (this.abortController.signal.aborted) {
|
||||
// Emit interrupted event so UI can clean up properly
|
||||
await this.comboReceiver?.on({ type: "interrupted" });
|
||||
return;
|
||||
}
|
||||
throw e;
|
||||
|
|
|
|||
|
|
@ -113,19 +113,8 @@ export class TuiRenderer implements AgentEventReceiver {
|
|||
this.onInterruptCallback();
|
||||
}
|
||||
|
||||
// Stop the loading animation immediately
|
||||
if (this.currentLoadingAnimation) {
|
||||
this.currentLoadingAnimation.stop();
|
||||
this.statusContainer.clear();
|
||||
this.currentLoadingAnimation = null;
|
||||
}
|
||||
|
||||
// Don't show message here - the interrupted event will handle it
|
||||
|
||||
// Re-enable editor submission
|
||||
this.editor.disableSubmit = false;
|
||||
|
||||
this.ui.requestRender();
|
||||
// Don't do any UI cleanup here - let the interrupted event handle it
|
||||
// This avoids race conditions and ensures the interrupted message is shown
|
||||
|
||||
// Don't forward to editor
|
||||
return false;
|
||||
|
|
@ -280,6 +269,8 @@ export class TuiRenderer implements AgentEventReceiver {
|
|||
this.chatContainer.addChild(new TextComponent(chalk.red("[Interrupted by user]"), { bottom: 1 }));
|
||||
// Re-enable editor submission
|
||||
this.editor.disableSubmit = false;
|
||||
// Explicitly request render to ensure message is displayed
|
||||
this.ui.requestRender();
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue