mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-17 12:11:20 +00:00
fix: repair stale agent run context sweep
This commit is contained in:
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Gateway/tailscale: start Tailscale exposure and the gateway update check before awaiting channel and plugin sidecar startup so remote operators are not locked out when startup sidecars stall.
|
||||
- QQBot/streaming: make block streaming configurable per QQ bot account via `streaming.mode` (`"partial"` | `"off"`, default `"partial"`) instead of hardcoding it off, so responses can be delivered incrementally. (#63746)
|
||||
- Dreaming/gateway: require `operator.admin` for persistent `/dreaming on|off` changes and treat missing gateway client scopes as unprivileged instead of silently allowing config writes. (#63872) Thanks @mbelinky.
|
||||
- Gateway/agents: fix stale run-context TTL cleanup so the new maintenance sweep compiles and resets orphaned run sequence state correctly. (#52731) thanks @artwalker
|
||||
|
||||
## 2026.4.9
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { beforeEach, describe, expect, test } from "vitest";
|
||||
import { beforeEach, describe, expect, test, vi } from "vitest";
|
||||
import {
|
||||
clearAgentRunContext,
|
||||
emitAgentEvent,
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
registerAgentRunContext,
|
||||
resetAgentEventsForTest,
|
||||
resetAgentRunContextForTest,
|
||||
sweepStaleRunContexts,
|
||||
} from "./agent-events.js";
|
||||
|
||||
type AgentEventsModule = typeof import("./agent-events.js");
|
||||
@@ -194,4 +195,40 @@ describe("agent-events sequencing", () => {
|
||||
|
||||
first.resetAgentEventsForTest();
|
||||
});
|
||||
|
||||
test("sweeps stale run contexts and clears their sequence state", async () => {
|
||||
const stop = vi.spyOn(Date, "now");
|
||||
stop.mockReturnValue(100);
|
||||
registerAgentRunContext("run-stale", { sessionKey: "session-stale", registeredAt: 100 });
|
||||
registerAgentRunContext("run-active", { sessionKey: "session-active", registeredAt: 100 });
|
||||
|
||||
stop.mockReturnValue(200);
|
||||
emitAgentEvent({ runId: "run-stale", stream: "assistant", data: { text: "stale" } });
|
||||
|
||||
stop.mockReturnValue(900);
|
||||
emitAgentEvent({ runId: "run-active", stream: "assistant", data: { text: "active" } });
|
||||
|
||||
stop.mockReturnValue(1_000);
|
||||
expect(sweepStaleRunContexts(500)).toBe(1);
|
||||
expect(getAgentRunContext("run-stale")).toBeUndefined();
|
||||
expect(getAgentRunContext("run-active")).toMatchObject({ sessionKey: "session-active" });
|
||||
|
||||
const seen: Array<{ runId: string; seq: number }> = [];
|
||||
const unsubscribe = onAgentEvent((evt) => {
|
||||
if (evt.runId === "run-stale" || evt.runId === "run-active") {
|
||||
seen.push({ runId: evt.runId, seq: evt.seq });
|
||||
}
|
||||
});
|
||||
|
||||
emitAgentEvent({ runId: "run-stale", stream: "assistant", data: { text: "restarted" } });
|
||||
emitAgentEvent({ runId: "run-active", stream: "assistant", data: { text: "continued" } });
|
||||
|
||||
unsubscribe();
|
||||
stop.mockRestore();
|
||||
|
||||
expect(seen).toEqual([
|
||||
{ runId: "run-stale", seq: 1 },
|
||||
{ runId: "run-active", seq: 2 },
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -140,7 +140,10 @@ export function registerAgentRunContext(runId: string, context: AgentRunContext)
|
||||
const state = getAgentEventState();
|
||||
const existing = state.runContextById.get(runId);
|
||||
if (!existing) {
|
||||
state.runContextById.set(runId, { ...context, registeredAt: context.registeredAt ?? Date.now() });
|
||||
state.runContextById.set(runId, {
|
||||
...context,
|
||||
registeredAt: context.registeredAt ?? Date.now(),
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (context.sessionKey && existing.sessionKey !== context.sessionKey) {
|
||||
@@ -171,6 +174,7 @@ export function clearAgentRunContext(runId: string) {
|
||||
* Guards against orphaned entries when lifecycle "end"/"error" events are missed.
|
||||
*/
|
||||
export function sweepStaleRunContexts(maxAgeMs = 30 * 60 * 1000): number {
|
||||
const state = getAgentEventState();
|
||||
const now = Date.now();
|
||||
let swept = 0;
|
||||
for (const [runId, ctx] of state.runContextById.entries()) {
|
||||
|
||||
Reference in New Issue
Block a user