Skip to content

Commit 9d772d6

Browse files
committed
fix(ci): normalize bundle mcp paths and skip explicit channel scans
1 parent ff6541f commit 9d772d6

File tree

3 files changed

+44
-14
lines changed

3 files changed

+44
-14
lines changed

src/infra/outbound/channel-selection.test.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,23 @@ describe("resolveMessageChannelSelection", () => {
143143
});
144144
});
145145

146+
it("does not probe configured channels when an explicit channel is available", async () => {
147+
const isConfigured = vi.fn(async () => true);
148+
mocks.listChannelPlugins.mockReturnValue([makePlugin({ id: "slack", isConfigured })]);
149+
150+
const selection = await resolveMessageChannelSelection({
151+
cfg: {} as never,
152+
channel: "slack",
153+
});
154+
155+
expect(selection).toEqual({
156+
channel: "slack",
157+
configured: [],
158+
source: "explicit",
159+
});
160+
expect(isConfigured).not.toHaveBeenCalled();
161+
});
162+
146163
it("falls back to tool context channel when explicit channel is unknown", async () => {
147164
const selection = await resolveMessageChannelSelection({
148165
cfg: {} as never,

src/infra/outbound/channel-selection.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { listChannelPlugins } from "../../channels/plugins/index.js";
21
import type { ChannelPlugin } from "../../channels/plugins/types.js";
32
import type { OpenClawConfig } from "../../config/config.js";
3+
import { listChannelPlugins } from "../../channels/plugins/index.js";
44
import { defaultRuntime } from "../../runtime.js";
55
import {
66
listDeliverableMessageChannels,
@@ -165,7 +165,7 @@ export async function resolveMessageChannelSelection(params: {
165165
if (fallback) {
166166
return {
167167
channel: fallback,
168-
configured: await listConfiguredMessageChannels(params.cfg),
168+
configured: [],
169169
source: "tool-context-fallback",
170170
};
171171
}
@@ -176,7 +176,7 @@ export async function resolveMessageChannelSelection(params: {
176176
}
177177
return {
178178
channel: availableExplicit,
179-
configured: await listConfiguredMessageChannels(params.cfg),
179+
configured: [],
180180
source: "explicit",
181181
};
182182
}
@@ -188,7 +188,7 @@ export async function resolveMessageChannelSelection(params: {
188188
if (fallback) {
189189
return {
190190
channel: fallback,
191-
configured: await listConfiguredMessageChannels(params.cfg),
191+
configured: [],
192192
source: "tool-context-fallback",
193193
};
194194
}

src/plugins/bundle-mcp.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import fs from "node:fs";
22
import path from "node:path";
33
import type { OpenClawConfig } from "../config/config.js";
4+
import type { PluginBundleFormat } from "./types.js";
45
import { applyMergePatch } from "../config/merge-patch.js";
56
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
67
import { isRecord } from "../utils.js";
@@ -13,7 +14,7 @@ import {
1314
} from "./bundle-manifest.js";
1415
import { normalizePluginsConfig, resolveEffectiveEnableState } from "./config-state.js";
1516
import { loadPluginManifestRegistry } from "./manifest-registry.js";
16-
import type { PluginBundleFormat } from "./types.js";
17+
import { safeRealpathSync } from "./path-safety.js";
1718

1819
export type BundleMcpServerConfig = Record<string, unknown>;
1920

@@ -121,6 +122,14 @@ function expandBundleRootPlaceholders(value: string, rootDir: string): string {
121122
return value.split(CLAUDE_PLUGIN_ROOT_PLACEHOLDER).join(rootDir);
122123
}
123124

125+
function canonicalizeBundlePath(targetPath: string): string {
126+
return path.normalize(safeRealpathSync(targetPath) ?? path.resolve(targetPath));
127+
}
128+
129+
function normalizeExpandedAbsolutePath(value: string): string {
130+
return path.isAbsolute(value) ? path.normalize(value) : value;
131+
}
132+
124133
function absolutizeBundleMcpServer(params: {
125134
rootDir: string;
126135
baseDir: string;
@@ -137,7 +146,7 @@ function absolutizeBundleMcpServer(params: {
137146
const expanded = expandBundleRootPlaceholders(command, params.rootDir);
138147
next.command = isExplicitRelativePath(expanded)
139148
? path.resolve(params.baseDir, expanded)
140-
: expanded;
149+
: normalizeExpandedAbsolutePath(expanded);
141150
}
142151

143152
const cwd = next.cwd;
@@ -150,7 +159,7 @@ function absolutizeBundleMcpServer(params: {
150159
if (typeof workingDirectory === "string") {
151160
const expanded = expandBundleRootPlaceholders(workingDirectory, params.rootDir);
152161
next.workingDirectory = path.isAbsolute(expanded)
153-
? expanded
162+
? path.normalize(expanded)
154163
: path.resolve(params.baseDir, expanded);
155164
}
156165

@@ -161,7 +170,7 @@ function absolutizeBundleMcpServer(params: {
161170
}
162171
const expanded = expandBundleRootPlaceholders(entry, params.rootDir);
163172
if (!isExplicitRelativePath(expanded)) {
164-
return expanded;
173+
return normalizeExpandedAbsolutePath(expanded);
165174
}
166175
return path.resolve(params.baseDir, expanded);
167176
});
@@ -171,7 +180,9 @@ function absolutizeBundleMcpServer(params: {
171180
next.env = Object.fromEntries(
172181
Object.entries(next.env).map(([key, value]) => [
173182
key,
174-
typeof value === "string" ? expandBundleRootPlaceholders(value, params.rootDir) : value,
183+
typeof value === "string"
184+
? normalizeExpandedAbsolutePath(expandBundleRootPlaceholders(value, params.rootDir))
185+
: value,
175186
]),
176187
);
177188
}
@@ -183,10 +194,11 @@ function loadBundleFileBackedMcpConfig(params: {
183194
rootDir: string;
184195
relativePath: string;
185196
}): BundleMcpConfig {
186-
const absolutePath = path.resolve(params.rootDir, params.relativePath);
197+
const rootDir = canonicalizeBundlePath(params.rootDir);
198+
const absolutePath = path.resolve(rootDir, params.relativePath);
187199
const opened = openBoundaryFileSync({
188200
absolutePath,
189-
rootPath: params.rootDir,
201+
rootPath: rootDir,
190202
boundaryLabel: "plugin root",
191203
rejectHardlinks: true,
192204
});
@@ -200,12 +212,12 @@ function loadBundleFileBackedMcpConfig(params: {
200212
}
201213
const raw = JSON.parse(fs.readFileSync(opened.fd, "utf-8")) as unknown;
202214
const servers = extractMcpServerMap(raw);
203-
const baseDir = path.dirname(absolutePath);
215+
const baseDir = canonicalizeBundlePath(path.dirname(absolutePath));
204216
return {
205217
mcpServers: Object.fromEntries(
206218
Object.entries(servers).map(([serverName, server]) => [
207219
serverName,
208-
absolutizeBundleMcpServer({ rootDir: params.rootDir, baseDir, server }),
220+
absolutizeBundleMcpServer({ rootDir, baseDir, server }),
209221
]),
210222
),
211223
};
@@ -221,12 +233,13 @@ function loadBundleInlineMcpConfig(params: {
221233
if (!isRecord(params.raw.mcpServers)) {
222234
return { mcpServers: {} };
223235
}
236+
const baseDir = canonicalizeBundlePath(params.baseDir);
224237
const servers = extractMcpServerMap(params.raw.mcpServers);
225238
return {
226239
mcpServers: Object.fromEntries(
227240
Object.entries(servers).map(([serverName, server]) => [
228241
serverName,
229-
absolutizeBundleMcpServer({ rootDir: params.baseDir, baseDir: params.baseDir, server }),
242+
absolutizeBundleMcpServer({ rootDir: baseDir, baseDir, server }),
230243
]),
231244
),
232245
};

0 commit comments

Comments
 (0)