fix(diffs): refresh live tool config

This commit is contained in:
Vincent Koc
2026-04-22 16:13:32 -07:00
parent 1019b663ce
commit 44965bf63c
4 changed files with 258 additions and 15 deletions

View File

@@ -192,6 +192,138 @@ describe("PlaywrightDiffScreenshotter", () => {
});
describe("diffs plugin registration", () => {
it("uses live runtime tool config through the registered tool factory", async () => {
type RegisteredTool = {
execute?: (toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;
};
type HttpRouteHandler = (
req: IncomingMessage,
res: ServerResponse,
) => boolean | Promise<boolean>;
type RegisteredHttpRouteParams = Parameters<OpenClawPluginApi["registerHttpRoute"]>[0];
let registeredToolFactory:
| ((ctx: OpenClawPluginToolContext) => RegisteredTool | RegisteredTool[] | null | undefined)
| undefined;
let registeredHttpRouteHandler: HttpRouteHandler | undefined;
let configFile: OpenClawConfig = {
gateway: {
port: 18789,
bind: "loopback",
},
plugins: {
entries: {
diffs: {
config: {
viewerBaseUrl: "https://startup.example.com/openclaw",
defaults: {
mode: "view",
theme: "light",
background: false,
layout: "split",
showLineNumbers: false,
diffIndicators: "classic",
lineSpacing: 2,
},
},
},
},
},
} as OpenClawConfig;
const api = createTestPluginApi({
id: "diffs",
name: "Diffs",
description: "Diffs",
source: "test",
config: {
gateway: {
port: 18789,
bind: "loopback",
},
},
pluginConfig: {
viewerBaseUrl: "https://startup.example.com/openclaw",
defaults: {
mode: "view",
theme: "light",
background: false,
layout: "split",
showLineNumbers: false,
diffIndicators: "classic",
lineSpacing: 2,
},
},
runtime: {
config: {
loadConfig: () => configFile,
},
} as never,
registerTool(tool: Parameters<OpenClawPluginApi["registerTool"]>[0]) {
registeredToolFactory = typeof tool === "function" ? tool : () => tool;
},
registerHttpRoute(params: RegisteredHttpRouteParams) {
registeredHttpRouteHandler = params.handler as HttpRouteHandler;
},
on: vi.fn(),
});
registerDiffsPlugin(api as unknown as OpenClawPluginApi);
configFile = {
...configFile,
plugins: {
entries: {
diffs: {
config: {
viewerBaseUrl: "https://live.example.com/gateway",
defaults: {
mode: "view",
theme: "dark",
background: true,
layout: "unified",
showLineNumbers: true,
diffIndicators: "bars",
lineSpacing: 1.6,
},
},
},
},
},
} as OpenClawConfig;
const registeredTool = registeredToolFactory?.({
agentId: "main",
sessionId: "session-456",
messageChannel: "discord",
agentAccountId: "default",
}) as RegisteredTool | undefined;
const result = await registeredTool?.execute?.("tool-1", {
before: "one\n",
after: "two\n",
});
const details = (result as { details?: Record<string, unknown> } | undefined)?.details;
const viewerPath = String(details?.viewerPath);
const res = createMockServerResponse();
const handled = await registeredHttpRouteHandler?.(
localReq({
method: "GET",
url: viewerPath,
}),
res,
);
expect(handled).toBe(true);
expect(String(details?.viewerUrl)).toContain("https://live.example.com/gateway");
expect(res.statusCode).toBe(200);
expect(String(res.body)).toContain('body data-theme="dark"');
expect(String(res.body)).toContain('"backgroundEnabled":true');
expect(String(res.body)).toContain('"diffStyle":"unified"');
expect(String(res.body)).toContain('"disableLineNumbers":false');
expect(String(res.body)).toContain('"diffIndicators":"bars"');
expect(String(res.body)).toContain("--diffs-line-height: 24px;");
});
it("uses live runtime viewer-access config through the registered HTTP handler", async () => {
type RegisteredTool = {
execute?: (toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;
@@ -299,12 +431,6 @@ describe("diffs plugin registration", () => {
expect(handled).toBe(true);
expect(res.statusCode).toBe(200);
expect(String(res.body)).toContain('body data-theme="light"');
expect(String(res.body)).toContain('"backgroundEnabled":false');
expect(String(res.body)).toContain('"diffStyle":"split"');
expect(String(res.body)).toContain('"disableLineNumbers":true');
expect(String(res.body)).toContain('"diffIndicators":"classic"');
expect(String(res.body)).toContain("--diffs-line-height: 30px;");
expect((result as { details?: Record<string, unknown> } | undefined)?.details?.context).toEqual(
{
agentId: "main",
@@ -344,6 +470,107 @@ describe("diffs plugin registration", () => {
expect(proxiedHandled).toBe(true);
expect(proxiedRes.statusCode).toBe(404);
});
it("fails closed for remote viewer access when the live diffs plugin entry is removed", async () => {
type RegisteredTool = {
execute?: (toolCallId: string, params: Record<string, unknown>) => Promise<unknown>;
};
type HttpRouteHandler = (
req: IncomingMessage,
res: ServerResponse,
) => boolean | Promise<boolean>;
type RegisteredHttpRouteParams = Parameters<OpenClawPluginApi["registerHttpRoute"]>[0];
let registeredToolFactory:
| ((ctx: OpenClawPluginToolContext) => RegisteredTool | RegisteredTool[] | null | undefined)
| undefined;
let registeredHttpRouteHandler: HttpRouteHandler | undefined;
let configFile: OpenClawConfig = {
gateway: {
port: 18789,
bind: "loopback",
},
plugins: {
entries: {
diffs: {
config: {
security: {
allowRemoteViewer: true,
},
},
},
},
},
} as OpenClawConfig;
const api = createTestPluginApi({
id: "diffs",
name: "Diffs",
description: "Diffs",
source: "test",
config: {
gateway: {
port: 18789,
bind: "loopback",
},
},
pluginConfig: {
security: {
allowRemoteViewer: true,
},
},
runtime: {
config: {
loadConfig: () => configFile,
},
} as never,
registerTool(tool: Parameters<OpenClawPluginApi["registerTool"]>[0]) {
registeredToolFactory = typeof tool === "function" ? tool : () => tool;
},
registerHttpRoute(params: RegisteredHttpRouteParams) {
registeredHttpRouteHandler = params.handler as HttpRouteHandler;
},
on: vi.fn(),
});
registerDiffsPlugin(api as unknown as OpenClawPluginApi);
const registeredTool = registeredToolFactory?.({
agentId: "main",
sessionId: "session-789",
messageChannel: "discord",
agentAccountId: "default",
}) as RegisteredTool | undefined;
const result = await registeredTool?.execute?.("tool-1", {
before: "one\n",
after: "two\n",
});
const viewerPath = String(
(result as { details?: Record<string, unknown> } | undefined)?.details?.viewerPath,
);
configFile = {
...configFile,
plugins: {
entries: {},
},
} as OpenClawConfig;
const proxiedRes = createMockServerResponse();
const proxiedHandled = await registeredHttpRouteHandler?.(
localReq({
method: "GET",
url: viewerPath,
headers: {
"x-forwarded-for": "203.0.113.10",
},
}),
proxiedRes,
);
expect(proxiedHandled).toBe(true);
expect(proxiedRes.statusCode).toBe(404);
});
});
function createConfig(): OpenClawConfig {

View File

@@ -1,5 +1,5 @@
import path from "node:path";
import { resolvePluginConfigObject } from "openclaw/plugin-sdk/config-runtime";
import { resolveLivePluginConfigObject } from "openclaw/plugin-sdk/config-runtime";
import { resolvePreferredOpenClawTmpDir, type OpenClawPluginApi } from "../api.js";
import {
resolveDiffsPluginDefaults,
@@ -12,24 +12,38 @@ import { DiffArtifactStore } from "./store.js";
import { createDiffsTool } from "./tool.js";
export function registerDiffsPlugin(api: OpenClawPluginApi): void {
const defaults = resolveDiffsPluginDefaults(api.pluginConfig);
const viewerBaseUrl = resolveDiffsPluginViewerBaseUrl(api.pluginConfig);
const store = new DiffArtifactStore({
rootDir: path.join(resolvePreferredOpenClawTmpDir(), "openclaw-diffs"),
logger: api.logger,
});
const resolveCurrentPluginConfig = () =>
resolveLivePluginConfigObject(
api.runtime.config?.loadConfig,
"diffs",
api.pluginConfig as Record<string, unknown>,
) ?? {};
const resolveCurrentAccessConfig = () => {
const currentConfig = api.runtime.config?.loadConfig?.() ?? api.config;
const pluginConfig = resolvePluginConfigObject(currentConfig, "diffs") ?? api.pluginConfig;
const pluginConfig = resolveCurrentPluginConfig();
return {
allowRemoteViewer: resolveDiffsPluginSecurity(pluginConfig).allowRemoteViewer,
trustedProxies: currentConfig.gateway?.trustedProxies,
allowRealIpFallback: currentConfig.gateway?.allowRealIpFallback === true,
};
};
const initialAccessConfig = resolveCurrentAccessConfig();
api.registerTool(
(ctx) => createDiffsTool({ api, store, defaults, viewerBaseUrl, context: ctx }),
(ctx) => {
const pluginConfig = resolveCurrentPluginConfig();
return createDiffsTool({
api,
store,
defaults: resolveDiffsPluginDefaults(pluginConfig),
viewerBaseUrl: resolveDiffsPluginViewerBaseUrl(pluginConfig),
context: ctx,
});
},
{
name: "diffs",
},
@@ -41,9 +55,9 @@ export function registerDiffsPlugin(api: OpenClawPluginApi): void {
handler: createDiffsHttpHandler({
store,
logger: api.logger,
allowRemoteViewer: resolveDiffsPluginSecurity(api.pluginConfig).allowRemoteViewer,
trustedProxies: api.config.gateway?.trustedProxies,
allowRealIpFallback: api.config.gateway?.allowRealIpFallback === true,
allowRemoteViewer: initialAccessConfig.allowRemoteViewer,
trustedProxies: initialAccessConfig.trustedProxies,
allowRealIpFallback: initialAccessConfig.allowRealIpFallback,
resolveAccessConfig: resolveCurrentAccessConfig,
}),
});