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 44099a80e8.
- Required merge gates passed before the squash merge.

Prepared head SHA: 44099a80e8
Review: https://github.com/openclaw/openclaw/pull/85022#issuecomment-4510333662

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>
This commit is contained in:
Kaspre
2026-05-22 01:20:15 -04:00
committed by GitHub
parent 6a3377255d
commit e32e0f3f7f
5 changed files with 63 additions and 1 deletions

View File

@@ -34,6 +34,7 @@ Docs: https://docs.openclaw.ai
- 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.
- 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.
- 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.
- 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.
- Agents/subagents: surface blocked child-run completions as errors instead of successful subagent finishes. (#80886) Thanks @TurboTheTurtle.
- 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.
- WhatsApp: update Baileys to `7.0.0-rc13` and drop the obsolete logger type patch.

View File

@@ -121,6 +121,22 @@ describe("outbound channel resolution", () => {
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
});
it("returns a bundled plugin without bootstrapping", async () => {
const plugin = { id: "alpha" };
getLoadedChannelPluginMock.mockReturnValue(undefined);
getChannelPluginMock.mockReturnValue(plugin);
const channelResolution = await importChannelResolution("bundled-plugin");
expect(
channelResolution.resolveOutboundChannelPlugin({
channel: "alpha",
cfg: {} as never,
allowBootstrap: true,
}),
).toBe(plugin);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
});
it("falls back to the active registry when getChannelPlugin misses", async () => {
const plugin = { id: "alpha" };
getChannelPluginMock.mockReturnValue(undefined);

View File

@@ -192,8 +192,13 @@ export function resolveOutboundChannelPlugin(params: {
return directCurrent;
}
const bundledCurrent = resolve();
if (bundledCurrent) {
return bundledCurrent;
}
if (params.allowBootstrap !== true) {
return resolve();
return undefined;
}
maybeBootstrapChannelPlugin({ channel: normalized, cfg: params.cfg });

View File

@@ -245,6 +245,36 @@ describe("resolveMessageChannelSelection", () => {
verify?.(setupResult as never);
});
it("allows bootstrap while checking explicit and fallback channels", async () => {
const cfg = {} as never;
mocks.resolveOutboundChannelPlugin.mockImplementation(({ channel }: { channel: string }) =>
channel === "beta" ? { id: "beta" } : undefined,
);
await expect(
expectResolvedSelection({
cfg,
channel: "alpha",
fallbackChannel: "beta",
}),
).resolves.toEqual({
channel: "beta",
configured: [],
source: "tool-context-fallback",
});
expect(mocks.resolveOutboundChannelPlugin).toHaveBeenNthCalledWith(1, {
channel: "alpha",
cfg,
allowBootstrap: true,
});
expect(mocks.resolveOutboundChannelPlugin).toHaveBeenNthCalledWith(2, {
channel: "beta",
cfg,
allowBootstrap: true,
});
});
it.each([
{
params: { cfg: {} as never, channel: "channel:C123", fallbackChannel: "not-a-channel" },

View File

@@ -49,9 +49,19 @@ function resolveAvailableKnownChannel(params: {
if (!normalized) {
return undefined;
}
// Pass `allowBootstrap: true` so the in-agent message tool path can resolve
// outbound channels in processes where external channel adapters have not
// been eagerly loaded (e.g. `openclaw agent --local`). Already-loaded and
// bundled plugins still resolve through side-effect-free fast paths first.
// Without the bootstrap fallback, official external channels can surface as
// the recurring "Channel is unavailable" error on `--local`-routed
// dispatches that the CLI send-path could deliver to.
// Adjacent to #77254 (cron-announce / final-reply paths); this closes the
// remaining in-agent caller in the same family.
return resolveOutboundChannelPlugin({
channel: normalized,
cfg: params.cfg,
allowBootstrap: true,
})
? normalized
: undefined;