mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-27 21:07:01 -06:00
fix: scope delayed clear feedback
This commit is contained in:
@@ -1109,6 +1109,13 @@ type ClearChatHistoryState = ChatState &
|
||||
|
||||
type ClearChatHistoryResult = "completed" | "failed" | "uncertain";
|
||||
|
||||
type ClearChatViewOwner = {
|
||||
client: ClearChatHistoryState["client"];
|
||||
connectionEpoch: number;
|
||||
sessionKey: string;
|
||||
agentId?: string;
|
||||
};
|
||||
|
||||
type RewindChatHistoryState = ChatState &
|
||||
Parameters<typeof scheduleChatScroll>[0] & {
|
||||
handleChatDraftChange: (next: string) => void;
|
||||
@@ -1142,6 +1149,14 @@ function clearCachedChatMessagesForSession(
|
||||
clearChatMessagesFromCache(state.chatMessagesBySession, state, { sessionKey, agentId });
|
||||
}
|
||||
|
||||
function ownsClearChatView(state: ClearChatHistoryState, owner: ClearChatViewOwner): boolean {
|
||||
return (
|
||||
state.client === owner.client &&
|
||||
state.connectionEpoch === owner.connectionEpoch &&
|
||||
visibleSessionMatches(state, owner.sessionKey, owner.agentId)
|
||||
);
|
||||
}
|
||||
|
||||
function clearPostResetBranchPrecondition(
|
||||
state: ClearChatHistoryState,
|
||||
target: {
|
||||
@@ -1178,6 +1193,12 @@ export async function clearChatHistory(
|
||||
const connectionEpoch = state.connectionEpoch;
|
||||
const sessionKey = state.sessionKey;
|
||||
const agentParams = scopedAgentParamsForSession(state, sessionKey);
|
||||
const originalViewOwner: ClearChatViewOwner = {
|
||||
client,
|
||||
connectionEpoch,
|
||||
sessionKey,
|
||||
agentId: agentParams.agentId,
|
||||
};
|
||||
const runId = state.chatRunId;
|
||||
const hadActiveRun = hasAbortableChatSessionRun(state);
|
||||
try {
|
||||
@@ -1197,6 +1218,12 @@ export async function clearChatHistory(
|
||||
state.connectionEpoch !== connectionEpoch ||
|
||||
!state.connected
|
||||
) {
|
||||
const feedbackOwner: ClearChatViewOwner = {
|
||||
client: state.client,
|
||||
connectionEpoch: state.connectionEpoch,
|
||||
sessionKey,
|
||||
agentId: agentParams.agentId,
|
||||
};
|
||||
let historyRefreshed = false;
|
||||
if (
|
||||
state.client &&
|
||||
@@ -1215,20 +1242,24 @@ export async function clearChatHistory(
|
||||
history,
|
||||
);
|
||||
}
|
||||
setChatError(
|
||||
state,
|
||||
historyRefreshed
|
||||
? "The clear request may have completed. Current history was refreshed; review it before resuming queued messages."
|
||||
: "The clear request may have completed. Cached history was cleared, but current history could not be refreshed; reconnect and review it before resuming queued messages.",
|
||||
);
|
||||
scheduleChatScroll(state);
|
||||
if (ownsClearChatView(state, feedbackOwner)) {
|
||||
setChatError(
|
||||
state,
|
||||
historyRefreshed
|
||||
? "The clear request may have completed. Current history was refreshed; review it before resuming queued messages."
|
||||
: "The clear request may have completed. Cached history was cleared, but current history could not be refreshed; reconnect and review it before resuming queued messages.",
|
||||
);
|
||||
scheduleChatScroll(state);
|
||||
}
|
||||
// sessions.reset is not idempotent. Treat an uncertain completion as
|
||||
// consumed so a durable /clear row cannot erase newer history on retry.
|
||||
return "uncertain";
|
||||
}
|
||||
} catch (err) {
|
||||
setChatError(state, String(err));
|
||||
scheduleChatScroll(state);
|
||||
if (ownsClearChatView(state, originalViewOwner)) {
|
||||
setChatError(state, String(err));
|
||||
scheduleChatScroll(state);
|
||||
}
|
||||
return "failed";
|
||||
}
|
||||
if (!visibleSessionMatches(state, sessionKey, agentParams.agentId)) {
|
||||
@@ -1253,7 +1284,9 @@ export async function clearChatHistory(
|
||||
{ client, connectionEpoch, sessionKey, agentId: agentParams.agentId },
|
||||
history,
|
||||
);
|
||||
scheduleChatScroll(state);
|
||||
if (ownsClearChatView(state, originalViewOwner)) {
|
||||
scheduleChatScroll(state);
|
||||
}
|
||||
return "completed";
|
||||
}
|
||||
|
||||
|
||||
@@ -7318,7 +7318,7 @@ describe("handleSendChat", () => {
|
||||
expect(listStoredChatOutboxes(host)).toStrictEqual([]);
|
||||
});
|
||||
|
||||
it("invalidates the captured session cache when a rejected clear switches routes", async () => {
|
||||
it("invalidates the captured session cache without replacing the visible route error", async () => {
|
||||
const reset = createDeferred<unknown>();
|
||||
|
||||
const sourceSessionKey = "agent:main:source";
|
||||
@@ -7348,6 +7348,8 @@ describe("handleSendChat", () => {
|
||||
host.sessionKey = visibleSessionKey;
|
||||
syncVisibleChatQueueProjection(host);
|
||||
host.chatMessages = [{ role: "user", content: "visible history" }];
|
||||
host.lastError = "Visible session error";
|
||||
host.chatError = "Visible session error";
|
||||
reset.reject(new Error("post-commit lifecycle failed"));
|
||||
await clearing;
|
||||
|
||||
@@ -7358,8 +7360,8 @@ describe("handleSendChat", () => {
|
||||
}),
|
||||
).toEqual([{ role: "user", content: "cached visible history" }]);
|
||||
expect(host.chatMessages).toEqual([{ role: "user", content: "visible history" }]);
|
||||
expect(host.lastError).toContain("clear request may have completed");
|
||||
expect(host.lastError).toContain("could not be refreshed");
|
||||
expect(host.lastError).toBe("Visible session error");
|
||||
expect(host.chatError).toBe("Visible session error");
|
||||
expect(listStoredChatOutboxes(host)).toStrictEqual([]);
|
||||
});
|
||||
|
||||
@@ -7412,6 +7414,55 @@ describe("handleSendChat", () => {
|
||||
).toHaveLength(0);
|
||||
});
|
||||
|
||||
it.each(["route", "connection"] as const)(
|
||||
"does not apply uncertain clear feedback after a %s change during history refresh",
|
||||
async (change) => {
|
||||
const reset = createDeferred<unknown>();
|
||||
const history = createDeferred<unknown>();
|
||||
const sourceSessionKey = "agent:main:source";
|
||||
const replacementRequest = makeRequestMock({
|
||||
"chat.history": () => history.promise,
|
||||
});
|
||||
const host = makeHost({
|
||||
requestHandlers: {
|
||||
"sessions.reset": () => reset.promise,
|
||||
},
|
||||
connectionEpoch: 1,
|
||||
chatMessage: "/clear",
|
||||
chatMessages: [{ role: "user", content: "source history" }],
|
||||
sessionKey: sourceSessionKey,
|
||||
});
|
||||
|
||||
const clearing = handleSendChat(host);
|
||||
await waitForFast(() =>
|
||||
expect(host.request).toHaveBeenCalledWith("sessions.reset", { key: sourceSessionKey }),
|
||||
);
|
||||
host.client = clientWithRequest(replacementRequest);
|
||||
host.connectionEpoch = 2;
|
||||
reset.resolve({ ok: true });
|
||||
await waitForFast(() =>
|
||||
expect(replacementRequest).toHaveBeenCalledWith("chat.history", {
|
||||
sessionKey: sourceSessionKey,
|
||||
limit: 100,
|
||||
}),
|
||||
);
|
||||
|
||||
if (change === "route") {
|
||||
host.sessionKey = "agent:main:replacement";
|
||||
} else {
|
||||
host.client = clientWithRequest(makeRequestMock());
|
||||
host.connectionEpoch = 3;
|
||||
}
|
||||
host.lastError = "Replacement session error";
|
||||
host.chatError = "Replacement session error";
|
||||
history.resolve({ messages: [], thinkingLevel: null });
|
||||
await clearing;
|
||||
|
||||
expect(host.lastError).toBe("Replacement session error");
|
||||
expect(host.chatError).toBe("Replacement session error");
|
||||
},
|
||||
);
|
||||
|
||||
it("clears a canonically equivalent alias that becomes visible while reset is pending", async () => {
|
||||
const reset = createDeferred<unknown>();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user