Skip to content

Commit 7372b00

Browse files
committed
fix(web): scope run errors to current run
1 parent db52ad9 commit 7372b00

2 files changed

Lines changed: 106 additions & 5 deletions

File tree

web/src/utils/eventBridge.test.ts

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,6 +1022,8 @@ describe("eventBridge", () => {
10221022
expect(useUIStore.getState().toasts.at(-1)?.message).toBe(
10231023
"已达到本次运行最大轮数,可继续发送消息或调高 runtime.max_turns",
10241024
);
1025+
useSessionStore.setState({ currentSessionId: "" } as any);
1026+
useGatewayStore.setState({ currentRunId: "" } as any);
10251027
});
10261028

10271029
it("RunError with max-turn stop reason uses explicit max-turn UX instead of generic run failed", () => {
@@ -1069,6 +1071,7 @@ describe("eventBridge", () => {
10691071
it("RunError with max-turn stop reason is handled during session mismatch", () => {
10701072
const api = createMockGatewayAPI();
10711073
useSessionStore.setState({ currentSessionId: "sess-current" } as any);
1074+
useGatewayStore.setState({ currentRunId: "run-max-turn-mismatch" } as any);
10721075
useChatStore.getState().addMessage({
10731076
id: "tool-running-run-error-mismatch",
10741077
role: "tool",
@@ -1106,6 +1109,92 @@ describe("eventBridge", () => {
11061109
);
11071110
});
11081111

1112+
it("RunError during session mismatch is ignored when run id is stale", () => {
1113+
const api = createMockGatewayAPI();
1114+
useSessionStore.setState({ currentSessionId: "sess-current" } as any);
1115+
useGatewayStore.setState({ currentRunId: "run-current" } as any);
1116+
useChatStore.getState().addMessage({
1117+
id: "tool-running-stale-run-error",
1118+
role: "tool",
1119+
type: "tool_call",
1120+
content: "",
1121+
toolName: "bash",
1122+
toolCallId: "tc-stale-run-error",
1123+
toolStatus: "running",
1124+
timestamp: Date.now(),
1125+
});
1126+
useChatStore.getState().setGenerating(true);
1127+
1128+
handleGatewayEvent(
1129+
{
1130+
type: EventType.RunError,
1131+
payload: {
1132+
event_type: EventType.RunError,
1133+
payload: {
1134+
code: "max_turn_exceeded",
1135+
message: "runtime: max turn limit reached (40)",
1136+
stop_reason: "max_turn_exceeded",
1137+
},
1138+
},
1139+
session_id: "sess-stale",
1140+
run_id: "run-stale",
1141+
},
1142+
api,
1143+
);
1144+
1145+
expect(useChatStore.getState().isGenerating).toBe(true);
1146+
expect(useChatStore.getState().stopReason).toBe("");
1147+
expect(useChatStore.getState().messages[0].toolStatus).toBe("running");
1148+
expect(useUIStore.getState().toasts).toHaveLength(0);
1149+
useSessionStore.setState({ currentSessionId: "" } as any);
1150+
useGatewayStore.setState({ currentRunId: "" } as any);
1151+
});
1152+
1153+
it("RunError for current run is handled while transitioning", () => {
1154+
const api = createMockGatewayAPI();
1155+
useSessionStore.setState({ currentSessionId: "sess-current" } as any);
1156+
useGatewayStore.setState({ currentRunId: "run-transition" } as any);
1157+
useChatStore.setState({ isTransitioning: true } as any);
1158+
useChatStore.getState().addMessage({
1159+
id: "tool-running-transition-run-error",
1160+
role: "tool",
1161+
type: "tool_call",
1162+
content: "",
1163+
toolName: "bash",
1164+
toolCallId: "tc-transition-run-error",
1165+
toolStatus: "running",
1166+
timestamp: Date.now(),
1167+
});
1168+
useChatStore.getState().setGenerating(true);
1169+
1170+
handleGatewayEvent(
1171+
{
1172+
type: EventType.RunError,
1173+
payload: {
1174+
event_type: EventType.RunError,
1175+
payload: {
1176+
code: "max_turn_exceeded",
1177+
message: "runtime: max turn limit reached (40)",
1178+
stop_reason: "max_turn_exceeded",
1179+
},
1180+
},
1181+
session_id: "sess-stale",
1182+
run_id: "run-transition",
1183+
},
1184+
api,
1185+
);
1186+
1187+
expect(useChatStore.getState().isGenerating).toBe(false);
1188+
expect(useChatStore.getState().stopReason).toBe("max_turn_exceeded");
1189+
expect(useChatStore.getState().messages[0].toolStatus).toBe("error");
1190+
expect(useUIStore.getState().toasts.at(-1)?.message).toBe(
1191+
"已达到本次运行最大轮数,可继续发送消息或调高 runtime.max_turns",
1192+
);
1193+
useSessionStore.setState({ currentSessionId: "" } as any);
1194+
useGatewayStore.setState({ currentRunId: "" } as any);
1195+
useChatStore.setState({ isTransitioning: false } as any);
1196+
});
1197+
11091198
it("RunCanceled does not convert running tool calls to done", () => {
11101199
const api = createMockGatewayAPI();
11111200
useChatStore.getState().addMessage({

web/src/utils/eventBridge.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -534,10 +534,7 @@ function normalizeUserQuestionRequestedPayload(
534534
}
535535

536536
const CRITICAL_EVENTS = new Set<string>([EventType.Error, EventType.RunError]);
537-
const SESSION_AGNOSTIC_EVENTS = new Set<string>([
538-
EventType.Error,
539-
EventType.RunError,
540-
]);
537+
const SESSION_AGNOSTIC_EVENTS = new Set<string>([EventType.Error]);
541538

542539
function strField(payload: unknown, key: string): string {
543540
return ((payload as PayloadRecord)?.[key] as string) ?? "";
@@ -547,6 +544,20 @@ function getRunKey(frameRunId: string | undefined): string {
547544
return (frameRunId || useGatewayStore.getState().currentRunId || "").trim();
548545
}
549546

547+
function isCurrentRunScopedTerminalEvent(
548+
eventType: string,
549+
frameRunId: string | undefined,
550+
): boolean {
551+
if (eventType !== EventType.RunError) return false;
552+
const eventRunId = (frameRunId || "").trim();
553+
const currentRunId = useGatewayStore.getState().currentRunId.trim();
554+
return (
555+
eventRunId !== "" &&
556+
currentRunId !== "" &&
557+
eventRunId === currentRunId
558+
);
559+
}
560+
550561
function isMaxTurnExceeded(reason: string, code = ""): boolean {
551562
return (
552563
reason === StopReason.MaxTurnExceeded ||
@@ -682,7 +693,8 @@ export function handleGatewayEvent(
682693
frameSessionId &&
683694
currentSessionId &&
684695
frameSessionId !== currentSessionId &&
685-
!SESSION_AGNOSTIC_EVENTS.has(eventType)
696+
!SESSION_AGNOSTIC_EVENTS.has(eventType) &&
697+
!isCurrentRunScopedTerminalEvent(eventType, frameRunId)
686698
) {
687699
return;
688700
}

0 commit comments

Comments
 (0)