fix: harden resume and SSE replay
This commit is contained in:
@@ -8,6 +8,29 @@ test("ui_request sets pendingWidget", () => {
|
||||
expect(useSessionStore.getState().pendingWidget?.id).toBe("u1");
|
||||
});
|
||||
|
||||
test("the same gate descriptor id is handled once even after pending is cleared", () => {
|
||||
const store = useSessionStore.getState();
|
||||
const gate = {
|
||||
type: "ui_request" as const,
|
||||
ui_request: { id: "gate-1", widget: "select", title: "Review tables" },
|
||||
};
|
||||
|
||||
store.applyEvent(gate);
|
||||
store.applyEvent({
|
||||
type: "ui_request",
|
||||
ui_request: { ...gate.ui_request, title: "Duplicate transport copy" },
|
||||
});
|
||||
store.clearPending();
|
||||
store.applyEvent(gate);
|
||||
|
||||
const state = useSessionStore.getState();
|
||||
expect(state.pendingWidget).toBeNull();
|
||||
expect(state.activityLog.filter((entry) => entry.kind === "gate")).toEqual([
|
||||
{ kind: "gate", phase: null, text: "Review tables" },
|
||||
]);
|
||||
expect(state.seenGateIds).toEqual(new Set(["gate-1"]));
|
||||
});
|
||||
|
||||
test("text_delta accumulates into transcript", () => {
|
||||
const s = useSessionStore.getState();
|
||||
s.applyEvent({ type: "text_delta", text: "Ana" });
|
||||
|
||||
@@ -16,6 +16,7 @@ interface SessionState {
|
||||
lastSystemEvent: StreamEvent | null;
|
||||
currentPhase: string | null;
|
||||
phaseError: string | null;
|
||||
seenGateIds: Set<string>;
|
||||
// True while a Pi turn is in flight. Turned off by the backend-forwarded `agent_end`
|
||||
// system event — the only end-of-turn signal on the final workflow step, where no
|
||||
// follow-up gate arrives to release the spinner.
|
||||
@@ -62,6 +63,7 @@ const empty = {
|
||||
lastSystemEvent: null,
|
||||
currentPhase: null as string | null,
|
||||
phaseError: null as string | null,
|
||||
seenGateIds: new Set<string>(),
|
||||
agentActive: false,
|
||||
};
|
||||
|
||||
@@ -70,9 +72,11 @@ export const useSessionStore = create<SessionState>((set) => ({
|
||||
applyEvent: (e) =>
|
||||
set((st) => {
|
||||
if (e.type === "ui_request") {
|
||||
if (st.seenGateIds.has(e.ui_request.id)) return {};
|
||||
const currentPhase = phaseOf(e.ui_request.phase, st.currentPhase);
|
||||
return {
|
||||
pendingWidget: e.ui_request,
|
||||
seenGateIds: new Set([...st.seenGateIds, e.ui_request.id]),
|
||||
// The turn is blocked on the gate, not ended: keep the agent marked active.
|
||||
agentActive: true,
|
||||
currentPhase,
|
||||
|
||||
Reference in New Issue
Block a user