fix(ui): stop unsupported wiki RPC probes during startup (#67905)

* UI: gate wiki method probes by advertised methods

* test(ui): cover legacy wiki method fallback
This commit is contained in:
Rubén Cuevas
2026-04-19 03:06:30 -04:00
committed by GitHub
parent 6d427f8c2a
commit a1f277e30e
2 changed files with 171 additions and 3 deletions

View File

@@ -22,6 +22,7 @@ function createState(): { state: DreamingState; request: ReturnType<typeof vi.fn
request,
} as unknown as DreamingState["client"],
connected: true,
hello: null,
configSnapshot: { hash: "hash-1" },
applySessionKey: "main",
dreamingStatusLoading: false,
@@ -227,6 +228,11 @@ describe("dreaming controller", () => {
it("loads and normalizes wiki import insights", async () => {
const { state, request } = createState();
state.hello = {
type: "hello-ok",
protocol: 3,
features: { methods: ["wiki.importInsights"] },
};
state.configSnapshot = {
hash: "hash-1",
config: {
@@ -297,6 +303,40 @@ describe("dreaming controller", () => {
expect(state.wikiImportInsightsLoading).toBe(false);
});
it("falls back to config gating for wiki import insights when methods are not advertised", async () => {
const { state, request } = createState();
state.configSnapshot = {
hash: "hash-1",
config: {
plugins: {
entries: {
"memory-wiki": {
enabled: true,
},
},
},
},
};
request.mockResolvedValue({
sourceType: "chatgpt",
totalItems: 1,
totalClusters: 1,
clusters: [],
});
await loadWikiImportInsights(state);
expect(request).toHaveBeenCalledWith("wiki.importInsights", {});
expect(state.wikiImportInsights).toEqual(
expect.objectContaining({
totalItems: 1,
totalClusters: 1,
}),
);
expect(state.wikiImportInsightsError).toBeNull();
expect(state.wikiImportInsightsLoading).toBe(false);
});
it("skips wiki import insights when memory-wiki is not enabled", async () => {
const { state, request } = createState();
state.configSnapshot = {
@@ -321,8 +361,48 @@ describe("dreaming controller", () => {
expect(state.wikiImportInsightsLoading).toBe(false);
});
it("skips wiki import insights when the gateway does not advertise the method", async () => {
const { state, request } = createState();
state.hello = {
type: "hello-ok",
protocol: 3,
features: { methods: ["doctor.memory.status"] },
};
state.configSnapshot = {
hash: "hash-1",
config: {
plugins: {
entries: {
"memory-wiki": {
enabled: true,
},
},
},
},
};
state.wikiImportInsights = {
sourceType: "chatgpt",
totalItems: 1,
totalClusters: 1,
clusters: [],
};
state.wikiImportInsightsError = "unknown method: wiki.importInsights";
await loadWikiImportInsights(state);
expect(request).not.toHaveBeenCalled();
expect(state.wikiImportInsights).toBeNull();
expect(state.wikiImportInsightsError).toBeNull();
expect(state.wikiImportInsightsLoading).toBe(false);
});
it("loads and normalizes the wiki memory palace", async () => {
const { state, request } = createState();
state.hello = {
type: "hello-ok",
protocol: 3,
features: { methods: ["wiki.palace"] },
};
state.configSnapshot = {
hash: "hash-1",
config: {
@@ -391,6 +471,41 @@ describe("dreaming controller", () => {
expect(state.wikiMemoryPalaceLoading).toBe(false);
});
it("falls back to config gating for wiki memory palace when methods are not advertised", async () => {
const { state, request } = createState();
state.configSnapshot = {
hash: "hash-1",
config: {
plugins: {
entries: {
"memory-wiki": {
enabled: true,
},
},
},
},
};
request.mockResolvedValue({
totalItems: 1,
totalClaims: 2,
totalQuestions: 0,
totalContradictions: 0,
clusters: [],
});
await loadWikiMemoryPalace(state);
expect(request).toHaveBeenCalledWith("wiki.palace", {});
expect(state.wikiMemoryPalace).toEqual(
expect.objectContaining({
totalItems: 1,
totalClaims: 2,
}),
);
expect(state.wikiMemoryPalaceError).toBeNull();
expect(state.wikiMemoryPalaceLoading).toBe(false);
});
it("skips wiki memory palace when memory-wiki is not enabled", async () => {
const { state, request } = createState();
state.configSnapshot = {
@@ -416,6 +531,42 @@ describe("dreaming controller", () => {
expect(state.wikiMemoryPalaceLoading).toBe(false);
});
it("skips wiki memory palace when the gateway does not advertise the method", async () => {
const { state, request } = createState();
state.hello = {
type: "hello-ok",
protocol: 3,
features: { methods: ["doctor.memory.status"] },
};
state.configSnapshot = {
hash: "hash-1",
config: {
plugins: {
entries: {
"memory-wiki": {
enabled: true,
},
},
},
},
};
state.wikiMemoryPalace = {
totalItems: 1,
totalClaims: 1,
totalQuestions: 0,
totalContradictions: 0,
clusters: [],
};
state.wikiMemoryPalaceError = "unknown method: wiki.palace";
await loadWikiMemoryPalace(state);
expect(request).not.toHaveBeenCalled();
expect(state.wikiMemoryPalace).toBeNull();
expect(state.wikiMemoryPalaceError).toBeNull();
expect(state.wikiMemoryPalaceLoading).toBe(false);
});
it("patches config to update global dreaming enablement", async () => {
const { state, request } = createState();
state.configSnapshot = {

View File

@@ -1,4 +1,4 @@
import type { GatewayBrowserClient } from "../gateway.ts";
import type { GatewayBrowserClient, GatewayHelloOk } from "../gateway.ts";
import { isPluginEnabledInConfigSnapshot } from "../plugin-activation.ts";
import type { ConfigSnapshot } from "../types.ts";
@@ -201,6 +201,7 @@ type WikiMemoryPalacePayload = {
export type DreamingState = {
client: GatewayBrowserClient | null;
connected: boolean;
hello: GatewayHelloOk | null;
configSnapshot: ConfigSnapshot | null;
applySessionKey: string;
dreamingStatusLoading: boolean;
@@ -236,6 +237,22 @@ function isMemoryWikiEnabled(state: DreamingState): boolean {
});
}
function hasGatewayMethod(state: DreamingState, method: string): boolean | null {
const methods = state.hello?.features?.methods;
if (!Array.isArray(methods)) {
return null;
}
return methods.includes(method);
}
function canCallMemoryWikiMethod(state: DreamingState, method: string): boolean {
const available = hasGatewayMethod(state, method);
if (available !== null) {
return available;
}
return isMemoryWikiEnabled(state);
}
function buildDreamDiaryActionSuccessMessage(
method:
| "doctor.memory.backfillDreamDiary"
@@ -748,7 +765,7 @@ export async function loadWikiImportInsights(state: DreamingState): Promise<void
if (!state.client || !state.connected || state.wikiImportInsightsLoading) {
return;
}
if (!isMemoryWikiEnabled(state)) {
if (!canCallMemoryWikiMethod(state, "wiki.importInsights")) {
state.wikiImportInsights = null;
state.wikiImportInsightsError = null;
return;
@@ -772,7 +789,7 @@ export async function loadWikiMemoryPalace(state: DreamingState): Promise<void>
if (!state.client || !state.connected || state.wikiMemoryPalaceLoading) {
return;
}
if (!isMemoryWikiEnabled(state)) {
if (!canCallMemoryWikiMethod(state, "wiki.palace")) {
state.wikiMemoryPalace = null;
state.wikiMemoryPalaceError = null;
return;