fix(diffs): refresh live viewer access policy

This commit is contained in:
Vincent Koc
2026-04-22 14:13:56 -07:00
parent 0588dfe15d
commit f1372681a8
5 changed files with 68 additions and 7 deletions

View File

@@ -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.

View File

@@ -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<string, unknown>) => Promise<unknown>;
};
@@ -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<OpenClawPluginApi["registerTool"]>[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);
});
});

View File

@@ -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;
}

View File

@@ -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 () => ({

View File

@@ -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)",