Revert the timeline optimistic and pagination rework (#2596)

* Add npm release track invariant and beta dist-tag procedure

* Revert "Keep older chat history and image previews stable (#2490)"

The timeline optimistic/pagination rework needs more work than main releases can wait for. Reverted together with #2484; both are reapplied on integration/timeline-optimistic-rework.

* Revert "Stop completed turns from appearing stuck (#2484)"

See the #2490 revert. #2490 built on this submission-authority change, so the two revert and reapply as a pair.
This commit is contained in:
Mohamed Boudra
2026-07-29 13:35:43 +02:00
committed by GitHub
parent ab24070075
commit acb8506d1f
81 changed files with 1721 additions and 7893 deletions
@@ -1,123 +0,0 @@
import { describe, expect, it } from "vitest";
import {
acceptMessageSubmission,
beginMessageSubmission,
getActiveMessageSubmissions,
getSendingClientMessageIds,
observeAcceptedMessageSubmissionsRunning,
observeMessageSubmissionCanonical,
rejectMessageSubmission,
} from "./model";
const submittedAt = new Date("2026-07-26T10:00:00.000Z");
describe("message submission transactions", () => {
it("tracks every in-flight submission independently", () => {
const first = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
const both = beginMessageSubmission(first, {
clientMessageId: "client-2",
submittedAt: new Date(submittedAt.getTime() + 1),
});
expect(getActiveMessageSubmissions(both).map((item) => item.clientMessageId)).toEqual([
"client-1",
"client-2",
]);
expect(getSendingClientMessageIds(both)).toEqual(["client-1", "client-2"]);
});
it("removes only the RPC-accepted transaction", () => {
const both = beginMessageSubmission(
beginMessageSubmission([], { clientMessageId: "client-1", submittedAt }),
{ clientMessageId: "client-2", submittedAt },
);
expect(acceptMessageSubmission(both, "client-1", true, false)).toEqual([
{
clientMessageId: "client-2",
submittedAt,
rpcAccepted: false,
providerAcknowledged: false,
},
]);
});
it("bridges an accepted RPC until the correlated running state is observed", () => {
const sending = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
const accepted = acceptMessageSubmission(sending, "client-1", false, false);
expect(getActiveMessageSubmissions(accepted)).toHaveLength(1);
expect(accepted[0].rpcAccepted).toBe(true);
expect(observeAcceptedMessageSubmissionsRunning(accepted)).toEqual([]);
});
it("settles an accepted RPC when provider acknowledgement arrives after running was missed", () => {
const sending = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
const accepted = acceptMessageSubmission(sending, "client-1", false, false);
expect(observeMessageSubmissionCanonical(accepted, ["client-1"])).toEqual([]);
});
it("settles an explicitly out-of-band acceptance without lifecycle inference", () => {
const sending = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
expect(acceptMessageSubmission(sending, "client-1", false, true)).toEqual([]);
});
it("settles an idle acceptance from a daemon without submission disposition", () => {
const sending = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
expect(acceptMessageSubmission(sending, "client-1", false, undefined)).toEqual([]);
});
it("records provider acknowledgement without settling another transaction", () => {
const both = beginMessageSubmission(
beginMessageSubmission([], { clientMessageId: "client-1", submittedAt }),
{ clientMessageId: "client-2", submittedAt },
);
const observed = observeMessageSubmissionCanonical(both, ["client-1"]);
expect(observed).toEqual([
{
clientMessageId: "client-1",
submittedAt,
rpcAccepted: false,
providerAcknowledged: true,
},
{
clientMessageId: "client-2",
submittedAt,
rpcAccepted: false,
providerAcknowledged: false,
},
]);
expect(getSendingClientMessageIds(observed)).toEqual(["client-2"]);
});
it("does not roll back a provider-acknowledged prompt on a later transport error", () => {
const sending = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
const observed = observeMessageSubmissionCanonical(sending, ["client-1"]);
expect(rejectMessageSubmission(observed, "client-1")).toEqual({
outcome: "accepted",
submissions: [],
});
});
it("rejects an unacknowledged transaction", () => {
const sending = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
expect(rejectMessageSubmission(sending, "client-1")).toEqual({
outcome: "rejected",
submissions: [],
});
});
it("does not create duplicate transaction identity", () => {
const sending = beginMessageSubmission([], { clientMessageId: "client-1", submittedAt });
expect(() =>
beginMessageSubmission(sending, { clientMessageId: "client-1", submittedAt }),
).toThrow("Message submission already exists");
});
});
@@ -1,108 +0,0 @@
export interface PendingMessageSubmission {
clientMessageId: string;
submittedAt: Date;
}
export type MessageSubmissionRecord = PendingMessageSubmission & {
rpcAccepted: boolean;
providerAcknowledged: boolean;
};
const EMPTY_MESSAGE_SUBMISSIONS: readonly MessageSubmissionRecord[] = [];
export function getActiveMessageSubmissions(
submissions: readonly MessageSubmissionRecord[] | null | undefined,
): readonly PendingMessageSubmission[] {
return submissions ?? EMPTY_MESSAGE_SUBMISSIONS;
}
export function getSendingClientMessageIds(
submissions: readonly MessageSubmissionRecord[] | null | undefined,
): string[] {
return (submissions ?? [])
.filter((submission) => !submission.providerAcknowledged)
.map((submission) => submission.clientMessageId);
}
export type MessageSubmissionRejectionOutcome = "rejected" | "accepted" | "unknown";
export interface MessageSubmissionRejectionResult {
submissions: MessageSubmissionRecord[];
outcome: MessageSubmissionRejectionOutcome;
}
export function beginMessageSubmission(
submissions: readonly MessageSubmissionRecord[],
input: PendingMessageSubmission,
): MessageSubmissionRecord[] {
if (submissions.some((submission) => submission.clientMessageId === input.clientMessageId)) {
throw new Error(`Message submission already exists: ${input.clientMessageId}`);
}
return [...submissions, { ...input, rpcAccepted: false, providerAcknowledged: false }];
}
export function acceptMessageSubmission(
submissions: readonly MessageSubmissionRecord[],
clientMessageId: string,
isAgentRunning: boolean,
outOfBand: boolean | undefined,
): MessageSubmissionRecord[] {
const index = submissions.findIndex(
(submission) => submission.clientMessageId === clientMessageId,
);
if (index < 0) return submissions as MessageSubmissionRecord[];
// COMPAT(messageSubmissionDisposition): daemons before v0.2.3 omitted outOfBand.
// Their normal-send response follows the ordered running/canonical events, while an
// out-of-band response arrives with the agent still idle. Remove after 2027-01-27.
const legacyOutOfBand = outOfBand === undefined && !isAgentRunning;
if (
outOfBand === true ||
legacyOutOfBand ||
isAgentRunning ||
submissions[index].providerAcknowledged
) {
return submissions.filter((_, submissionIndex) => submissionIndex !== index);
}
if (submissions[index].rpcAccepted) return submissions as MessageSubmissionRecord[];
const next = submissions.slice();
next[index] = { ...next[index], rpcAccepted: true };
return next;
}
export function observeAcceptedMessageSubmissionsRunning(
submissions: readonly MessageSubmissionRecord[],
): MessageSubmissionRecord[] {
const next = submissions.filter((submission) => !submission.rpcAccepted);
return next.length === submissions.length ? (submissions as MessageSubmissionRecord[]) : next;
}
export function observeMessageSubmissionCanonical(
submissions: readonly MessageSubmissionRecord[],
clientMessageIds: readonly string[],
): MessageSubmissionRecord[] {
if (clientMessageIds.length === 0) return submissions as MessageSubmissionRecord[];
const observed = new Set(clientMessageIds);
let changed = false;
const next = submissions.flatMap((submission): MessageSubmissionRecord[] => {
if (submission.providerAcknowledged || !observed.has(submission.clientMessageId)) {
return [submission];
}
changed = true;
return submission.rpcAccepted ? [] : [{ ...submission, providerAcknowledged: true }];
});
return changed ? next : (submissions as MessageSubmissionRecord[]);
}
export function rejectMessageSubmission(
submissions: readonly MessageSubmissionRecord[],
clientMessageId: string,
): MessageSubmissionRejectionResult {
const submission = submissions.find((item) => item.clientMessageId === clientMessageId);
if (!submission) {
return { outcome: "unknown", submissions: submissions as MessageSubmissionRecord[] };
}
return {
outcome: submission.providerAcknowledged || submission.rpcAccepted ? "accepted" : "rejected",
submissions: submissions.filter((item) => item.clientMessageId !== clientMessageId),
};
}
@@ -1,20 +0,0 @@
import type { MessageSubmissionWriter } from "@/composer/actions";
import { useSessionStore } from "@/stores/session-store";
/**
* Binds the submission lifecycle to a host session. Every path that sends a message to an
* agent — composer send, queued send-now, automatic queue drain — goes through this so a
* submitted row and its pending state are always created together.
*/
export function createMessageSubmissionWriter(serverId: string): MessageSubmissionWriter {
return {
begin: (agentId, message) =>
useSessionStore.getState().beginAgentMessageSubmission(serverId, agentId, message),
accept: (agentId, clientMessageId, outOfBand) =>
useSessionStore
.getState()
.acceptAgentMessageSubmission(serverId, agentId, clientMessageId, outOfBand),
reject: (agentId, clientMessageId) =>
useSessionStore.getState().rejectAgentMessageSubmission(serverId, agentId, clientMessageId),
};
}