fix: suppress disabled channel read-only presence

This commit is contained in:
Peter Steinberger
2026-04-22 03:09:08 +01:00
parent 8d021ee7bf
commit de6f548a7c
4 changed files with 169 additions and 19 deletions

View File

@@ -958,6 +958,100 @@ describe("listConfiguredChannelIdsForReadOnlyScope", () => {
).toEqual([]);
});
it("treats disabled channel config as a hard read-only env suppressor", () => {
listPotentialConfiguredChannelIds.mockReturnValue(["demo-channel"]);
listPotentialConfiguredChannelPresenceSignals.mockReturnValue([
{ channelId: "demo-channel", source: "env" },
]);
const config = {
channels: {
"Demo-Channel": {
enabled: false,
token: "stale-token",
},
},
plugins: {
entries: {
"demo-channel": {
enabled: true,
},
},
},
} as OpenClawConfig;
expect(
resolveConfiguredChannelPresencePolicy({
config,
workspaceDir: "/tmp",
env: {
DEMO_CHANNEL_TOKEN: "ambient",
} as NodeJS.ProcessEnv,
includePersistedAuthState: false,
}),
).toEqual([]);
expect(
listConfiguredChannelIdsForReadOnlyScope({
config,
workspaceDir: "/tmp",
env: {
DEMO_CHANNEL_TOKEN: "ambient",
} as NodeJS.ProcessEnv,
includePersistedAuthState: false,
}),
).toEqual([]);
});
it("treats disabled channel config as a hard persisted-auth suppressor", () => {
listPotentialConfiguredChannelIds.mockReturnValue(["demo-channel"]);
listPotentialConfiguredChannelPresenceSignals.mockReturnValue([
{ channelId: "demo-channel", source: "persisted-auth" },
]);
expect(
listConfiguredChannelIdsForReadOnlyScope({
config: {
channels: {
"demo-channel": {
enabled: false,
},
},
plugins: {
entries: {
"demo-channel": {
enabled: true,
},
},
},
} as OpenClawConfig,
workspaceDir: "/tmp",
env: {},
}),
).toEqual([]);
});
it("treats disabled channel config as a hard manifest-env suppressor", () => {
expect(
listConfiguredChannelIdsForReadOnlyScope({
config: {
channels: {
"external-env-channel": {
enabled: false,
},
},
plugins: {
allow: ["external-env-channel-plugin"],
},
} as OpenClawConfig,
workspaceDir: "/tmp",
env: {
EXTERNAL_ENV_CHANNEL_TOKEN: "token",
} as NodeJS.ProcessEnv,
includePersistedAuthState: false,
}),
).toEqual([]);
});
it("lets explicit bundled channel config bypass restrictive allowlists", () => {
const config = {
channels: {

View File

@@ -322,6 +322,24 @@ function addPolicySignal(
sources.add(source);
}
function listDisabledChannelIdsForConfig(config: OpenClawConfig): string[] {
const channels = config.channels;
if (!channels || typeof channels !== "object" || Array.isArray(channels)) {
return [];
}
return Object.entries(channels)
.filter(([, value]) => {
return (
value &&
typeof value === "object" &&
!Array.isArray(value) &&
(value as { enabled?: unknown }).enabled === false
);
})
.map(([channelId]) => normalizeOptionalLowercaseString(channelId))
.filter((channelId): channelId is string => Boolean(channelId));
}
export function resolveConfiguredChannelPresencePolicy(params: {
config: OpenClawConfig;
activationSourceConfig?: OpenClawConfig;
@@ -344,6 +362,7 @@ export function resolveConfiguredChannelPresencePolicy(params: {
cache: params.cache,
}).plugins;
const disabledChannelIds = new Set(listDisabledChannelIdsForConfig(params.config));
const entrySources = new Map<string, Set<ConfiguredChannelPresenceSource>>();
for (const channelId of listExplicitConfiguredChannelIdsForConfig(params.config)) {
addPolicySignal(entrySources, channelId, "explicit-config");
@@ -364,6 +383,9 @@ export function resolveConfiguredChannelPresencePolicy(params: {
})) {
addPolicySignal(entrySources, signal.channelId, signal.source);
}
for (const channelId of disabledChannelIds) {
entrySources.delete(channelId);
}
const activationSource = createPluginActivationSource({
config: params.activationSourceConfig ?? params.config,
@@ -428,22 +450,7 @@ export function listConfiguredAnnounceChannelIdsForConfig(params: {
env?: NodeJS.ProcessEnv;
cache?: boolean;
}): string[] {
const channels = params.config.channels;
const disabledChannelIds = new Set(
channels && typeof channels === "object" && !Array.isArray(channels)
? Object.entries(channels)
.filter(([, value]) => {
return (
value &&
typeof value === "object" &&
!Array.isArray(value) &&
(value as { enabled?: unknown }).enabled === false
);
})
.map(([channelId]) => normalizeOptionalLowercaseString(channelId))
.filter((channelId): channelId is string => Boolean(channelId))
: [],
);
const disabledChannelIds = new Set(listDisabledChannelIdsForConfig(params.config));
return normalizeChannelIds([
...listExplicitConfiguredChannelIdsForConfig(params.config),
...listConfiguredChannelIdsForReadOnlyScope({