Skip to content

Commit e32e0f3

Browse files
Kaspreclaudeclawsweeper[bot]Takhoffman
authored
fix(channels): pass allowBootstrap from channel-selection so in-agent message tool resolves channels in --local processes (#85022)
Summary: - The branch passes `allowBootstrap: true` through outbound channel selection, preserves bundled-plugin resolution before bootstrap, adds focused regression tests, and documents the fix in the changelog. - Reproducibility: yes. source inspection gives a high-confidence reproduction path: current main omits `allow ... run the live current-main failure, but the supplied after-fix terminal proof exercises the implicated path. Automerge notes: - PR branch already contained follow-up commit before automerge: test(channels): cover bootstrap channel selection - PR branch already contained follow-up commit before automerge: fix(channels): avoid unnecessary bootstrap during message sends - PR branch already contained follow-up commit before automerge: fix(channels): pass allowBootstrap from channel-selection so in-agent… Validation: - ClawSweeper review passed for head 44099a8. - Required merge gates passed before the squash merge. Prepared head SHA: 44099a8 Review: #85022 (comment) Co-authored-by: Kaspre <kaspre@gmail.com> Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com> Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> Approved-by: takhoffman Co-authored-by: takhoffman <781889+takhoffman@users.noreply.github.com>
1 parent 6a33772 commit e32e0f3

5 files changed

Lines changed: 63 additions & 1 deletion

File tree

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai
3434
- Auto-reply/ACP: wait for same-channel block reply delivery before starting tool work, while still honoring ACP dispatch aborts so stopped turns do not wait on slow channel sends. (#83722) Thanks @IWhatsskill.
3535
- Codex/ACP: mark required child-run completions that only report progress, omit a final deliverable, or fail requester delivery as blocked while preserving real final reports. (#85110) Thanks @IWhatsskill.
3636
- Channels: treat bare abort messages such as `stop`, `abort`, and `wait` as immediate control commands in inbound debounce paths so stop requests are not delayed behind pending message coalescing. (#83348) Thanks @IWhatsskill.
37+
- Channels/message tool: resolve configured external channel plugins during in-agent channel selection, so `openclaw agent --local` message-tool sends no longer report an available channel as unavailable. (#85022) Thanks @Kaspre.
3738
- Agents/subagents: surface blocked child-run completions as errors instead of successful subagent finishes. (#80886) Thanks @TurboTheTurtle.
3839
- Agents/Pi: treat accepted embedded `sessions_spawn` child-session handoffs as terminal progress so parent turns no longer report false non-deliverable failures. (#85054) Thanks @samzong.
3940
- WhatsApp: update Baileys to `7.0.0-rc13` and drop the obsolete logger type patch.

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

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,22 @@ describe("outbound channel resolution", () => {
121121
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
122122
});
123123

124+
it("returns a bundled plugin without bootstrapping", async () => {
125+
const plugin = { id: "alpha" };
126+
getLoadedChannelPluginMock.mockReturnValue(undefined);
127+
getChannelPluginMock.mockReturnValue(plugin);
128+
const channelResolution = await importChannelResolution("bundled-plugin");
129+
130+
expect(
131+
channelResolution.resolveOutboundChannelPlugin({
132+
channel: "alpha",
133+
cfg: {} as never,
134+
allowBootstrap: true,
135+
}),
136+
).toBe(plugin);
137+
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
138+
});
139+
124140
it("falls back to the active registry when getChannelPlugin misses", async () => {
125141
const plugin = { id: "alpha" };
126142
getChannelPluginMock.mockReturnValue(undefined);

src/infra/outbound/channel-resolution.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,8 +192,13 @@ export function resolveOutboundChannelPlugin(params: {
192192
return directCurrent;
193193
}
194194

195+
const bundledCurrent = resolve();
196+
if (bundledCurrent) {
197+
return bundledCurrent;
198+
}
199+
195200
if (params.allowBootstrap !== true) {
196-
return resolve();
201+
return undefined;
197202
}
198203

199204
maybeBootstrapChannelPlugin({ channel: normalized, cfg: params.cfg });

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -245,6 +245,36 @@ describe("resolveMessageChannelSelection", () => {
245245
verify?.(setupResult as never);
246246
});
247247

248+
it("allows bootstrap while checking explicit and fallback channels", async () => {
249+
const cfg = {} as never;
250+
mocks.resolveOutboundChannelPlugin.mockImplementation(({ channel }: { channel: string }) =>
251+
channel === "beta" ? { id: "beta" } : undefined,
252+
);
253+
254+
await expect(
255+
expectResolvedSelection({
256+
cfg,
257+
channel: "alpha",
258+
fallbackChannel: "beta",
259+
}),
260+
).resolves.toEqual({
261+
channel: "beta",
262+
configured: [],
263+
source: "tool-context-fallback",
264+
});
265+
266+
expect(mocks.resolveOutboundChannelPlugin).toHaveBeenNthCalledWith(1, {
267+
channel: "alpha",
268+
cfg,
269+
allowBootstrap: true,
270+
});
271+
expect(mocks.resolveOutboundChannelPlugin).toHaveBeenNthCalledWith(2, {
272+
channel: "beta",
273+
cfg,
274+
allowBootstrap: true,
275+
});
276+
});
277+
248278
it.each([
249279
{
250280
params: { cfg: {} as never, channel: "channel:C123", fallbackChannel: "not-a-channel" },

src/infra/outbound/channel-selection.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,9 +49,19 @@ function resolveAvailableKnownChannel(params: {
4949
if (!normalized) {
5050
return undefined;
5151
}
52+
// Pass `allowBootstrap: true` so the in-agent message tool path can resolve
53+
// outbound channels in processes where external channel adapters have not
54+
// been eagerly loaded (e.g. `openclaw agent --local`). Already-loaded and
55+
// bundled plugins still resolve through side-effect-free fast paths first.
56+
// Without the bootstrap fallback, official external channels can surface as
57+
// the recurring "Channel is unavailable" error on `--local`-routed
58+
// dispatches that the CLI send-path could deliver to.
59+
// Adjacent to #77254 (cron-announce / final-reply paths); this closes the
60+
// remaining in-agent caller in the same family.
5261
return resolveOutboundChannelPlugin({
5362
channel: normalized,
5463
cfg: params.cfg,
64+
allowBootstrap: true,
5565
})
5666
? normalized
5767
: undefined;

0 commit comments

Comments
 (0)