Skip to content

Commit b604f43

Browse files
committed
fix(gateway): preserve configured channel warning source
1 parent 4b2ecef commit b604f43

5 files changed

Lines changed: 76 additions & 22 deletions

File tree

src/gateway/server-startup-log.test.ts

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ const pluginRegistryMocks = vi.hoisted(() => ({
88
loadPluginManifestRegistryForPluginRegistry: vi.fn(),
99
}));
1010

11-
vi.mock("../plugins/plugin-registry-contributions.js", () => ({
11+
vi.mock("../plugins/plugin-registry.js", async (importOriginal) => ({
12+
...(await importOriginal<typeof import("../plugins/plugin-registry.js")>()),
1213
loadPluginManifestRegistryForPluginRegistry:
1314
pluginRegistryMocks.loadPluginManifestRegistryForPluginRegistry,
1415
}));
@@ -101,7 +102,34 @@ describe("gateway startup log", () => {
101102

102103
expect(warn.mock.calls).toEqual([
103104
[
104-
"configured channel warning: channels.slack is configured but no channel plugin is loadable (untrusted-plugin). Run `openclaw doctor --fix` or update plugins.allow/plugins.entries before relying on this channel.",
105+
'configured channel warning: channels.slack: channel is configured, but external plugin "slack" is installed without explicit trust. Add plugins.entries.slack.enabled=true. Fix plugin enablement before relying on setup guidance for this channel.',
106+
],
107+
]);
108+
});
109+
110+
it("warns when a configured channel has no owning plugin", async () => {
111+
const info = vi.fn();
112+
const warn = vi.fn();
113+
114+
await logGatewayStartup({
115+
cfg: {
116+
channels: {
117+
"missing-chat": {
118+
enabled: true,
119+
token: "configured",
120+
},
121+
},
122+
},
123+
bindHost: "127.0.0.1",
124+
loadedPluginIds: [],
125+
port: 18789,
126+
log: { info, warn },
127+
isNixMode: false,
128+
});
129+
130+
expect(warn.mock.calls).toEqual([
131+
[
132+
"configured channel warning: channels.missing-chat is configured but no channel plugin is installed or loadable (no-channel-owner). Run `openclaw doctor --fix` or install the channel plugin before relying on this channel.",
105133
],
106134
]);
107135
});
@@ -138,7 +166,7 @@ describe("gateway startup log", () => {
138166
isNixMode: false,
139167
});
140168

141-
expect(warn.mock.calls[0]?.[0]).toContain("channels.slack is configured");
169+
expect(warn.mock.calls[0]?.[0]).toContain("channels.slack: channel is configured");
142170
expect(warn.mock.calls[0]?.[0]).not.toContain(String.fromCharCode(0x1b));
143171
});
144172

src/gateway/server-startup-log.ts

Lines changed: 36 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,6 @@ import {
1515
import { resolveThinkingDefault } from "../agents/model-thinking-default.js";
1616
import type { OpenClawConfig } from "../config/types.openclaw.js";
1717
import { getResolvedLoggerSettings } from "../logging.js";
18-
import {
19-
resolveConfiguredChannelPresencePolicy,
20-
type ConfiguredChannelPresencePolicyEntry,
21-
} from "../plugins/channel-presence-policy.js";
2218
import { collectEnabledInsecureOrDangerousFlagsFromCurrentSnapshot } from "../security/dangerous-config-flags-current.js";
2319

2420
type StartupThinkLevel =
@@ -70,7 +66,7 @@ export async function logGatewayStartup(params: {
7066
params.log.info("gateway: running in Nix mode (config managed externally)");
7167
}
7268

73-
for (const warning of collectConfiguredChannelStartupWarnings({
69+
for (const warning of await collectConfiguredChannelStartupWarnings({
7470
cfg: params.cfg,
7571
activationSourceConfig: params.activationSourceConfig,
7672
})) {
@@ -178,28 +174,50 @@ export function formatAgentModelStartupDetails(params: {
178174
return `thinking=${thinking}, fast=${formatFastModeValue(fast.mode)}`;
179175
}
180176

181-
function collectConfiguredChannelStartupWarnings(params: {
177+
async function collectConfiguredChannelStartupWarnings(params: {
182178
cfg: OpenClawConfig;
183179
activationSourceConfig?: OpenClawConfig;
184-
}): string[] {
185-
return resolveConfiguredChannelPresencePolicy({
180+
}): Promise<string[]> {
181+
const [blockerModule, presencePolicyModule, pluginRegistryModule] = await Promise.all([
182+
import("../commands/doctor/shared/channel-plugin-blockers.js"),
183+
import("../plugins/channel-presence-policy.js"),
184+
import("../plugins/plugin-registry.js"),
185+
]);
186+
const manifestRegistry = pluginRegistryModule.loadPluginManifestRegistryForPluginRegistry({
186187
config: params.cfg,
187-
activationSourceConfig: params.activationSourceConfig,
188-
includePersistedAuthState: false,
189-
})
190-
.filter((entry) => !entry.effective && entry.blockedReasons.length > 0)
191-
.map(formatConfiguredChannelStartupWarning);
188+
env: process.env,
189+
includeDisabled: true,
190+
});
191+
const hits = blockerModule.scanConfiguredChannelPluginBlockers(
192+
params.cfg,
193+
process.env,
194+
params.activationSourceConfig,
195+
);
196+
const blockerWarnings = blockerModule
197+
.collectConfiguredChannelPluginBlockerWarnings(hits)
198+
.map((warning) => `configured channel warning: ${warning.replace(/^[-]\s*/u, "")}`);
199+
const missingOwnerWarnings = presencePolicyModule
200+
.resolveConfiguredChannelPresencePolicy({
201+
config: params.cfg,
202+
activationSourceConfig: params.activationSourceConfig,
203+
includePersistedAuthState: false,
204+
manifestRecords: manifestRegistry.plugins,
205+
})
206+
.filter((entry) => !entry.effective && entry.blockedReasons.includes("no-channel-owner"))
207+
.map(formatConfiguredChannelMissingOwnerStartupWarning);
208+
return [...blockerWarnings, ...missingOwnerWarnings];
192209
}
193210

194-
function formatConfiguredChannelStartupWarning(
195-
entry: ConfiguredChannelPresencePolicyEntry,
196-
): string {
211+
function formatConfiguredChannelMissingOwnerStartupWarning(entry: {
212+
channelId: string;
213+
blockedReasons: readonly string[];
214+
}): string {
197215
const channelId = sanitizeForLog(entry.channelId);
198216
const reasons = normalizeSortedUniqueStringEntries(entry.blockedReasons).join(", ");
199217
return (
200218
`configured channel warning: channels.${channelId} is configured but no channel plugin ` +
201-
`is loadable (${reasons}). Run \`openclaw doctor --fix\` or update plugins.allow/plugins.entries ` +
202-
"before relying on this channel."
219+
`is installed or loadable (${reasons}). Run \`openclaw doctor --fix\` or install the ` +
220+
"channel plugin before relying on this channel."
203221
);
204222
}
205223

src/gateway/server-startup-post-attach.test.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -388,6 +388,11 @@ describe("startGatewayPostAttachRuntime", () => {
388388
expect(hoisted.setInternalHooksEnabled).not.toHaveBeenCalled();
389389
expect(hoisted.logGatewayStartup).toHaveBeenCalledTimes(1);
390390
expect(firstStartupLog().loadedPluginIds).toEqual(["beta", "alpha"]);
391+
expect(hoisted.logGatewayStartup).toHaveBeenCalledWith(
392+
expect.objectContaining({
393+
activationSourceConfig: { hooks: { internal: { enabled: false } } },
394+
}),
395+
);
391396
expect(log.info).toHaveBeenCalledWith("gateway ready");
392397
expect(hoisted.scheduleRestartAbortedMainSessionRecovery).toHaveBeenCalledWith({
393398
cfg: { hooks: { internal: { enabled: false } } },
@@ -2240,6 +2245,7 @@ function createPostAttachParams(overrides: Partial<PostAttachParams> = {}): Post
22402245
error: vi.fn(),
22412246
},
22422247
gatewayPluginConfigAtStart: { hooks: { internal: { enabled: false } } } as never,
2248+
activationSourceConfig: { hooks: { internal: { enabled: false } } } as never,
22432249
pluginRegistry: {
22442250
plugins: [
22452251
{ id: "beta", status: "loaded" },

src/gateway/server-startup-post-attach.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1096,6 +1096,7 @@ export async function startGatewayPostAttachRuntime(
10961096
debug?: (msg: string) => void;
10971097
};
10981098
gatewayPluginConfigAtStart: OpenClawConfig;
1099+
activationSourceConfig: OpenClawConfig;
10991100
pluginRegistry: ReturnType<typeof loadOpenClawPlugins>;
11001101
defaultWorkspaceDir: string;
11011102
deps: CliDeps;
@@ -1176,7 +1177,7 @@ export async function startGatewayPostAttachRuntime(
11761177
const startupLogPromise = measureStartup(params.startupTrace, "post-attach.log", () =>
11771178
runtimeDeps.logGatewayStartup({
11781179
cfg: params.cfgAtStart,
1179-
activationSourceConfig: params.gatewayPluginConfigAtStart,
1180+
activationSourceConfig: params.activationSourceConfig,
11801181
bindHost: params.bindHost,
11811182
bindHosts: params.bindHosts,
11821183
port: params.port,

src/gateway/server.impl.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1640,6 +1640,7 @@ export async function startGatewayServer(
16401640
controlUiBasePath,
16411641
logTailscale,
16421642
gatewayPluginConfigAtStart,
1643+
activationSourceConfig: startupActivationSourceConfig,
16431644
pluginRegistry,
16441645
defaultWorkspaceDir,
16451646
deps,

0 commit comments

Comments
 (0)