diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e65f84dd19..f3682948496 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -36,6 +36,7 @@ Docs: https://docs.openclaw.ai - CLI/Claude: verify stored Claude CLI session ids have a readable project transcript before resuming, clearing phantom bindings with `reason=transcript-missing` instead of silently starting fresh under `--resume`. Fixes #70177. - 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. - 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. - Plugins/install: keep bundled plugin dependencies off npm install while repairing them when plugins activate from a packaged install, including Feishu/Lark, Browser, and direct bundled channel setup-entry loads. - Memory/LanceDB: retry initialization after a failed LanceDB load and report unsupported Intel macOS native runtime clearly instead of caching the failure or repeatedly attempting an install that cannot work. diff --git a/extensions/diffs/src/browser.test.ts b/extensions/diffs/src/browser.test.ts index fd02bb8786b..502a70df4d2 100644 --- a/extensions/diffs/src/browser.test.ts +++ b/extensions/diffs/src/browser.test.ts @@ -192,7 +192,7 @@ describe("PlaywrightDiffScreenshotter", () => { }); describe("diffs plugin registration", () => { - it("applies plugin-config defaults through registered tool and viewer handler", async () => { + it("uses live runtime viewer-access config through the registered HTTP handler", async () => { type RegisteredTool = { execute?: (toolCallId: string, params: Record) => Promise; }; @@ -207,6 +207,23 @@ describe("diffs plugin registration", () => { | undefined; let registeredHttpRouteHandler: HttpRouteHandler | undefined; const on = vi.fn(); + let configFile: OpenClawConfig = { + gateway: { + port: 18789, + bind: "loopback", + }, + plugins: { + entries: { + diffs: { + config: { + security: { + allowRemoteViewer: true, + }, + }, + }, + }, + }, + } as OpenClawConfig; const api = createTestPluginApi({ id: "diffs", @@ -233,7 +250,11 @@ describe("diffs plugin registration", () => { allowRemoteViewer: true, }, }, - runtime: {} as never, + runtime: { + config: { + loadConfig: () => configFile, + }, + } as never, registerTool(tool: Parameters[0]) { registeredToolFactory = typeof tool === "function" ? tool : () => tool; }, @@ -293,6 +314,21 @@ describe("diffs plugin registration", () => { }, ); + configFile = { + ...configFile, + plugins: { + entries: { + diffs: { + config: { + security: { + allowRemoteViewer: false, + }, + }, + }, + }, + }, + } as OpenClawConfig; + const proxiedRes = createMockServerResponse(); const proxiedHandled = await registeredHttpRouteHandler?.( localReq({ @@ -306,7 +342,7 @@ describe("diffs plugin registration", () => { ); expect(proxiedHandled).toBe(true); - expect(proxiedRes.statusCode).toBe(200); + expect(proxiedRes.statusCode).toBe(404); }); }); diff --git a/extensions/diffs/src/http.ts b/extensions/diffs/src/http.ts index a4158ac3d3d..4033faf1436 100644 --- a/extensions/diffs/src/http.ts +++ b/extensions/diffs/src/http.ts @@ -29,6 +29,11 @@ export function createDiffsHttpHandler(params: { allowRemoteViewer?: boolean; trustedProxies?: readonly string[]; allowRealIpFallback?: boolean; + resolveAccessConfig?: () => { + allowRemoteViewer?: boolean; + trustedProxies?: readonly string[]; + allowRealIpFallback?: boolean; + }; }) { const viewerFailureLimiter = new ViewerFailureLimiter(); @@ -46,11 +51,16 @@ export function createDiffsHttpHandler(params: { return false; } - const access = resolveViewerAccess(req, { + const accessConfig = params.resolveAccessConfig?.() ?? { + allowRemoteViewer: params.allowRemoteViewer, trustedProxies: params.trustedProxies, allowRealIpFallback: params.allowRealIpFallback, + }; + const access = resolveViewerAccess(req, { + trustedProxies: accessConfig.trustedProxies, + allowRealIpFallback: accessConfig.allowRealIpFallback, }); - if (!access.localRequest && params.allowRemoteViewer !== true) { + if (!access.localRequest && accessConfig.allowRemoteViewer !== true) { respondText(res, 404, "Diff not found"); return true; } diff --git a/extensions/diffs/src/plugin.ts b/extensions/diffs/src/plugin.ts index 7cb7ec7fa0f..d58b180fb41 100644 --- a/extensions/diffs/src/plugin.ts +++ b/extensions/diffs/src/plugin.ts @@ -1,4 +1,5 @@ import path from "node:path"; +import { resolvePluginConfigObject } from "openclaw/plugin-sdk/config-runtime"; import { resolvePreferredOpenClawTmpDir, type OpenClawPluginApi } from "../api.js"; import { resolveDiffsPluginDefaults, @@ -12,12 +13,20 @@ import { createDiffsTool } from "./tool.js"; export function registerDiffsPlugin(api: OpenClawPluginApi): void { const defaults = resolveDiffsPluginDefaults(api.pluginConfig); - const security = resolveDiffsPluginSecurity(api.pluginConfig); const viewerBaseUrl = resolveDiffsPluginViewerBaseUrl(api.pluginConfig); const store = new DiffArtifactStore({ rootDir: path.join(resolvePreferredOpenClawTmpDir(), "openclaw-diffs"), logger: api.logger, }); + const resolveCurrentAccessConfig = () => { + const currentConfig = api.runtime.config?.loadConfig?.() ?? api.config; + const pluginConfig = resolvePluginConfigObject(currentConfig, "diffs") ?? api.pluginConfig; + return { + allowRemoteViewer: resolveDiffsPluginSecurity(pluginConfig).allowRemoteViewer, + trustedProxies: currentConfig.gateway?.trustedProxies, + allowRealIpFallback: currentConfig.gateway?.allowRealIpFallback === true, + }; + }; api.registerTool( (ctx) => createDiffsTool({ api, store, defaults, viewerBaseUrl, context: ctx }), @@ -32,9 +41,10 @@ export function registerDiffsPlugin(api: OpenClawPluginApi): void { handler: createDiffsHttpHandler({ store, logger: api.logger, - allowRemoteViewer: security.allowRemoteViewer, + allowRemoteViewer: resolveDiffsPluginSecurity(api.pluginConfig).allowRemoteViewer, trustedProxies: api.config.gateway?.trustedProxies, allowRealIpFallback: api.config.gateway?.allowRealIpFallback === true, + resolveAccessConfig: resolveCurrentAccessConfig, }), }); api.on("before_prompt_build", async () => ({ diff --git a/src/plugins/contracts/boundary-invariants.test.ts b/src/plugins/contracts/boundary-invariants.test.ts index 2cb8fd6138e..44b71bd515d 100644 --- a/src/plugins/contracts/boundary-invariants.test.ts +++ b/src/plugins/contracts/boundary-invariants.test.ts @@ -51,6 +51,10 @@ const BUNDLED_LIVE_CONFIG_HOOK_GUARDS = { 'resolvePluginConfigObject(api.runtime.config.loadConfig(), "active-memory")', "api.runtime.config.loadConfig()", ], + "extensions/diffs/src/plugin.ts": [ + 'resolvePluginConfigObject(currentConfig, "diffs")', + "api.runtime.config?.loadConfig?.() ?? api.config", + ], "extensions/memory-core/src/dreaming.ts": [ 'params.reason === "runtime"', "resolveMemoryCorePluginConfig(startupCfg)",