fix: patch flakey interrupt (#446)

This commit is contained in:
Charles Packer
2026-01-01 21:33:49 -08:00
committed by GitHub
parent 397560ef00
commit 4d9fea64ee
3 changed files with 26 additions and 18 deletions

View File

@@ -136,7 +136,6 @@ import {
} from "./helpers/toolNameMapping";
import {
alwaysRequiresUserInput,
isFancyUITool,
isTaskTool,
} from "./helpers/toolNameMapping.js";
import { useSuspend } from "./hooks/useSuspend/useSuspend.ts";
@@ -1162,6 +1161,8 @@ export default function App({
sessionStatsRef.current.updateUsageFromBuffers(buffersRef.current);
const wasInterrupted = !!buffersRef.current.interrupted;
const wasAborted = !!signal?.aborted;
let stopReasonToHandle = wasAborted ? "cancelled" : stopReason;
// Immediate refresh after stream completes to show final state unless
// the user already cancelled (handleInterrupt rendered the UI).
@@ -1169,8 +1170,15 @@ export default function App({
refreshDerived();
}
// If the turn was interrupted client-side but the backend had already emitted
// requires_approval, treat it as a cancel. This avoids re-entering approval flow
// and keeps queue-cancel flags consistent with the normal cancel branch below.
if (wasInterrupted && stopReasonToHandle === "requires_approval") {
stopReasonToHandle = "cancelled";
}
// Case 1: Turn ended normally
if (stopReason === "end_turn") {
if (stopReasonToHandle === "end_turn") {
setStreaming(false);
llmApiErrorRetriesRef.current = 0; // Reset retry counter on success
@@ -1208,7 +1216,7 @@ export default function App({
}
// Case 1.5: Stream was cancelled by user
if (stopReason === "cancelled") {
if (stopReasonToHandle === "cancelled") {
setStreaming(false);
// Check if this cancel was triggered by queue threshold
@@ -1244,7 +1252,7 @@ export default function App({
}
// Case 2: Requires approval
if (stopReason === "requires_approval") {
if (stopReasonToHandle === "requires_approval") {
// Clear stale state immediately to prevent ID mismatch bugs
setAutoHandledResults([]);
setAutoDeniedApprovals([]);
@@ -1658,7 +1666,10 @@ export default function App({
// Unexpected stop reason (error, llm_api_error, etc.)
// Check if this is a retriable error (transient LLM API error)
const retriable = await isRetriableError(stopReason, lastRunId);
const retriable = await isRetriableError(
stopReasonToHandle,
lastRunId,
);
if (
retriable &&
@@ -1716,8 +1727,9 @@ export default function App({
telemetry.trackError(
fallbackError
? "FallbackError"
: stopReason || "unknown_stop_reason",
fallbackError || `Stream stopped with reason: ${stopReason}`,
: stopReasonToHandle || "unknown_stop_reason",
fallbackError ||
`Stream stopped with reason: ${stopReasonToHandle}`,
"message_stream",
{
modelId: currentModelId || undefined,

View File

@@ -54,20 +54,16 @@ export const AgentInfoBar = memo(function AgentInfoBar({
</Box>
<Box>
{isCloudUser && (
<>
<Link url={`https://app.letta.com/agents/${agentId}`}>
<Text>Open in ADE </Text>
</Link>
</>
<Link url={`https://app.letta.com/agents/${agentId}`}>
<Text>Open in ADE </Text>
</Link>
)}
</Box>
<Box>
{isCloudUser && (
<>
<Link url="https://app.letta.com/settings/organization/usage">
<Text>View usage </Text>
</Link>
</>
<Link url="https://app.letta.com/settings/organization/usage">
<Text>View usage </Text>
</Link>
)}
{!isCloudUser && <Text dimColor> · {serverUrl}</Text>}
</Box>

View File

@@ -130,7 +130,7 @@ export const InlineQuestionApproval = memo(
});
}
// Always insert the space character
setCustomText((prev) => prev + " ");
setCustomText((prev) => `${prev} `);
return;
}
if (key.escape) {