@openclaw/bluebubbles 2026.3.1 → 2026.3.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,44 @@
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
2
+ import { afterEach, describe, expect, it } from "vitest";
3
+ import { createEmptyPluginRegistry } from "../../../src/plugins/registry.js";
4
+ import { setActivePluginRegistry } from "../../../src/plugins/runtime.js";
5
+ import type { WebhookTarget } from "./monitor-shared.js";
6
+ import { registerBlueBubblesWebhookTarget } from "./monitor.js";
7
+
8
+ function createTarget(): WebhookTarget {
9
+ return {
10
+ account: { accountId: "default" } as WebhookTarget["account"],
11
+ config: {} as OpenClawConfig,
12
+ runtime: {},
13
+ core: {} as WebhookTarget["core"],
14
+ path: "/bluebubbles-webhook",
15
+ };
16
+ }
17
+
18
+ describe("registerBlueBubblesWebhookTarget", () => {
19
+ afterEach(() => {
20
+ setActivePluginRegistry(createEmptyPluginRegistry());
21
+ });
22
+
23
+ it("registers and unregisters plugin HTTP route at path boundaries", () => {
24
+ const registry = createEmptyPluginRegistry();
25
+ setActivePluginRegistry(registry);
26
+
27
+ const unregisterA = registerBlueBubblesWebhookTarget(createTarget());
28
+ const unregisterB = registerBlueBubblesWebhookTarget(createTarget());
29
+
30
+ expect(registry.httpRoutes).toHaveLength(1);
31
+ expect(registry.httpRoutes[0]).toEqual(
32
+ expect.objectContaining({
33
+ pluginId: "bluebubbles",
34
+ path: "/bluebubbles-webhook",
35
+ source: "bluebubbles-webhook",
36
+ }),
37
+ );
38
+
39
+ unregisterA();
40
+ expect(registry.httpRoutes).toHaveLength(1);
41
+ unregisterB();
42
+ expect(registry.httpRoutes).toHaveLength(0);
43
+ });
44
+ });
@@ -0,0 +1,89 @@
1
+ import type { WizardPrompter } from "openclaw/plugin-sdk/bluebubbles";
2
+ import { describe, expect, it, vi } from "vitest";
3
+
4
+ vi.mock("openclaw/plugin-sdk/bluebubbles", () => ({
5
+ DEFAULT_ACCOUNT_ID: "default",
6
+ addWildcardAllowFrom: vi.fn(),
7
+ formatDocsLink: (_url: string, fallback: string) => fallback,
8
+ hasConfiguredSecretInput: (value: unknown) => {
9
+ if (typeof value === "string") {
10
+ return value.trim().length > 0;
11
+ }
12
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
13
+ return false;
14
+ }
15
+ const ref = value as { source?: unknown; provider?: unknown; id?: unknown };
16
+ const validSource = ref.source === "env" || ref.source === "file" || ref.source === "exec";
17
+ return (
18
+ validSource &&
19
+ typeof ref.provider === "string" &&
20
+ ref.provider.trim().length > 0 &&
21
+ typeof ref.id === "string" &&
22
+ ref.id.trim().length > 0
23
+ );
24
+ },
25
+ mergeAllowFromEntries: (_existing: unknown, entries: string[]) => entries,
26
+ createAccountListHelpers: () => ({
27
+ listAccountIds: () => ["default"],
28
+ resolveDefaultAccountId: () => "default",
29
+ }),
30
+ normalizeSecretInputString: (value: unknown) => {
31
+ if (typeof value !== "string") {
32
+ return undefined;
33
+ }
34
+ const trimmed = value.trim();
35
+ return trimmed.length > 0 ? trimmed : undefined;
36
+ },
37
+ normalizeAccountId: (value?: string | null) =>
38
+ value && value.trim().length > 0 ? value : "default",
39
+ promptAccountId: vi.fn(),
40
+ resolveAccountIdForConfigure: async (params: {
41
+ accountOverride?: string;
42
+ defaultAccountId: string;
43
+ }) => params.accountOverride?.trim() || params.defaultAccountId,
44
+ }));
45
+
46
+ describe("bluebubbles onboarding SecretInput", () => {
47
+ it("preserves existing password SecretRef when user keeps current credential", async () => {
48
+ const { blueBubblesOnboardingAdapter } = await import("./onboarding.js");
49
+ type ConfigureContext = Parameters<
50
+ NonNullable<typeof blueBubblesOnboardingAdapter.configure>
51
+ >[0];
52
+ const passwordRef = { source: "env", provider: "default", id: "BLUEBUBBLES_PASSWORD" };
53
+ const confirm = vi
54
+ .fn()
55
+ .mockResolvedValueOnce(true) // keep server URL
56
+ .mockResolvedValueOnce(true) // keep password SecretRef
57
+ .mockResolvedValueOnce(false); // keep default webhook path
58
+ const text = vi.fn();
59
+ const note = vi.fn();
60
+
61
+ const prompter = {
62
+ confirm,
63
+ text,
64
+ note,
65
+ } as unknown as WizardPrompter;
66
+
67
+ const context = {
68
+ cfg: {
69
+ channels: {
70
+ bluebubbles: {
71
+ enabled: true,
72
+ serverUrl: "http://127.0.0.1:1234",
73
+ password: passwordRef,
74
+ },
75
+ },
76
+ },
77
+ prompter,
78
+ runtime: { ...console, exit: vi.fn() } as ConfigureContext["runtime"],
79
+ forceAllowFrom: false,
80
+ accountOverrides: {},
81
+ shouldPromptAccountIds: false,
82
+ } satisfies ConfigureContext;
83
+
84
+ const result = await blueBubblesOnboardingAdapter.configure(context);
85
+
86
+ expect(result.cfg.channels?.bluebubbles?.password).toEqual(passwordRef);
87
+ expect(text).not.toHaveBeenCalled();
88
+ });
89
+ });
package/src/onboarding.ts CHANGED
@@ -4,39 +4,33 @@ import type {
4
4
  OpenClawConfig,
5
5
  DmPolicy,
6
6
  WizardPrompter,
7
- } from "openclaw/plugin-sdk";
7
+ } from "openclaw/plugin-sdk/bluebubbles";
8
8
  import {
9
9
  DEFAULT_ACCOUNT_ID,
10
- addWildcardAllowFrom,
11
10
  formatDocsLink,
12
11
  mergeAllowFromEntries,
13
12
  normalizeAccountId,
14
- promptAccountId,
15
- } from "openclaw/plugin-sdk";
13
+ resolveAccountIdForConfigure,
14
+ setTopLevelChannelDmPolicyWithAllowFrom,
15
+ } from "openclaw/plugin-sdk/bluebubbles";
16
16
  import {
17
17
  listBlueBubblesAccountIds,
18
18
  resolveBlueBubblesAccount,
19
19
  resolveDefaultBlueBubblesAccountId,
20
20
  } from "./accounts.js";
21
+ import { applyBlueBubblesConnectionConfig } from "./config-apply.js";
22
+ import { hasConfiguredSecretInput, normalizeSecretInputString } from "./secret-input.js";
21
23
  import { parseBlueBubblesAllowTarget } from "./targets.js";
22
24
  import { normalizeBlueBubblesServerUrl } from "./types.js";
23
25
 
24
26
  const channel = "bluebubbles" as const;
25
27
 
26
28
  function setBlueBubblesDmPolicy(cfg: OpenClawConfig, dmPolicy: DmPolicy): OpenClawConfig {
27
- const allowFrom =
28
- dmPolicy === "open" ? addWildcardAllowFrom(cfg.channels?.bluebubbles?.allowFrom) : undefined;
29
- return {
30
- ...cfg,
31
- channels: {
32
- ...cfg.channels,
33
- bluebubbles: {
34
- ...cfg.channels?.bluebubbles,
35
- dmPolicy,
36
- ...(allowFrom ? { allowFrom } : {}),
37
- },
38
- },
39
- };
29
+ return setTopLevelChannelDmPolicyWithAllowFrom({
30
+ cfg,
31
+ channel: "bluebubbles",
32
+ dmPolicy,
33
+ });
40
34
  }
41
35
 
42
36
  function setBlueBubblesAllowFrom(
@@ -158,21 +152,16 @@ export const blueBubblesOnboardingAdapter: ChannelOnboardingAdapter = {
158
152
  };
159
153
  },
160
154
  configure: async ({ cfg, prompter, accountOverrides, shouldPromptAccountIds }) => {
161
- const blueBubblesOverride = accountOverrides.bluebubbles?.trim();
162
155
  const defaultAccountId = resolveDefaultBlueBubblesAccountId(cfg);
163
- let accountId = blueBubblesOverride
164
- ? normalizeAccountId(blueBubblesOverride)
165
- : defaultAccountId;
166
- if (shouldPromptAccountIds && !blueBubblesOverride) {
167
- accountId = await promptAccountId({
168
- cfg,
169
- prompter,
170
- label: "BlueBubbles",
171
- currentId: accountId,
172
- listAccountIds: listBlueBubblesAccountIds,
173
- defaultAccountId,
174
- });
175
- }
156
+ const accountId = await resolveAccountIdForConfigure({
157
+ cfg,
158
+ prompter,
159
+ label: "BlueBubbles",
160
+ accountOverride: accountOverrides.bluebubbles,
161
+ shouldPromptAccountIds,
162
+ listAccountIds: listBlueBubblesAccountIds,
163
+ defaultAccountId,
164
+ });
176
165
 
177
166
  let next = cfg;
178
167
  const resolvedAccount = resolveBlueBubblesAccount({ cfg: next, accountId });
@@ -222,8 +211,11 @@ export const blueBubblesOnboardingAdapter: ChannelOnboardingAdapter = {
222
211
  }
223
212
 
224
213
  // Prompt for password
225
- let password = resolvedAccount.config.password?.trim();
226
- if (!password) {
214
+ const existingPassword = resolvedAccount.config.password;
215
+ const existingPasswordText = normalizeSecretInputString(existingPassword);
216
+ const hasConfiguredPassword = hasConfiguredSecretInput(existingPassword);
217
+ let password: unknown = existingPasswordText;
218
+ if (!hasConfiguredPassword) {
227
219
  await prompter.note(
228
220
  [
229
221
  "Enter the BlueBubbles server password.",
@@ -247,6 +239,8 @@ export const blueBubblesOnboardingAdapter: ChannelOnboardingAdapter = {
247
239
  validate: (value) => (String(value ?? "").trim() ? undefined : "Required"),
248
240
  });
249
241
  password = String(entered).trim();
242
+ } else if (!existingPasswordText) {
243
+ password = existingPassword;
250
244
  }
251
245
  }
252
246
 
@@ -277,42 +271,16 @@ export const blueBubblesOnboardingAdapter: ChannelOnboardingAdapter = {
277
271
  }
278
272
 
279
273
  // Apply config
280
- if (accountId === DEFAULT_ACCOUNT_ID) {
281
- next = {
282
- ...next,
283
- channels: {
284
- ...next.channels,
285
- bluebubbles: {
286
- ...next.channels?.bluebubbles,
287
- enabled: true,
288
- serverUrl,
289
- password,
290
- webhookPath,
291
- },
292
- },
293
- };
294
- } else {
295
- next = {
296
- ...next,
297
- channels: {
298
- ...next.channels,
299
- bluebubbles: {
300
- ...next.channels?.bluebubbles,
301
- enabled: true,
302
- accounts: {
303
- ...next.channels?.bluebubbles?.accounts,
304
- [accountId]: {
305
- ...next.channels?.bluebubbles?.accounts?.[accountId],
306
- enabled: next.channels?.bluebubbles?.accounts?.[accountId]?.enabled ?? true,
307
- serverUrl,
308
- password,
309
- webhookPath,
310
- },
311
- },
312
- },
313
- },
314
- };
315
- }
274
+ next = applyBlueBubblesConnectionConfig({
275
+ cfg: next,
276
+ accountId,
277
+ patch: {
278
+ serverUrl,
279
+ password,
280
+ webhookPath,
281
+ },
282
+ accountEnabled: "preserve-or-true",
283
+ });
316
284
 
317
285
  await prompter.note(
318
286
  [
package/src/probe.ts CHANGED
@@ -1,4 +1,5 @@
1
- import type { BaseProbeResult } from "openclaw/plugin-sdk";
1
+ import type { BaseProbeResult } from "openclaw/plugin-sdk/bluebubbles";
2
+ import { normalizeSecretInputString } from "./secret-input.js";
2
3
  import { buildBlueBubblesApiUrl, blueBubblesFetchWithTimeout } from "./types.js";
3
4
 
4
5
  export type BlueBubblesProbe = BaseProbeResult & {
@@ -35,8 +36,8 @@ export async function fetchBlueBubblesServerInfo(params: {
35
36
  accountId?: string;
36
37
  timeoutMs?: number;
37
38
  }): Promise<BlueBubblesServerInfo | null> {
38
- const baseUrl = params.baseUrl?.trim();
39
- const password = params.password?.trim();
39
+ const baseUrl = normalizeSecretInputString(params.baseUrl);
40
+ const password = normalizeSecretInputString(params.password);
40
41
  if (!baseUrl || !password) {
41
42
  return null;
42
43
  }
@@ -138,8 +139,8 @@ export async function probeBlueBubbles(params: {
138
139
  password?: string | null;
139
140
  timeoutMs?: number;
140
141
  }): Promise<BlueBubblesProbe> {
141
- const baseUrl = params.baseUrl?.trim();
142
- const password = params.password?.trim();
142
+ const baseUrl = normalizeSecretInputString(params.baseUrl);
143
+ const password = normalizeSecretInputString(params.password);
143
144
  if (!baseUrl) {
144
145
  return { ok: false, error: "serverUrl not configured" };
145
146
  }
package/src/reactions.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
1
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
2
2
  import { resolveBlueBubblesServerAccount } from "./account-resolve.js";
3
3
  import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
4
4
  import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
@@ -1,12 +1 @@
1
- export function resolveRequestUrl(input: RequestInfo | URL): string {
2
- if (typeof input === "string") {
3
- return input;
4
- }
5
- if (input instanceof URL) {
6
- return input.toString();
7
- }
8
- if (typeof input === "object" && input && "url" in input && typeof input.url === "string") {
9
- return input.url;
10
- }
11
- return String(input);
12
- }
1
+ export { resolveRequestUrl } from "openclaw/plugin-sdk/bluebubbles";
package/src/runtime.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
2
2
 
3
3
  let runtime: PluginRuntime | null = null;
4
4
  type LegacyRuntimeLogShape = { log?: (message: string) => void };
@@ -0,0 +1,13 @@
1
+ import {
2
+ buildSecretInputSchema,
3
+ hasConfiguredSecretInput,
4
+ normalizeResolvedSecretInputString,
5
+ normalizeSecretInputString,
6
+ } from "openclaw/plugin-sdk/bluebubbles";
7
+
8
+ export {
9
+ buildSecretInputSchema,
10
+ hasConfiguredSecretInput,
11
+ normalizeResolvedSecretInputString,
12
+ normalizeSecretInputString,
13
+ };
@@ -23,31 +23,43 @@ export function extractBlueBubblesMessageId(payload: unknown): string {
23
23
  if (!payload || typeof payload !== "object") {
24
24
  return "unknown";
25
25
  }
26
- const record = payload as Record<string, unknown>;
27
- const data =
28
- record.data && typeof record.data === "object"
29
- ? (record.data as Record<string, unknown>)
26
+
27
+ const asRecord = (value: unknown): Record<string, unknown> | null =>
28
+ value && typeof value === "object" && !Array.isArray(value)
29
+ ? (value as Record<string, unknown>)
30
30
  : null;
31
- const candidates = [
32
- record.messageId,
33
- record.messageGuid,
34
- record.message_guid,
35
- record.guid,
36
- record.id,
37
- data?.messageId,
38
- data?.messageGuid,
39
- data?.message_guid,
40
- data?.message_id,
41
- data?.guid,
42
- data?.id,
43
- ];
44
- for (const candidate of candidates) {
45
- if (typeof candidate === "string" && candidate.trim()) {
46
- return candidate.trim();
31
+
32
+ const record = payload as Record<string, unknown>;
33
+ const dataRecord = asRecord(record.data);
34
+ const resultRecord = asRecord(record.result);
35
+ const payloadRecord = asRecord(record.payload);
36
+ const messageRecord = asRecord(record.message);
37
+ const dataArrayFirst = Array.isArray(record.data) ? asRecord(record.data[0]) : null;
38
+
39
+ const roots = [record, dataRecord, resultRecord, payloadRecord, messageRecord, dataArrayFirst];
40
+
41
+ for (const root of roots) {
42
+ if (!root) {
43
+ continue;
47
44
  }
48
- if (typeof candidate === "number" && Number.isFinite(candidate)) {
49
- return String(candidate);
45
+ const candidates = [
46
+ root.message_id,
47
+ root.messageId,
48
+ root.messageGuid,
49
+ root.message_guid,
50
+ root.guid,
51
+ root.id,
52
+ root.uuid,
53
+ ];
54
+ for (const candidate of candidates) {
55
+ if (typeof candidate === "string" && candidate.trim()) {
56
+ return candidate.trim();
57
+ }
58
+ if (typeof candidate === "number" && Number.isFinite(candidate)) {
59
+ return String(candidate);
60
+ }
50
61
  }
51
62
  }
63
+
52
64
  return "unknown";
53
65
  }
package/src/send.test.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { PluginRuntime } from "openclaw/plugin-sdk";
1
+ import type { PluginRuntime } from "openclaw/plugin-sdk/bluebubbles";
2
2
  import { beforeEach, describe, expect, it, vi } from "vitest";
3
3
  import "./test-mocks.js";
4
4
  import { getCachedBlueBubblesPrivateApiStatus } from "./probe.js";
@@ -721,6 +721,30 @@ describe("send", () => {
721
721
  expect(result.messageId).toBe("msg-guid-789");
722
722
  });
723
723
 
724
+ it("extracts top-level message_id from response payload", async () => {
725
+ mockResolvedHandleTarget();
726
+ mockSendResponse({ message_id: "bb-msg-321" });
727
+
728
+ const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
729
+ serverUrl: "http://localhost:1234",
730
+ password: "test",
731
+ });
732
+
733
+ expect(result.messageId).toBe("bb-msg-321");
734
+ });
735
+
736
+ it("extracts nested result.message_id from response payload", async () => {
737
+ mockResolvedHandleTarget();
738
+ mockSendResponse({ result: { message_id: "bb-msg-654" } });
739
+
740
+ const result = await sendMessageBlueBubbles("+15551234567", "Hello", {
741
+ serverUrl: "http://localhost:1234",
742
+ password: "test",
743
+ });
744
+
745
+ expect(result.messageId).toBe("bb-msg-654");
746
+ });
747
+
724
748
  it("resolves credentials from config", async () => {
725
749
  mockResolvedHandleTarget();
726
750
  mockSendResponse({ data: { guid: "msg-123" } });
package/src/send.ts CHANGED
@@ -1,12 +1,13 @@
1
1
  import crypto from "node:crypto";
2
- import type { OpenClawConfig } from "openclaw/plugin-sdk";
3
- import { stripMarkdown } from "openclaw/plugin-sdk";
2
+ import type { OpenClawConfig } from "openclaw/plugin-sdk/bluebubbles";
3
+ import { stripMarkdown } from "openclaw/plugin-sdk/bluebubbles";
4
4
  import { resolveBlueBubblesAccount } from "./accounts.js";
5
5
  import {
6
6
  getCachedBlueBubblesPrivateApiStatus,
7
7
  isBlueBubblesPrivateApiStatusEnabled,
8
8
  } from "./probe.js";
9
9
  import { warnBlueBubbles } from "./runtime.js";
10
+ import { normalizeSecretInputString } from "./secret-input.js";
10
11
  import { extractBlueBubblesMessageId, resolveBlueBubblesSendTarget } from "./send-helpers.js";
11
12
  import { extractHandleFromChatGuid, normalizeBlueBubblesHandle } from "./targets.js";
12
13
  import {
@@ -107,6 +108,19 @@ function resolvePrivateApiDecision(params: {
107
108
  };
108
109
  }
109
110
 
111
+ async function parseBlueBubblesMessageResponse(res: Response): Promise<BlueBubblesSendResult> {
112
+ const body = await res.text();
113
+ if (!body) {
114
+ return { messageId: "ok" };
115
+ }
116
+ try {
117
+ const parsed = JSON.parse(body) as unknown;
118
+ return { messageId: extractBlueBubblesMessageId(parsed) };
119
+ } catch {
120
+ return { messageId: "ok" };
121
+ }
122
+ }
123
+
110
124
  type BlueBubblesChatRecord = Record<string, unknown>;
111
125
 
112
126
  function extractChatGuid(chat: BlueBubblesChatRecord): string | null {
@@ -341,16 +355,7 @@ async function createNewChatWithMessage(params: {
341
355
  }
342
356
  throw new Error(`BlueBubbles create chat failed (${res.status}): ${errorText || "unknown"}`);
343
357
  }
344
- const body = await res.text();
345
- if (!body) {
346
- return { messageId: "ok" };
347
- }
348
- try {
349
- const parsed = JSON.parse(body) as unknown;
350
- return { messageId: extractBlueBubblesMessageId(parsed) };
351
- } catch {
352
- return { messageId: "ok" };
353
- }
358
+ return parseBlueBubblesMessageResponse(res);
354
359
  }
355
360
 
356
361
  export async function sendMessageBlueBubbles(
@@ -372,8 +377,12 @@ export async function sendMessageBlueBubbles(
372
377
  cfg: opts.cfg ?? {},
373
378
  accountId: opts.accountId,
374
379
  });
375
- const baseUrl = opts.serverUrl?.trim() || account.config.serverUrl?.trim();
376
- const password = opts.password?.trim() || account.config.password?.trim();
380
+ const baseUrl =
381
+ normalizeSecretInputString(opts.serverUrl) ||
382
+ normalizeSecretInputString(account.config.serverUrl);
383
+ const password =
384
+ normalizeSecretInputString(opts.password) ||
385
+ normalizeSecretInputString(account.config.password);
377
386
  if (!baseUrl) {
378
387
  throw new Error("BlueBubbles serverUrl is required");
379
388
  }
@@ -459,14 +468,5 @@ export async function sendMessageBlueBubbles(
459
468
  const errorText = await res.text();
460
469
  throw new Error(`BlueBubbles send failed (${res.status}): ${errorText || "unknown"}`);
461
470
  }
462
- const body = await res.text();
463
- if (!body) {
464
- return { messageId: "ok" };
465
- }
466
- try {
467
- const parsed = JSON.parse(body) as unknown;
468
- return { messageId: extractBlueBubblesMessageId(parsed) };
469
- } catch {
470
- return { messageId: "ok" };
471
- }
471
+ return parseBlueBubblesMessageResponse(res);
472
472
  }
package/src/targets.ts CHANGED
@@ -2,9 +2,10 @@ import {
2
2
  isAllowedParsedChatSender,
3
3
  parseChatAllowTargetPrefixes,
4
4
  parseChatTargetPrefixesOrThrow,
5
+ type ParsedChatTarget,
5
6
  resolveServicePrefixedAllowTarget,
6
7
  resolveServicePrefixedTarget,
7
- } from "openclaw/plugin-sdk";
8
+ } from "openclaw/plugin-sdk/bluebubbles";
8
9
 
9
10
  export type BlueBubblesService = "imessage" | "sms" | "auto";
10
11
 
@@ -14,11 +15,7 @@ export type BlueBubblesTarget =
14
15
  | { kind: "chat_identifier"; chatIdentifier: string }
15
16
  | { kind: "handle"; to: string; service: BlueBubblesService };
16
17
 
17
- export type BlueBubblesAllowTarget =
18
- | { kind: "chat_id"; chatId: number }
19
- | { kind: "chat_guid"; chatGuid: string }
20
- | { kind: "chat_identifier"; chatIdentifier: string }
21
- | { kind: "handle"; handle: string };
18
+ export type BlueBubblesAllowTarget = ParsedChatTarget | { kind: "handle"; handle: string };
22
19
 
23
20
  const CHAT_ID_PREFIXES = ["chat_id:", "chatid:", "chat:"];
24
21
  const CHAT_GUID_PREFIXES = ["chat_guid:", "chatguid:", "guid:"];
package/src/types.ts CHANGED
@@ -1,6 +1,6 @@
1
- import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
1
+ import type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/bluebubbles";
2
2
 
3
- export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk";
3
+ export type { DmPolicy, GroupPolicy } from "openclaw/plugin-sdk/bluebubbles";
4
4
 
5
5
  export type BlueBubblesGroupConfig = {
6
6
  /** If true, only respond in this group when mentioned. */