mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-10 23:44:33 +00:00
fix(queue): harden drain/abort/timeout race handling
- reject new lane enqueues once gateway drain begins - always reset lane draining state and isolate onWait callback failures - persist per-session abort cutoff and skip stale queued messages - avoid false 600s agentTurn timeout in isolated cron jobs Fixes #27407 Fixes #27332 Fixes #27427 Co-authored-by: Kevin Shenghui <shenghuikevin@github.com> Co-authored-by: zjmy <zhangjunmengyang@gmail.com> Co-authored-by: suko <miha.sukic@gmail.com>
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import type { SessionEntry } from "../../config/sessions.js";
|
||||
import type { TemplateContext } from "../templating.js";
|
||||
import { clearInlineDirectives } from "./get-reply-directives-utils.js";
|
||||
import { buildTestCtx } from "./test-ctx.js";
|
||||
@@ -146,4 +147,78 @@ describe("handleInlineActions", () => {
|
||||
}),
|
||||
);
|
||||
});
|
||||
|
||||
it("skips stale queued messages that are at or before the /stop cutoff", async () => {
|
||||
const typing = createTypingController();
|
||||
const sessionEntry: SessionEntry = {
|
||||
sessionId: "session-1",
|
||||
updatedAt: Date.now(),
|
||||
abortCutoffMessageSid: "42",
|
||||
abortedLastRun: true,
|
||||
};
|
||||
const sessionStore = { "s:main": sessionEntry };
|
||||
const ctx = buildTestCtx({
|
||||
Body: "old queued message",
|
||||
CommandBody: "old queued message",
|
||||
MessageSid: "41",
|
||||
});
|
||||
|
||||
const result = await handleInlineActions(
|
||||
createHandleInlineActionsInput({
|
||||
ctx,
|
||||
typing,
|
||||
cleanedBody: "old queued message",
|
||||
command: {
|
||||
rawBodyNormalized: "old queued message",
|
||||
commandBodyNormalized: "old queued message",
|
||||
},
|
||||
overrides: {
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result).toEqual({ kind: "reply", reply: undefined });
|
||||
expect(typing.cleanup).toHaveBeenCalled();
|
||||
expect(handleCommandsMock).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it("clears /stop cutoff when a newer message arrives", async () => {
|
||||
const typing = createTypingController();
|
||||
const sessionEntry: SessionEntry = {
|
||||
sessionId: "session-2",
|
||||
updatedAt: Date.now(),
|
||||
abortCutoffMessageSid: "42",
|
||||
abortedLastRun: true,
|
||||
};
|
||||
const sessionStore = { "s:main": sessionEntry };
|
||||
handleCommandsMock.mockResolvedValue({ shouldContinue: false, reply: { text: "ok" } });
|
||||
const ctx = buildTestCtx({
|
||||
Body: "new message",
|
||||
CommandBody: "new message",
|
||||
MessageSid: "43",
|
||||
});
|
||||
|
||||
const result = await handleInlineActions(
|
||||
createHandleInlineActionsInput({
|
||||
ctx,
|
||||
typing,
|
||||
cleanedBody: "new message",
|
||||
command: {
|
||||
rawBodyNormalized: "new message",
|
||||
commandBodyNormalized: "new message",
|
||||
},
|
||||
overrides: {
|
||||
sessionEntry,
|
||||
sessionStore,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(result).toEqual({ kind: "reply", reply: { text: "ok" } });
|
||||
expect(sessionStore["s:main"]?.abortCutoffMessageSid).toBeUndefined();
|
||||
expect(sessionStore["s:main"]?.abortCutoffTimestamp).toBeUndefined();
|
||||
expect(handleCommandsMock).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user