fix: seed SSE history state from one snapshot (#61855) (thanks @100yenadmin)

This commit is contained in:
Peter Steinberger
2026-04-06 15:04:53 +01:00
parent d519f39c6e
commit c7c0550dc9
4 changed files with 60 additions and 1 deletions

View File

@@ -24,6 +24,7 @@ Docs: https://docs.openclaw.ai
- macOS/gateway version: strip trailing commit metadata from CLI version output before semver parsing so the Mac app recognizes installed gateway versions like `OpenClaw 2026.4.2 (d74a122)` again. (#61111) Thanks @oliviareid-svg.
- Memory/dreaming: strip managed Light Sleep and REM blocks before daily-note ingestion so dreaming summaries stop re-ingesting their own staged output into new candidates. (#61720) Thanks @MonkeyLeeT.
- Plugins/Windows: disable native Jiti loading for setup and doctor contract registries on Windows so onboarding and config-doctor plugin probes stop crashing with `ERR_UNSUPPORTED_ESM_URL_SCHEME`. (#61836, #61853)
- Gateway/history: seed SSE startup history and raw transcript sequence tracking from one initial transcript snapshot so first history events cannot diverge from subsequent message sequence numbering. (#61855) Thanks @100yenadmin.
- Agents/context overflow: combine oversized and aggregate tool-result recovery in one repair pass, and restore a total-context overflow backstop during tool loops so recoverable sessions retry instead of failing early. (#61651) Thanks @Takhoffman.
- Gateway/containers: auto-bind to `0.0.0.0` during container startup for Docker and Podman compatibility, while keeping host-side status and doctor checks on the hardened loopback default when `gateway.bind` is unset. (#61818) Thanks @openperf.
- TUI/status: route `/status` through the shared session-status command and move the old gateway-wide diagnostic summary to `/gateway-status` (`/gwstatus`). Thanks @vincentkoc.

View File

@@ -0,0 +1,56 @@
import { describe, expect, test, vi } from "vitest";
import { SessionHistorySseState } from "./session-history-state.js";
import * as sessionUtils from "./session-utils.js";
describe("SessionHistorySseState", () => {
test("uses the initial raw snapshot for both first history and seq seeding", () => {
const readSpy = vi.spyOn(sessionUtils, "readSessionMessages").mockReturnValue([
{
role: "assistant",
content: [{ type: "text", text: "stale disk message" }],
__openclaw: { seq: 1 },
},
]);
try {
const state = new SessionHistorySseState({
target: { sessionId: "sess-main" },
initialRawMessages: [
{
role: "assistant",
content: [{ type: "text", text: "fresh snapshot message" }],
__openclaw: { seq: 2 },
},
],
});
expect(state.snapshot().messages).toHaveLength(1);
expect(
(
state.snapshot().messages[0] as {
content?: Array<{ text?: string }>;
__openclaw?: { seq?: number };
}
).content?.[0]?.text,
).toBe("fresh snapshot message");
expect(
(
state.snapshot().messages[0] as {
__openclaw?: { seq?: number };
}
).__openclaw?.seq,
).toBe(2);
const appended = state.appendInlineMessage({
message: {
role: "assistant",
content: [{ type: "text", text: "next message" }],
},
});
expect(appended?.messageSeq).toBe(3);
expect(readSpy).not.toHaveBeenCalled();
} finally {
readSpy.mockRestore();
}
});
});

View File

@@ -97,12 +97,13 @@ export class SessionHistorySseState {
maxChars?: number;
limit?: number;
cursor?: string;
initialRawMessages?: unknown[];
}) {
this.target = params.target;
this.maxChars = params.maxChars ?? DEFAULT_CHAT_HISTORY_TEXT_MAX_CHARS;
this.limit = params.limit;
this.cursor = params.cursor;
const rawMessages = this.readRawMessages();
const rawMessages = params.initialRawMessages ?? this.readRawMessages();
this.sentHistory = sanitizeRawTranscriptMessages({
rawMessages,
maxChars: this.maxChars,

View File

@@ -200,6 +200,7 @@ export async function handleSessionHistoryHttpRequest(
maxChars: effectiveMaxChars,
limit,
cursor,
initialRawMessages: rawSnapshot,
});
sentHistory = sseState.snapshot();
setSseHeaders(res);