





















@@ -219,6 +219,55 @@ describe("command queue", () => {
219219expect(calls).toEqual(["first", "second"]);
220220});
221221222+it("reports queueAhead after priority insertion", async () => {
223+vi.useFakeTimers();
224+try {
225+const { task: blocker, release } = enqueueBlockedMainTask(async () => "blocker");
226+const calls: string[] = [];
227+let queuedAhead: number | null = null;
228+229+const background = enqueueCommandInLane(
230+CommandLane.Main,
231+async () => {
232+calls.push("background");
233+return "background";
234+},
235+{ priority: "background" },
236+);
237+const foreground = enqueueCommandInLane(
238+CommandLane.Main,
239+async () => {
240+calls.push("foreground");
241+return "foreground";
242+},
243+{
244+priority: "foreground",
245+warnAfterMs: 5,
246+onWait: (_ms, ahead) => {
247+queuedAhead = ahead;
248+},
249+},
250+);
251+252+await vi.advanceTimersByTimeAsync(6);
253+release();
254+await expect(blocker).resolves.toBe("blocker");
255+await expect(foreground).resolves.toBe("foreground");
256+await expect(background).resolves.toBe("background");
257+258+expect(calls).toEqual(["foreground", "background"]);
259+expect(queuedAhead).toBe(0);
260+const waitWarning = diagnosticMocks.diag.warn.mock.calls.find(
261+([message]) =>
262+typeof message === "string" && message.includes("lane wait exceeded: lane=main"),
263+);
264+expect(waitWarning?.[0]).toContain("queueAhead=0 activeAhead=1");
265+expect(waitWarning?.[0]).toContain("queueBehind=1");
266+} finally {
267+vi.useRealTimers();
268+}
269+});
270+222271it("logs enqueue depth after push", async () => {
223272const task = enqueueCommand(async () => {});
224273@@ -254,6 +303,11 @@ describe("command queue", () => {
254303expect(typeof waited).toBe("number");
255304expect(waited).toBeGreaterThanOrEqual(5);
256305expect(queuedAhead).toBe(0);
306+const waitWarning = diagnosticMocks.diag.warn.mock.calls.find(
307+([message]) =>
308+typeof message === "string" && message.includes("lane wait exceeded: lane=main"),
309+);
310+expect(waitWarning?.[0]).toContain("queueAhead=0 activeAhead=1");
257311} finally {
258312vi.useRealTimers();
259313}
@@ -729,6 +783,64 @@ describe("command queue", () => {
729783}
730784});
731785786+it("migrates legacy queued entries missing priority and wait diagnostics", async () => {
787+const key = Symbol.for("openclaw.commandQueueState");
788+const globalStore = globalThis as Record<PropertyKey, unknown>;
789+const original = globalStore[key];
790+let queuedAhead: number | null = null;
791+const legacyTask = new Promise<string>((resolve, reject) => {
792+globalStore[key] = {
793+gatewayDraining: false,
794+lanes: new Map([
795+[
796+CommandLane.Main,
797+{
798+lane: CommandLane.Main,
799+queue: [
800+{
801+task: async () => "done",
802+ resolve,
803+ reject,
804+enqueuedAt: Date.now() - 10,
805+warnAfterMs: 0,
806+onWait: (_ms: number, ahead: number) => {
807+queuedAhead = ahead;
808+},
809+},
810+],
811+activeTaskIds: new Set(),
812+maxConcurrent: 1,
813+draining: false,
814+generation: 0,
815+},
816+],
817+]),
818+activeTaskWaiters: new Set(),
819+nextTaskId: 1,
820+nextQueueSequence: 1,
821+};
822+});
823+824+try {
825+resetAllLanes();
826+827+await expect(legacyTask).resolves.toBe("done");
828+expect(queuedAhead).toBe(0);
829+const waitWarning = diagnosticMocks.diag.warn.mock.calls.find(
830+([message]) =>
831+typeof message === "string" && message.includes("lane wait exceeded: lane=main"),
832+);
833+expect(waitWarning?.[0]).toContain("queueAhead=0 activeAhead=0");
834+} finally {
835+if (original !== undefined) {
836+globalStore[key] = original;
837+} else {
838+delete globalStore[key];
839+}
840+resetCommandQueueStateForTest();
841+}
842+});
843+732844it("shares lane state across distinct module instances", async () => {
733845const commandQueueA = await importFreshModule<typeof import("./command-queue.js")>(
734846import.meta.url,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。