fix(frontend): preserve interrupted uniform-run order (#4834)

This commit is contained in:
Stellar鱼 2026-08-30 22:19:52 +08:00 committed by GitHub
parent 3820515155
commit 11e6cdd7e7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 58 additions and 1 deletions

View File

@ -675,7 +675,9 @@ export function restoreLocalTurnMessageOrder(
* accepted transient, far safer than pulling a completed turn's answer
* below the next user message. A resent turn after an interrupted run and
* pagination orphans from older turns (#4399) fail the checks as well and
* are left untouched.
* are left untouched. When an interrupted turn and the next turn share one
* synthetic run_id, the previous human anchor plus the missing terminal
* answer makes step ownership ambiguous, so that layout is also preserved.
*/
export function restoreReconnectedTurnMessageOrder(
messages: Message[],
@ -721,6 +723,21 @@ export function restoreReconnectedTurnMessageOrder(
}
}
const previousHumanRunId =
segmentStart > 0 ? getMessageRunId(messages[segmentStart - 1]!) : undefined;
const hasUniformRunIdSincePreviousHuman =
previousHumanRunId !== undefined &&
messages
.slice(segmentStart - 1)
.every(
(message) =>
isHiddenFromUIMessage(message) ||
getMessageRunId(message) === previousHumanRunId,
);
if (candidateStart === segmentStart && hasUniformRunIdSincePreviousHuman) {
return messages;
}
const runIdsAfter = new Set<string>();
let hasLiveOnlyStepAfter = false;
for (const message of messages.slice(humanIndex + 1)) {

View File

@ -1688,6 +1688,46 @@ test("reconnected turn order leaves a resent turn after an interrupted run untou
).toEqual([interruptedStep, human, newStep]);
});
test("reconnected turn order keeps interrupted uniform-run history in its original turn", () => {
// Branch-seeded and mocked feeds can stamp every message with one run_id.
// Without a terminal answer, that synthetic id cannot prove that the older
// tool step belongs to the newer human turn.
const human1 = {
id: "human-1",
type: "human",
content: "First question",
run_id: "run-x",
} as Message;
const interruptedStep = {
id: "step-1",
type: "ai",
content: "Working on the first question",
tool_calls: [{ id: "tc-1", name: "web_search", args: {} }],
run_id: "run-x",
} as unknown as Message;
const human2 = {
id: "human-2",
type: "human",
content: "Second question",
run_id: "run-x",
} as Message;
const step2 = {
id: "step-2",
type: "ai",
content: "Working on the second question",
run_id: "run-x",
} as Message;
expect(
restoreReconnectedTurnMessageOrder([
human1,
interruptedStep,
human2,
step2,
]),
).toEqual([human1, interruptedStep, human2, step2]);
});
test("reconnected turn order only moves steps of the sandwiched run in multi-turn history", () => {
const human1 = { id: "human-1", type: "human", content: "First" } as Message;
const step1 = {