






















@@ -3,7 +3,20 @@
33 */
44import fs from "node:fs/promises";
55import path from "node:path";
6-import { expect, test, vi } from "vitest";
6+import {
7+createPluginRegistryFixture,
8+registerTestPlugin,
9+} from "openclaw/plugin-sdk/plugin-test-contracts";
10+import { afterEach, expect, test, vi } from "vitest";
11+import { loadSessionStore } from "../config/sessions.js";
12+import { createEmptyPluginRegistry } from "../plugins/registry-empty.js";
13+import {
14+pinActivePluginSessionExtensionRegistry,
15+releasePinnedPluginSessionExtensionRegistry,
16+setActivePluginRegistry,
17+} from "../plugins/runtime.js";
18+import { createPluginRecord } from "../plugins/status.test-helpers.js";
19+import { buildGatewaySessionRow } from "./session-utils.js";
720import { embeddedRunMock, rpcReq, testState, writeSessionStore } from "./test-helpers.js";
821import {
922setupGatewaySessionsTestHarness,
@@ -20,6 +33,11 @@ const {
2033 resetConfiguredGlobalAgentSessionStore,
2134} = setupGatewaySessionsTestHarness();
223536+afterEach(() => {
37+releasePinnedPluginSessionExtensionRegistry();
38+setActivePluginRegistry(createEmptyPluginRegistry());
39+});
40+2341type MockCalls = {
2442mock: { calls: unknown[][] };
2543};
@@ -186,6 +204,72 @@ function expectMainPatchBroadcast(
186204});
187205}
188206207+test("sessions.pluginPatch over WebSocket keeps pinned startup extensions after active churn", async () => {
208+const { config, registry } = createPluginRegistryFixture();
209+registerTestPlugin({
210+ registry,
211+ config,
212+record: createPluginRecord({
213+id: "session-pin-ws-fixture",
214+name: "Session Pin WS Fixture",
215+}),
216+register(api) {
217+api.registerSessionExtension({
218+namespace: "workflow",
219+description: "Pinned workflow state",
220+});
221+},
222+});
223+setActivePluginRegistry(registry.registry);
224+pinActivePluginSessionExtensionRegistry(registry.registry);
225+setActivePluginRegistry(createEmptyPluginRegistry());
226+227+const { storePath } = await createSessionStoreDir();
228+await writeSessionStore({
229+entries: {
230+main: sessionStoreEntry("sess-main"),
231+},
232+});
233+234+const { ws } = await openClient();
235+const patched = await rpcReq<{ ok: boolean; key: string; value: { state: string } }>(
236+ws,
237+"sessions.pluginPatch",
238+{
239+key: "main",
240+pluginId: "session-pin-ws-fixture",
241+namespace: "workflow",
242+value: { state: "after-active-registry-churn" },
243+},
244+);
245+ws.close();
246+247+expect(patched.ok).toBe(true);
248+expect(patched.payload).toEqual({
249+ok: true,
250+key: "agent:main:main",
251+value: { state: "after-active-registry-churn" },
252+});
253+254+const store = loadSessionStore(storePath);
255+const entry = store.main ?? store["agent:main:main"];
256+expect(entry).toBeDefined();
257+const row = buildGatewaySessionRow({
258+cfg: { session: { store: storePath } },
259+ storePath,
260+ store,
261+key: "agent:main:main",
262+ entry,
263+});
264+expect(row.pluginExtensions).toEqual([
265+{
266+pluginId: "session-pin-ws-fixture",
267+namespace: "workflow",
268+value: { state: "after-active-registry-churn" },
269+},
270+]);
271+});
272+189273async function invokeSessionsCompact({
190274 getRuntimeConfig,
191275 params,
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。