fix(infra): share diagnostic event state across loaders

This commit is contained in:
Peter Steinberger
2026-04-25 18:52:26 +01:00
parent 44114328b4
commit d623354a0e
2 changed files with 61 additions and 9 deletions

View File

@@ -147,7 +147,24 @@ describe("diagnostic-events", () => {
]);
});
it("does not expose mutable diagnostic state on a global symbol", async () => {
it("shares diagnostic state across duplicate module instances", async () => {
const events: string[] = [];
onDiagnosticEvent((event) => {
events.push(event.type);
});
vi.resetModules();
const specifier = "./diagnostic-events.js";
const duplicateModule = (await import(specifier)) as typeof import("./diagnostic-events.js");
duplicateModule.emitDiagnosticEvent({
type: "message.queued",
source: "plugin",
});
expect(events).toEqual(["message.queued"]);
});
it("does not expose mutable diagnostic state on the obsolete global symbol", async () => {
const globalStore = globalThis as Record<PropertyKey, unknown>;
const events: boolean[] = [];
globalStore[Symbol.for("openclaw.diagnosticEventsState")] = {

View File

@@ -404,14 +404,49 @@ const ASYNC_DIAGNOSTIC_EVENT_TYPES = new Set<DiagnosticEventPayload["type"]>([
"log.record",
]);
const diagnosticEventsState: DiagnosticEventsGlobalState = {
enabled: true,
seq: 0,
listeners: new Set<DiagnosticEventListener>(),
dispatchDepth: 0,
asyncQueue: [],
asyncDrainScheduled: false,
};
const DIAGNOSTIC_EVENTS_STATE_KEY = Symbol.for("openclaw.diagnosticEvents.state.v1");
function createDiagnosticEventsState(): DiagnosticEventsGlobalState {
return {
enabled: true,
seq: 0,
listeners: new Set<DiagnosticEventListener>(),
dispatchDepth: 0,
asyncQueue: [],
asyncDrainScheduled: false,
};
}
function isDiagnosticEventsState(value: unknown): value is DiagnosticEventsGlobalState {
if (!value || typeof value !== "object") {
return false;
}
const candidate = value as Partial<DiagnosticEventsGlobalState>;
return (
typeof candidate.enabled === "boolean" &&
typeof candidate.seq === "number" &&
candidate.listeners instanceof Set &&
typeof candidate.dispatchDepth === "number" &&
Array.isArray(candidate.asyncQueue) &&
typeof candidate.asyncDrainScheduled === "boolean"
);
}
const diagnosticEventsState: DiagnosticEventsGlobalState = (() => {
const globalStore = globalThis as Record<PropertyKey, unknown>;
const existing = globalStore[DIAGNOSTIC_EVENTS_STATE_KEY];
if (isDiagnosticEventsState(existing)) {
return existing;
}
const created = createDiagnosticEventsState();
Object.defineProperty(globalStore, DIAGNOSTIC_EVENTS_STATE_KEY, {
configurable: true,
enumerable: false,
value: created,
writable: false,
});
return created;
})();
function getDiagnosticEventsState(): DiagnosticEventsGlobalState {
return diagnosticEventsState;