bluebubbles: gracefully handle disabled private API with action/tool filtering and fallbacks (#16002)

Merged via /review-pr -> /prepare-pr -> /merge-pr.

Prepared head SHA: 243cc0cc9a
Co-authored-by: tyler6204 <243?+tyler6204@users.noreply.github.com>
Co-authored-by: tyler6204 <64381258+tyler6204@users.noreply.github.com>
Reviewed-by: @tyler6204
This commit is contained in:
Tyler Yust
2026-02-13 21:15:56 -08:00
committed by GitHub
parent d8beddc8b7
commit 45e12d2388
13 changed files with 305 additions and 23 deletions

View File

@@ -2,6 +2,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk";
import crypto from "node:crypto";
import { stripMarkdown } from "openclaw/plugin-sdk";
import { resolveBlueBubblesAccount } from "./accounts.js";
import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
import {
extractHandleFromChatGuid,
normalizeBlueBubblesHandle,
@@ -397,6 +398,7 @@ export async function sendMessageBlueBubbles(
if (!password) {
throw new Error("BlueBubbles password is required");
}
const privateApiStatus = getCachedBlueBubblesPrivateApiStatus(account.accountId);
const target = resolveSendTarget(to);
const chatGuid = await resolveChatGuidForTarget({
@@ -422,18 +424,26 @@ export async function sendMessageBlueBubbles(
);
}
const effectId = resolveEffectId(opts.effectId);
const needsPrivateApi = Boolean(opts.replyToMessageGuid || effectId);
const wantsReplyThread = Boolean(opts.replyToMessageGuid?.trim());
const wantsEffect = Boolean(effectId);
const needsPrivateApi = wantsReplyThread || wantsEffect;
const canUsePrivateApi = needsPrivateApi && privateApiStatus !== false;
if (wantsEffect && privateApiStatus === false) {
throw new Error(
"BlueBubbles send failed: reply/effect requires Private API, but it is disabled on the BlueBubbles server.",
);
}
const payload: Record<string, unknown> = {
chatGuid,
tempGuid: crypto.randomUUID(),
message: strippedText,
};
if (needsPrivateApi) {
if (canUsePrivateApi) {
payload.method = "private-api";
}
// Add reply threading support
if (opts.replyToMessageGuid) {
if (wantsReplyThread && canUsePrivateApi) {
payload.selectedMessageGuid = opts.replyToMessageGuid;
payload.partIndex = typeof opts.replyToPartIndex === "number" ? opts.replyToPartIndex : 0;
}