mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 05:16:23 -06:00
fix(ui): report connection loss instead of silently dropping session actions (#123771)
* fix(ui): report connection loss instead of silently dropping session actions patchSession returned 'stale' with no message when the gateway dropped before a queued dialog submit (rename and similar) — the dialog closed and the edit vanished with no visible outcome. loadCheckpoint had the sibling gap: expanding a row while disconnected left the drawer claiming 'No checkpoints' beside a nonzero checkpoint badge. Both now surface the existing actionRequiresConnection message at their owning error surface. * test: widen patchSession test-support patch type for label patches
This commit is contained in:
committed by
GitHub
parent
0bdf11c9dc
commit
877f2ec083
@@ -25,6 +25,7 @@ export type TestSessionsPage = HTMLElement & {
|
|||||||
sessionMenu: { key: string; x: number; y: number } | null;
|
sessionMenu: { key: string; x: number; y: number } | null;
|
||||||
sessionMenuTrigger: HTMLElement | null;
|
sessionMenuTrigger: HTMLElement | null;
|
||||||
checkpointItemsByKey: Record<string, SessionCompactionCheckpoint[]>;
|
checkpointItemsByKey: Record<string, SessionCompactionCheckpoint[]>;
|
||||||
|
checkpointErrorByKey: Record<string, string>;
|
||||||
checkpointLoadingKey: string | null;
|
checkpointLoadingKey: string | null;
|
||||||
checkpointBusyKey: string | null;
|
checkpointBusyKey: string | null;
|
||||||
sessionMutationPending: boolean;
|
sessionMutationPending: boolean;
|
||||||
@@ -47,7 +48,7 @@ export type TestSessionsPage = HTMLElement & {
|
|||||||
) => void;
|
) => void;
|
||||||
patchSession: (
|
patchSession: (
|
||||||
key: string,
|
key: string,
|
||||||
patch: { archived?: boolean; pinned?: boolean },
|
patch: { archived?: boolean; pinned?: boolean; label?: string | null },
|
||||||
scope?: unknown,
|
scope?: unknown,
|
||||||
expectedSessionId?: string,
|
expectedSessionId?: string,
|
||||||
) => Promise<unknown>;
|
) => Promise<unknown>;
|
||||||
|
|||||||
@@ -281,6 +281,35 @@ describe("sessions page lifecycle", () => {
|
|||||||
expect(page.transcriptSearch).toEqual({ status: "idle" });
|
expect(page.transcriptSearch).toEqual({ status: "idle" });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("reports a connection error instead of silently dropping a patch", async () => {
|
||||||
|
const patch = vi.fn();
|
||||||
|
const sessions = createSessions({ patch });
|
||||||
|
const mutableGateway = createGateway({} as GatewayBrowserClient);
|
||||||
|
const page = await createPage(createContext(mutableGateway.gateway, sessions));
|
||||||
|
// Gateway drops while a rename dialog is open; submit lands afterwards.
|
||||||
|
mutableGateway.emit({ phase: "reconnecting", client: null });
|
||||||
|
|
||||||
|
const result = await page.patchSession("agent:main:main", { label: "renamed" });
|
||||||
|
|
||||||
|
expect(result).toBe("failed");
|
||||||
|
expect(patch).not.toHaveBeenCalled();
|
||||||
|
expect(page.error).toBe("Connect to the Gateway to change sessions.");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows a connection error in the checkpoints drawer while disconnected", async () => {
|
||||||
|
const mutableGateway = createGateway({} as GatewayBrowserClient);
|
||||||
|
const page = await createPage(createContext(mutableGateway.gateway, createSessions()));
|
||||||
|
mutableGateway.emit({ phase: "reconnecting", client: null });
|
||||||
|
|
||||||
|
await page.loadCheckpoint("agent:main:main");
|
||||||
|
|
||||||
|
// Without the recorded error the drawer would render "No checkpoints"
|
||||||
|
// beside a nonzero checkpoint badge.
|
||||||
|
expect(page.checkpointErrorByKey["agent:main:main"]).toBe(
|
||||||
|
"Connect to the Gateway to change sessions.",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it("drops a transcript result after the query changes while it is pending", async () => {
|
it("drops a transcript result after the query changes while it is pending", async () => {
|
||||||
const response = deferred<SessionsSearchResult>();
|
const response = deferred<SessionsSearchResult>();
|
||||||
const request = vi.fn(() => response.promise);
|
const request = vi.fn(() => response.promise);
|
||||||
|
|||||||
@@ -1144,7 +1144,10 @@ class SessionsPage extends OpenClawLightDomElement {
|
|||||||
expectedSessionId?: string,
|
expectedSessionId?: string,
|
||||||
): Promise<SessionsPageMutationResult> {
|
): Promise<SessionsPageMutationResult> {
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
return "stale";
|
// Nothing was attempted (e.g. rename dialog submitted after the gateway
|
||||||
|
// dropped); say so instead of silently swallowing the edit.
|
||||||
|
this.error = t("sessionsView.actionRequiresConnection");
|
||||||
|
return "failed";
|
||||||
}
|
}
|
||||||
if (typeof patch.archived === "boolean" && !expectedSessionId?.trim()) {
|
if (typeof patch.archived === "boolean" && !expectedSessionId?.trim()) {
|
||||||
this.error = "Session lifecycle action requires a durable session identity.";
|
this.error = "Session lifecycle action requires a durable session identity.";
|
||||||
@@ -1280,6 +1283,12 @@ class SessionsPage extends OpenClawLightDomElement {
|
|||||||
private async loadCheckpoint(sessionKey: string) {
|
private async loadCheckpoint(sessionKey: string) {
|
||||||
const scope = this.captureRequestScope();
|
const scope = this.captureRequestScope();
|
||||||
if (!scope) {
|
if (!scope) {
|
||||||
|
// Rows stay expandable while disconnected; without an error the drawer
|
||||||
|
// would claim "No checkpoints" beside a nonzero checkpoint badge.
|
||||||
|
this.checkpointErrorByKey = {
|
||||||
|
...this.checkpointErrorByKey,
|
||||||
|
[sessionKey]: t("sessionsView.actionRequiresConnection"),
|
||||||
|
};
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.checkpointTaskKey = sessionKey;
|
this.checkpointTaskKey = sessionKey;
|
||||||
|
|||||||
Reference in New Issue
Block a user