mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-28 13:26:04 -06:00
fix: preserve queued chat ownership
This commit is contained in:
@@ -683,7 +683,7 @@ describe("createSessionCapability", () => {
|
||||
{ model: "openai/gpt-new" },
|
||||
{ waitFor: priorPatch.promise },
|
||||
);
|
||||
expect(sessions.state.modelOverrides[key]).toBe("openai/gpt-new");
|
||||
expect(sessions.state.modelOverrides[key]).toBe("openai/gpt-old");
|
||||
expect(request).not.toHaveBeenCalledWith("sessions.patch", expect.anything());
|
||||
|
||||
publish(false);
|
||||
|
||||
@@ -163,20 +163,29 @@ export function createSessionMutations(host: SessionMutationsHost) {
|
||||
const hasModelPatch = Object.hasOwn(patchParams, "model");
|
||||
const managesModelOverride = hasModelPatch && options.deferModelOverride !== true;
|
||||
const normalizedKey = key.trim();
|
||||
const pendingModelPatch = pendingModelPatches.get(normalizedKey);
|
||||
const previousModelOverride = pendingModelPatch
|
||||
? pendingModelPatch.previous
|
||||
: host.readState().modelOverrides[normalizedKey];
|
||||
let previousModelOverride: string | null | undefined;
|
||||
let modelPatchStarted = false;
|
||||
const modelPatchToken = Symbol();
|
||||
if (managesModelOverride) {
|
||||
const startModelPatch = () => {
|
||||
if (!managesModelOverride || modelPatchStarted) {
|
||||
return;
|
||||
}
|
||||
const pendingModelPatch = pendingModelPatches.get(normalizedKey);
|
||||
previousModelOverride = pendingModelPatch
|
||||
? pendingModelPatch.previous
|
||||
: host.readState().modelOverrides[normalizedKey];
|
||||
modelPatchStarted = true;
|
||||
pendingModelPatches.set(normalizedKey, {
|
||||
token: modelPatchToken,
|
||||
previous: previousModelOverride,
|
||||
});
|
||||
setModelOverride(key, patchParams.model);
|
||||
};
|
||||
if (!options.waitFor) {
|
||||
startModelPatch();
|
||||
}
|
||||
const restoreModelOverride = () => {
|
||||
if (pendingModelPatches.get(normalizedKey)?.token === modelPatchToken) {
|
||||
if (modelPatchStarted && pendingModelPatches.get(normalizedKey)?.token === modelPatchToken) {
|
||||
pendingModelPatches.delete(normalizedKey);
|
||||
setModelOverride(key, previousModelOverride);
|
||||
}
|
||||
@@ -189,6 +198,7 @@ export function createSessionMutations(host: SessionMutationsHost) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
startModelPatch();
|
||||
const result = await requestSessionPatch(scope.client, key, patchParams, options);
|
||||
if (!host.connection.isCurrent(scope)) {
|
||||
restoreModelOverride();
|
||||
|
||||
@@ -579,6 +579,10 @@ export async function deliverChatQueueItem(
|
||||
const sessionKey = item.sessionKey ?? host.sessionKey;
|
||||
const storageMode = options.storageMode ?? "durable";
|
||||
const routingSessionKey = options.routingSessionKey ?? sessionKey;
|
||||
const deliveryClient = host.client;
|
||||
const deliveryConnectionEpoch = host.connectionEpoch;
|
||||
const deliveryAgentId =
|
||||
item.agentId ?? scopedAgentIdForSession(host, routingSessionKey) ?? undefined;
|
||||
const sendOptions = { ...options, routingSessionKey, storageMode };
|
||||
let result: QueuedChatSendResult;
|
||||
if (storageMode === "memory") {
|
||||
@@ -678,7 +682,12 @@ export async function deliverChatQueueItem(
|
||||
host.chatAttachments = options.previousAttachments;
|
||||
}
|
||||
}
|
||||
if (host.sessionKey === sessionKey) {
|
||||
if (
|
||||
host.client === deliveryClient &&
|
||||
host.connectionEpoch === deliveryConnectionEpoch &&
|
||||
host.sessionKey === routingSessionKey &&
|
||||
visibleSessionMatches(host, routingSessionKey, deliveryAgentId)
|
||||
) {
|
||||
scheduleChatScroll(host as unknown as Parameters<typeof scheduleChatScroll>[0], true);
|
||||
}
|
||||
if (result === "sent" && host.sessionKey === sessionKey && !host.chatRunId) {
|
||||
|
||||
@@ -1835,6 +1835,45 @@ describe("handleSendChat", () => {
|
||||
expect(patchCount).toBe(2);
|
||||
});
|
||||
|
||||
it("rolls a failed queued picker back to the preceding slash model value", async () => {
|
||||
const firstPatch = createDeferred<unknown>();
|
||||
let patchCount = 0;
|
||||
const host = makeHost({
|
||||
requestHandlers: {
|
||||
"sessions.patch": () => {
|
||||
patchCount += 1;
|
||||
if (patchCount === 1) {
|
||||
return firstPatch.promise;
|
||||
}
|
||||
throw new Error("picker rejected");
|
||||
},
|
||||
},
|
||||
});
|
||||
host.sessions.setModelOverride(host.sessionKey, "openai/gpt-old");
|
||||
|
||||
const slash = patchChatSessionSettings(
|
||||
host,
|
||||
host.sessionKey,
|
||||
{ model: "openai/gpt-5-mini" },
|
||||
{
|
||||
deferModelOverride: true,
|
||||
reconcile: () => {
|
||||
host.sessions.setModelOverride(host.sessionKey, "openai/gpt-5-mini");
|
||||
},
|
||||
},
|
||||
);
|
||||
await waitForFast(() => expect(patchCount).toBe(1));
|
||||
const picker = patchChatSessionSettings(host, host.sessionKey, {
|
||||
model: "openai/gpt-5",
|
||||
});
|
||||
|
||||
expect(host.sessions.state.modelOverrides[host.sessionKey]).toBe("openai/gpt-old");
|
||||
firstPatch.resolve(createResolvedModelPatch("gpt-5-mini", "openai"));
|
||||
await expect(slash).resolves.toBeTruthy();
|
||||
await expect(picker).rejects.toThrow("picker rejected");
|
||||
expect(host.sessions.state.modelOverrides[host.sessionKey]).toBe("openai/gpt-5-mini");
|
||||
});
|
||||
|
||||
it("keeps waiting when a late picker barrier cannot be persisted", async () => {
|
||||
const queuedText = "do not bypass the late picker";
|
||||
const history = createDeferred<unknown>();
|
||||
@@ -7513,6 +7552,7 @@ describe("handleSendChat", () => {
|
||||
limit: 100,
|
||||
}),
|
||||
);
|
||||
const scrollGeneration = host.chatScrollGeneration;
|
||||
|
||||
if (change === "route") {
|
||||
host.sessionKey = "agent:main:replacement";
|
||||
@@ -7527,6 +7567,7 @@ describe("handleSendChat", () => {
|
||||
|
||||
expect(host.lastError).toBe("Replacement session error");
|
||||
expect(host.chatError).toBe("Replacement session error");
|
||||
expect(host.chatScrollGeneration).toBe(scrollGeneration);
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user