mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 05:40:44 +00:00
fix(diffs): refresh live tool config
This commit is contained in:
@@ -59,6 +59,7 @@ Docs: https://docs.openclaw.ai
|
||||
- CLI sessions: persist CLI session clearing through the atomic session-store merge path, so expired Claude/Codex CLI bindings are actually removed before retrying without the stale session id. (#70298) Thanks @HFConsultant.
|
||||
- ACP/sessions_spawn: honor explicit `model` overrides for ACP child sessions instead of silently falling back to the target agent default model. (#70210) Thanks @felix-miao.
|
||||
- Diffs/viewer: re-read remote viewer access policy from live runtime config on each request, so toggling `plugins.entries.diffs.config.security.allowRemoteViewer` closes proxied viewer access immediately instead of waiting for a restart. Thanks @vincentkoc.
|
||||
- Diffs/tooling: re-read `viewerBaseUrl`, presentation defaults, and viewer access policy from live runtime config, and fail closed when the live `diffs` plugin entry disappears instead of reviving startup viewer settings. Thanks @vincentkoc.
|
||||
- Memory/LanceDB: stop resurrecting removed live `memory-lancedb` hook config from startup snapshots, so deleting or disabling the plugin entry shuts off auto-recall and auto-capture without a restart. Thanks @vincentkoc.
|
||||
- Active Memory: stop reviving removed live `active-memory` config from startup snapshots, so removing the plugin entry turns the hook off immediately instead of waiting for a restart. Thanks @vincentkoc.
|
||||
- Agents/subagents: drop bare `NO_REPLY` from the parent turn when the session still has pending spawned children, so direct-conversation surfaces such as Telegram DMs no longer rewrite the sentinel into visible fallback chatter while waiting for the child completion event. (#69942) Thanks @neeravmakwana.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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,
|
||||
}),
|
||||
});
|
||||
|
||||
@@ -49,7 +49,8 @@ const BUNDLED_TYPED_HOOK_REGISTRATION_GUARDS = {
|
||||
const BUNDLED_LIVE_CONFIG_HOOK_GUARDS = {
|
||||
"extensions/active-memory/index.ts": ["resolveLivePluginConfigObject(", '"active-memory"'],
|
||||
"extensions/diffs/src/plugin.ts": [
|
||||
'resolvePluginConfigObject(currentConfig, "diffs")',
|
||||
"resolveLivePluginConfigObject(",
|
||||
'"diffs"',
|
||||
"api.runtime.config?.loadConfig?.() ?? api.config",
|
||||
],
|
||||
"extensions/memory-core/src/dreaming.ts": [
|
||||
|
||||
Reference in New Issue
Block a user