@spinabot/brigade 1.7.0 → 1.9.0
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.
- package/README.md +19 -0
- package/dist/agents/agent-loop.d.ts +13 -0
- package/dist/agents/agent-loop.d.ts.map +1 -1
- package/dist/agents/agent-loop.js +5 -0
- package/dist/agents/agent-loop.js.map +1 -1
- package/dist/agents/channels/access-control/group-tool-policy.d.ts +73 -0
- package/dist/agents/channels/access-control/group-tool-policy.d.ts.map +1 -0
- package/dist/agents/channels/access-control/group-tool-policy.js +193 -0
- package/dist/agents/channels/access-control/group-tool-policy.js.map +1 -0
- package/dist/agents/channels/access-control/index.d.ts +1 -0
- package/dist/agents/channels/access-control/index.d.ts.map +1 -1
- package/dist/agents/channels/access-control/index.js +1 -0
- package/dist/agents/channels/access-control/index.js.map +1 -1
- package/dist/agents/channels/bluebubbles/account-config.d.ts +253 -0
- package/dist/agents/channels/bluebubbles/account-config.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/account-config.js +486 -0
- package/dist/agents/channels/bluebubbles/account-config.js.map +1 -0
- package/dist/agents/channels/bluebubbles/account-registry.d.ts +33 -0
- package/dist/agents/channels/bluebubbles/account-registry.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/account-registry.js +46 -0
- package/dist/agents/channels/bluebubbles/account-registry.js.map +1 -0
- package/dist/agents/channels/bluebubbles/adapter.d.ts +45 -0
- package/dist/agents/channels/bluebubbles/adapter.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/adapter.js +366 -0
- package/dist/agents/channels/bluebubbles/adapter.js.map +1 -0
- package/dist/agents/channels/bluebubbles/catchup-cursor.d.ts +52 -0
- package/dist/agents/channels/bluebubbles/catchup-cursor.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/catchup-cursor.js +118 -0
- package/dist/agents/channels/bluebubbles/catchup-cursor.js.map +1 -0
- package/dist/agents/channels/bluebubbles/catchup.d.ts +114 -0
- package/dist/agents/channels/bluebubbles/catchup.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/catchup.js +220 -0
- package/dist/agents/channels/bluebubbles/catchup.js.map +1 -0
- package/dist/agents/channels/bluebubbles/chat.d.ts +75 -0
- package/dist/agents/channels/bluebubbles/chat.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/chat.js +182 -0
- package/dist/agents/channels/bluebubbles/chat.js.map +1 -0
- package/dist/agents/channels/bluebubbles/connection.d.ts +95 -0
- package/dist/agents/channels/bluebubbles/connection.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/connection.js +413 -0
- package/dist/agents/channels/bluebubbles/connection.js.map +1 -0
- package/dist/agents/channels/bluebubbles/contact-names.d.ts +79 -0
- package/dist/agents/channels/bluebubbles/contact-names.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/contact-names.js +188 -0
- package/dist/agents/channels/bluebubbles/contact-names.js.map +1 -0
- package/dist/agents/channels/bluebubbles/debounce.d.ts +78 -0
- package/dist/agents/channels/bluebubbles/debounce.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/debounce.js +173 -0
- package/dist/agents/channels/bluebubbles/debounce.js.map +1 -0
- package/dist/agents/channels/bluebubbles/dedupe.d.ts +25 -0
- package/dist/agents/channels/bluebubbles/dedupe.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/dedupe.js +35 -0
- package/dist/agents/channels/bluebubbles/dedupe.js.map +1 -0
- package/dist/agents/channels/bluebubbles/effects.d.ts +17 -0
- package/dist/agents/channels/bluebubbles/effects.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/effects.js +57 -0
- package/dist/agents/channels/bluebubbles/effects.js.map +1 -0
- package/dist/agents/channels/bluebubbles/history.d.ts +56 -0
- package/dist/agents/channels/bluebubbles/history.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/history.js +140 -0
- package/dist/agents/channels/bluebubbles/history.js.map +1 -0
- package/dist/agents/channels/bluebubbles/index.d.ts +18 -0
- package/dist/agents/channels/bluebubbles/index.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/index.js +18 -0
- package/dist/agents/channels/bluebubbles/index.js.map +1 -0
- package/dist/agents/channels/bluebubbles/media.d.ts +104 -0
- package/dist/agents/channels/bluebubbles/media.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/media.js +222 -0
- package/dist/agents/channels/bluebubbles/media.js.map +1 -0
- package/dist/agents/channels/bluebubbles/messaging.d.ts +13 -0
- package/dist/agents/channels/bluebubbles/messaging.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/messaging.js +36 -0
- package/dist/agents/channels/bluebubbles/messaging.js.map +1 -0
- package/dist/agents/channels/bluebubbles/module.d.ts +24 -0
- package/dist/agents/channels/bluebubbles/module.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/module.js +52 -0
- package/dist/agents/channels/bluebubbles/module.js.map +1 -0
- package/dist/agents/channels/bluebubbles/normalize.d.ts +111 -0
- package/dist/agents/channels/bluebubbles/normalize.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/normalize.js +239 -0
- package/dist/agents/channels/bluebubbles/normalize.js.map +1 -0
- package/dist/agents/channels/bluebubbles/plugin.d.ts +46 -0
- package/dist/agents/channels/bluebubbles/plugin.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/plugin.js +242 -0
- package/dist/agents/channels/bluebubbles/plugin.js.map +1 -0
- package/dist/agents/channels/bluebubbles/probe.d.ts +79 -0
- package/dist/agents/channels/bluebubbles/probe.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/probe.js +138 -0
- package/dist/agents/channels/bluebubbles/probe.js.map +1 -0
- package/dist/agents/channels/bluebubbles/reactions.d.ts +46 -0
- package/dist/agents/channels/bluebubbles/reactions.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/reactions.js +207 -0
- package/dist/agents/channels/bluebubbles/reactions.js.map +1 -0
- package/dist/agents/channels/bluebubbles/send.d.ts +132 -0
- package/dist/agents/channels/bluebubbles/send.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/send.js +327 -0
- package/dist/agents/channels/bluebubbles/send.js.map +1 -0
- package/dist/agents/channels/bluebubbles/status-issues.d.ts +57 -0
- package/dist/agents/channels/bluebubbles/status-issues.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/status-issues.js +93 -0
- package/dist/agents/channels/bluebubbles/status-issues.js.map +1 -0
- package/dist/agents/channels/bluebubbles/types.d.ts +69 -0
- package/dist/agents/channels/bluebubbles/types.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/types.js +118 -0
- package/dist/agents/channels/bluebubbles/types.js.map +1 -0
- package/dist/agents/channels/bluebubbles/webhook.d.ts +97 -0
- package/dist/agents/channels/bluebubbles/webhook.d.ts.map +1 -0
- package/dist/agents/channels/bluebubbles/webhook.js +249 -0
- package/dist/agents/channels/bluebubbles/webhook.js.map +1 -0
- package/dist/agents/channels/bundled-channel-metas.d.ts +13 -0
- package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
- package/dist/agents/channels/bundled-channel-metas.js +33 -0
- package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
- package/dist/agents/channels/imessage/account-config.d.ts +178 -0
- package/dist/agents/channels/imessage/account-config.d.ts.map +1 -0
- package/dist/agents/channels/imessage/account-config.js +371 -0
- package/dist/agents/channels/imessage/account-config.js.map +1 -0
- package/dist/agents/channels/imessage/adapter.d.ts +36 -0
- package/dist/agents/channels/imessage/adapter.d.ts.map +1 -0
- package/dist/agents/channels/imessage/adapter.js +286 -0
- package/dist/agents/channels/imessage/adapter.js.map +1 -0
- package/dist/agents/channels/imessage/client.d.ts +99 -0
- package/dist/agents/channels/imessage/client.d.ts.map +1 -0
- package/dist/agents/channels/imessage/client.js +231 -0
- package/dist/agents/channels/imessage/client.js.map +1 -0
- package/dist/agents/channels/imessage/connection.d.ts +101 -0
- package/dist/agents/channels/imessage/connection.d.ts.map +1 -0
- package/dist/agents/channels/imessage/connection.js +367 -0
- package/dist/agents/channels/imessage/connection.js.map +1 -0
- package/dist/agents/channels/imessage/format.d.ts +45 -0
- package/dist/agents/channels/imessage/format.d.ts.map +1 -0
- package/dist/agents/channels/imessage/format.js +170 -0
- package/dist/agents/channels/imessage/format.js.map +1 -0
- package/dist/agents/channels/imessage/history.d.ts +49 -0
- package/dist/agents/channels/imessage/history.d.ts.map +1 -0
- package/dist/agents/channels/imessage/history.js +74 -0
- package/dist/agents/channels/imessage/history.js.map +1 -0
- package/dist/agents/channels/imessage/index.d.ts +25 -0
- package/dist/agents/channels/imessage/index.d.ts.map +1 -0
- package/dist/agents/channels/imessage/index.js +25 -0
- package/dist/agents/channels/imessage/index.js.map +1 -0
- package/dist/agents/channels/imessage/media.d.ts +92 -0
- package/dist/agents/channels/imessage/media.d.ts.map +1 -0
- package/dist/agents/channels/imessage/media.js +196 -0
- package/dist/agents/channels/imessage/media.js.map +1 -0
- package/dist/agents/channels/imessage/messaging.d.ts +14 -0
- package/dist/agents/channels/imessage/messaging.d.ts.map +1 -0
- package/dist/agents/channels/imessage/messaging.js +37 -0
- package/dist/agents/channels/imessage/messaging.js.map +1 -0
- package/dist/agents/channels/imessage/module.d.ts +16 -0
- package/dist/agents/channels/imessage/module.d.ts.map +1 -0
- package/dist/agents/channels/imessage/module.js +23 -0
- package/dist/agents/channels/imessage/module.js.map +1 -0
- package/dist/agents/channels/imessage/monitor.d.ts +207 -0
- package/dist/agents/channels/imessage/monitor.d.ts.map +1 -0
- package/dist/agents/channels/imessage/monitor.js +504 -0
- package/dist/agents/channels/imessage/monitor.js.map +1 -0
- package/dist/agents/channels/imessage/plugin.d.ts +53 -0
- package/dist/agents/channels/imessage/plugin.d.ts.map +1 -0
- package/dist/agents/channels/imessage/plugin.js +215 -0
- package/dist/agents/channels/imessage/plugin.js.map +1 -0
- package/dist/agents/channels/imessage/probe.d.ts +68 -0
- package/dist/agents/channels/imessage/probe.d.ts.map +1 -0
- package/dist/agents/channels/imessage/probe.js +134 -0
- package/dist/agents/channels/imessage/probe.js.map +1 -0
- package/dist/agents/channels/imessage/remote-attachments.d.ts +61 -0
- package/dist/agents/channels/imessage/remote-attachments.d.ts.map +1 -0
- package/dist/agents/channels/imessage/remote-attachments.js +131 -0
- package/dist/agents/channels/imessage/remote-attachments.js.map +1 -0
- package/dist/agents/channels/imessage/send.d.ts +61 -0
- package/dist/agents/channels/imessage/send.d.ts.map +1 -0
- package/dist/agents/channels/imessage/send.js +108 -0
- package/dist/agents/channels/imessage/send.js.map +1 -0
- package/dist/agents/channels/imessage/targets.d.ts +91 -0
- package/dist/agents/channels/imessage/targets.d.ts.map +1 -0
- package/dist/agents/channels/imessage/targets.js +245 -0
- package/dist/agents/channels/imessage/targets.js.map +1 -0
- package/dist/agents/channels/imessage/watch-error.d.ts +23 -0
- package/dist/agents/channels/imessage/watch-error.d.ts.map +1 -0
- package/dist/agents/channels/imessage/watch-error.js +69 -0
- package/dist/agents/channels/imessage/watch-error.js.map +1 -0
- package/dist/agents/channels/inbound-pipeline.d.ts +11 -0
- package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
- package/dist/agents/channels/inbound-pipeline.js +20 -1
- package/dist/agents/channels/inbound-pipeline.js.map +1 -1
- package/dist/agents/channels/manager.d.ts +9 -0
- package/dist/agents/channels/manager.d.ts.map +1 -1
- package/dist/agents/channels/manager.js.map +1 -1
- package/dist/agents/channels/sdk.d.ts +4 -0
- package/dist/agents/channels/sdk.d.ts.map +1 -1
- package/dist/agents/channels/sdk.js +4 -0
- package/dist/agents/channels/sdk.js.map +1 -1
- package/dist/agents/extensions/modules/index.d.ts.map +1 -1
- package/dist/agents/extensions/modules/index.js +12 -0
- package/dist/agents/extensions/modules/index.js.map +1 -1
- package/dist/agents/session-wiring.d.ts +31 -0
- package/dist/agents/session-wiring.d.ts.map +1 -1
- package/dist/agents/session-wiring.js +45 -2
- package/dist/agents/session-wiring.js.map +1 -1
- package/dist/agents/tools/bluebubbles-action-tool.d.ts +66 -0
- package/dist/agents/tools/bluebubbles-action-tool.d.ts.map +1 -0
- package/dist/agents/tools/bluebubbles-action-tool.js +234 -0
- package/dist/agents/tools/bluebubbles-action-tool.js.map +1 -0
- package/dist/agents/tools/registry.d.ts.map +1 -1
- package/dist/agents/tools/registry.js +18 -0
- package/dist/agents/tools/registry.js.map +1 -1
- package/dist/buildstamp.json +1 -1
- package/dist/cli/commands/expose.d.ts +40 -0
- package/dist/cli/commands/expose.d.ts.map +1 -0
- package/dist/cli/commands/expose.js +200 -0
- package/dist/cli/commands/expose.js.map +1 -0
- package/dist/cli/program/build-program.d.ts.map +1 -1
- package/dist/cli/program/build-program.js +61 -0
- package/dist/cli/program/build-program.js.map +1 -1
- package/dist/config/io.d.ts +41 -0
- package/dist/config/io.d.ts.map +1 -1
- package/dist/config/io.js.map +1 -1
- package/dist/core/server.d.ts.map +1 -1
- package/dist/core/server.js +48 -2
- package/dist/core/server.js.map +1 -1
- package/dist/core/tunnel/auth-proxy.d.ts +55 -0
- package/dist/core/tunnel/auth-proxy.d.ts.map +1 -0
- package/dist/core/tunnel/auth-proxy.js +179 -0
- package/dist/core/tunnel/auth-proxy.js.map +1 -0
- package/dist/core/tunnel/manager.d.ts +42 -0
- package/dist/core/tunnel/manager.d.ts.map +1 -0
- package/dist/core/tunnel/manager.js +102 -0
- package/dist/core/tunnel/manager.js.map +1 -0
- package/dist/core/tunnel/providers/bore.d.ts +18 -0
- package/dist/core/tunnel/providers/bore.d.ts.map +1 -0
- package/dist/core/tunnel/providers/bore.js +117 -0
- package/dist/core/tunnel/providers/bore.js.map +1 -0
- package/dist/core/tunnel/providers/cloudflared.d.ts +24 -0
- package/dist/core/tunnel/providers/cloudflared.d.ts.map +1 -0
- package/dist/core/tunnel/providers/cloudflared.js +179 -0
- package/dist/core/tunnel/providers/cloudflared.js.map +1 -0
- package/dist/core/tunnel/providers/custom.d.ts +21 -0
- package/dist/core/tunnel/providers/custom.d.ts.map +1 -0
- package/dist/core/tunnel/providers/custom.js +124 -0
- package/dist/core/tunnel/providers/custom.js.map +1 -0
- package/dist/core/tunnel/registry.d.ts +15 -0
- package/dist/core/tunnel/registry.d.ts.map +1 -0
- package/dist/core/tunnel/registry.js +26 -0
- package/dist/core/tunnel/registry.js.map +1 -0
- package/dist/core/tunnel/state.d.ts +39 -0
- package/dist/core/tunnel/state.d.ts.map +1 -0
- package/dist/core/tunnel/state.js +57 -0
- package/dist/core/tunnel/state.js.map +1 -0
- package/dist/core/tunnel/types.d.ts +61 -0
- package/dist/core/tunnel/types.d.ts.map +1 -0
- package/dist/core/tunnel/types.js +20 -0
- package/dist/core/tunnel/types.js.map +1 -0
- package/dist/infra/net/fetch-guard.d.ts +24 -2
- package/dist/infra/net/fetch-guard.d.ts.map +1 -1
- package/dist/infra/net/fetch-guard.js +78 -31
- package/dist/infra/net/fetch-guard.js.map +1 -1
- package/package.json +5 -1
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueBubbles catch-up backfill.
|
|
3
|
+
*
|
|
4
|
+
* BlueBubbles delivers inbound via webhook POST. When Brigade is down (restart,
|
|
5
|
+
* crash, upgrade) the server's webhook fire fails and is NOT retried, so any
|
|
6
|
+
* message that arrived during the outage is lost. On (re)connect, catch-up
|
|
7
|
+
* queries the server for messages delivered in a bounded recent window
|
|
8
|
+
* (`POST /api/v1/message/query`, `after: <sinceMs>`, capped by `limit`) and
|
|
9
|
+
* re-feeds each one through the SAME path a live webhook event takes —
|
|
10
|
+
* normalize → dedupe → dispatch.
|
|
11
|
+
*
|
|
12
|
+
* The dedupe is what makes this safe: a message already delivered live (or in a
|
|
13
|
+
* previous catch-up pass) is claimed in the connection's dedupe cache, so the
|
|
14
|
+
* replay is dropped there and the agent NEVER replies twice. Catch-up therefore
|
|
15
|
+
* recovers ONLY the genuinely-missed messages.
|
|
16
|
+
*
|
|
17
|
+
* `fetch` is INJECTABLE (the test seam) so the whole query → replay path runs
|
|
18
|
+
* with no live server. Never throws — a query failure logs and leaves the live
|
|
19
|
+
* webhook path untouched.
|
|
20
|
+
*/
|
|
21
|
+
import { type BlueBubblesCursorStore } from "./catchup-cursor.js";
|
|
22
|
+
import { type FetchLike } from "./types.js";
|
|
23
|
+
/** Default lookback window when running catch-up (30 min). */
|
|
24
|
+
export declare const BLUEBUBBLES_CATCHUP_DEFAULT_LOOKBACK_MS: number;
|
|
25
|
+
/** Default per-run message cap. */
|
|
26
|
+
export declare const BLUEBUBBLES_CATCHUP_DEFAULT_LIMIT = 50;
|
|
27
|
+
/** Hard ceiling on the per-run cap (defends against an unbounded backlog). */
|
|
28
|
+
export declare const BLUEBUBBLES_CATCHUP_MAX_LIMIT = 500;
|
|
29
|
+
/** Default per-message consecutive-failure ceiling before catch-up gives up + force-advances past it. */
|
|
30
|
+
export declare const BLUEBUBBLES_CATCHUP_DEFAULT_MAX_FAILURE_RETRIES = 10;
|
|
31
|
+
/** Hard ceiling on `maxFailureRetries`. */
|
|
32
|
+
export declare const BLUEBUBBLES_CATCHUP_MAX_FAILURE_RETRIES = 1000;
|
|
33
|
+
/** Tunables for one catch-up run. */
|
|
34
|
+
export interface BlueBubblesCatchupConfig {
|
|
35
|
+
/** Disable catch-up entirely. */
|
|
36
|
+
enabled?: boolean;
|
|
37
|
+
/** How far back to look on the FIRST run (no cursor yet) (ms). Also the legacy fixed-lookback when no accountId is given. */
|
|
38
|
+
lookbackMs?: number;
|
|
39
|
+
/** Max messages to fetch in one run. */
|
|
40
|
+
limit?: number;
|
|
41
|
+
/**
|
|
42
|
+
* Per-message retry ceiling. After this many consecutive failed replays of the
|
|
43
|
+
* same GUID, catch-up logs + force-advances the cursor past it (instead of
|
|
44
|
+
* holding indefinitely). Defaults to 10; clamped to [1, 1000]. Only meaningful
|
|
45
|
+
* when a persisted cursor is in use (an `accountId` was provided).
|
|
46
|
+
*/
|
|
47
|
+
maxFailureRetries?: number;
|
|
48
|
+
}
|
|
49
|
+
/** Args for one catch-up run. */
|
|
50
|
+
export interface RunBlueBubblesCatchupArgs {
|
|
51
|
+
serverUrl: string;
|
|
52
|
+
password: string;
|
|
53
|
+
config?: BlueBubblesCatchupConfig;
|
|
54
|
+
timeoutMs?: number;
|
|
55
|
+
/**
|
|
56
|
+
* Account id — enables the PERSISTED cursor (`lastSeenMs` + give-up retries).
|
|
57
|
+
* When omitted, catch-up falls back to the legacy fixed-lookback window with no
|
|
58
|
+
* persistence (unchanged behaviour).
|
|
59
|
+
*/
|
|
60
|
+
accountId?: string;
|
|
61
|
+
/** TEST SEAM — inject a mock fetch. */
|
|
62
|
+
fetchImpl?: FetchLike;
|
|
63
|
+
/** Allow private/LAN/loopback hosts through the SSRF guard (default TRUE for BlueBubbles). */
|
|
64
|
+
allowPrivateNetwork?: boolean;
|
|
65
|
+
/**
|
|
66
|
+
* Re-feed one raw message record through the connection's live webhook path
|
|
67
|
+
* (normalize → dedupe → dispatch). The connection wires this to the SAME
|
|
68
|
+
* `feedWebhookEvent` a real webhook uses, so dedupe drops any already-seen
|
|
69
|
+
* message. THROWING from this marks the record as a failed replay (the cursor
|
|
70
|
+
* holds before it, and after `maxFailureRetries` it's given up + skipped).
|
|
71
|
+
*/
|
|
72
|
+
feedRecord: (record: Record<string, unknown>) => void;
|
|
73
|
+
/** Optional logger. */
|
|
74
|
+
log?: (msg: string) => void;
|
|
75
|
+
/** Clock seam (tests). */
|
|
76
|
+
now?: () => number;
|
|
77
|
+
/** TEST SEAM — inject the cursor store (default: filesystem under the channel state dir). */
|
|
78
|
+
cursorStore?: BlueBubblesCursorStore;
|
|
79
|
+
}
|
|
80
|
+
/** Outcome of a catch-up run (for logging + tests). */
|
|
81
|
+
export interface BlueBubblesCatchupSummary {
|
|
82
|
+
/** True when the server query returned a recognised response. */
|
|
83
|
+
querySucceeded: boolean;
|
|
84
|
+
/** How many records the query returned. */
|
|
85
|
+
fetched: number;
|
|
86
|
+
/** How many were re-fed into the inbound path (pre-dedupe). */
|
|
87
|
+
replayed: number;
|
|
88
|
+
/** How many failed records crossed `maxFailureRetries` on THIS run (fresh give-ups). */
|
|
89
|
+
givenUp: number;
|
|
90
|
+
/** How many records were skipped because they were ALREADY given up in a prior run. */
|
|
91
|
+
skippedGivenUp: number;
|
|
92
|
+
/** How many records' replay threw on this run. */
|
|
93
|
+
failed: number;
|
|
94
|
+
/** The cursor before this run (epoch ms), or null on first run / no persistence. */
|
|
95
|
+
cursorBefore: number | null;
|
|
96
|
+
/** The cursor after this run (epoch ms). */
|
|
97
|
+
cursorAfter: number;
|
|
98
|
+
/** The `after` timestamp the query used (epoch ms). */
|
|
99
|
+
windowStartMs: number;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Run one bounded catch-up pass.
|
|
103
|
+
*
|
|
104
|
+
* With an `accountId`, queries strictly AFTER the persisted cursor (`lastSeenMs`),
|
|
105
|
+
* re-feeds each record through `feedRecord` (the connection's normalize+dedupe
|
|
106
|
+
* path), then advances the cursor: to "now" on a clean pass, held just before the
|
|
107
|
+
* earliest still-retrying failure, force-advanced past any GUID that has failed
|
|
108
|
+
* `maxFailureRetries` times, or to the page boundary when the fetch hit the
|
|
109
|
+
* per-run limit (so a long backlog drains across runs instead of stranding the
|
|
110
|
+
* tail). Without an `accountId`, falls back to the legacy fixed-lookback window
|
|
111
|
+
* with no persistence. Returns a summary; never throws.
|
|
112
|
+
*/
|
|
113
|
+
export declare function runBlueBubblesCatchup(args: RunBlueBubblesCatchupArgs): Promise<BlueBubblesCatchupSummary>;
|
|
114
|
+
//# sourceMappingURL=catchup.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catchup.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/bluebubbles/catchup.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,OAAO,EAKN,KAAK,sBAAsB,EAC3B,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAuD,KAAK,SAAS,EAAE,MAAM,YAAY,CAAC;AAEjG,8DAA8D;AAC9D,eAAO,MAAM,uCAAuC,QAAiB,CAAC;AACtE,mCAAmC;AACnC,eAAO,MAAM,iCAAiC,KAAK,CAAC;AACpD,8EAA8E;AAC9E,eAAO,MAAM,6BAA6B,MAAM,CAAC;AACjD,yGAAyG;AACzG,eAAO,MAAM,+CAA+C,KAAK,CAAC;AAClE,2CAA2C;AAC3C,eAAO,MAAM,uCAAuC,OAAQ,CAAC;AAE7D,qCAAqC;AACrC,MAAM,WAAW,wBAAwB;IACxC,iCAAiC;IACjC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,6HAA6H;IAC7H,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,iBAAiB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,iCAAiC;AACjC,MAAM,WAAW,yBAAyB;IACzC,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB;;;;OAIG;IACH,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,uCAAuC;IACvC,SAAS,CAAC,EAAE,SAAS,CAAC;IACtB,8FAA8F;IAC9F,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;;;OAMG;IACH,UAAU,EAAE,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IACtD,uBAAuB;IACvB,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5B,0BAA0B;IAC1B,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;IACnB,6FAA6F;IAC7F,WAAW,CAAC,EAAE,sBAAsB,CAAC;CACrC;AAED,uDAAuD;AACvD,MAAM,WAAW,yBAAyB;IACzC,iEAAiE;IACjE,cAAc,EAAE,OAAO,CAAC;IACxB,2CAA2C;IAC3C,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,QAAQ,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,OAAO,EAAE,MAAM,CAAC;IAChB,uFAAuF;IACvF,cAAc,EAAE,MAAM,CAAC;IACvB,kDAAkD;IAClD,MAAM,EAAE,MAAM,CAAC;IACf,oFAAoF;IACpF,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,4CAA4C;IAC5C,WAAW,EAAE,MAAM,CAAC;IACpB,uDAAuD;IACvD,aAAa,EAAE,MAAM,CAAC;CACtB;AAiCD;;;;;;;;;;;GAWG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,yBAAyB,GAAG,OAAO,CAAC,yBAAyB,CAAC,CA8J/G"}
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueBubbles catch-up backfill.
|
|
3
|
+
*
|
|
4
|
+
* BlueBubbles delivers inbound via webhook POST. When Brigade is down (restart,
|
|
5
|
+
* crash, upgrade) the server's webhook fire fails and is NOT retried, so any
|
|
6
|
+
* message that arrived during the outage is lost. On (re)connect, catch-up
|
|
7
|
+
* queries the server for messages delivered in a bounded recent window
|
|
8
|
+
* (`POST /api/v1/message/query`, `after: <sinceMs>`, capped by `limit`) and
|
|
9
|
+
* re-feeds each one through the SAME path a live webhook event takes —
|
|
10
|
+
* normalize → dedupe → dispatch.
|
|
11
|
+
*
|
|
12
|
+
* The dedupe is what makes this safe: a message already delivered live (or in a
|
|
13
|
+
* previous catch-up pass) is claimed in the connection's dedupe cache, so the
|
|
14
|
+
* replay is dropped there and the agent NEVER replies twice. Catch-up therefore
|
|
15
|
+
* recovers ONLY the genuinely-missed messages.
|
|
16
|
+
*
|
|
17
|
+
* `fetch` is INJECTABLE (the test seam) so the whole query → replay path runs
|
|
18
|
+
* with no live server. Never throws — a query failure logs and leaves the live
|
|
19
|
+
* webhook path untouched.
|
|
20
|
+
*/
|
|
21
|
+
import { capFailureRetriesMap, filesystemCursorStore, BLUEBUBBLES_MAX_FAILURE_RETRY_MAP_SIZE, } from "./catchup-cursor.js";
|
|
22
|
+
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl } from "./types.js";
|
|
23
|
+
/** Default lookback window when running catch-up (30 min). */
|
|
24
|
+
export const BLUEBUBBLES_CATCHUP_DEFAULT_LOOKBACK_MS = 30 * 60 * 1000;
|
|
25
|
+
/** Default per-run message cap. */
|
|
26
|
+
export const BLUEBUBBLES_CATCHUP_DEFAULT_LIMIT = 50;
|
|
27
|
+
/** Hard ceiling on the per-run cap (defends against an unbounded backlog). */
|
|
28
|
+
export const BLUEBUBBLES_CATCHUP_MAX_LIMIT = 500;
|
|
29
|
+
/** Default per-message consecutive-failure ceiling before catch-up gives up + force-advances past it. */
|
|
30
|
+
export const BLUEBUBBLES_CATCHUP_DEFAULT_MAX_FAILURE_RETRIES = 10;
|
|
31
|
+
/** Hard ceiling on `maxFailureRetries`. */
|
|
32
|
+
export const BLUEBUBBLES_CATCHUP_MAX_FAILURE_RETRIES = 1_000;
|
|
33
|
+
/** Clamp the per-run limit into `[1, MAX]`. */
|
|
34
|
+
function clampLimit(raw) {
|
|
35
|
+
const n = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : BLUEBUBBLES_CATCHUP_DEFAULT_LIMIT;
|
|
36
|
+
return Math.min(Math.max(n, 1), BLUEBUBBLES_CATCHUP_MAX_LIMIT);
|
|
37
|
+
}
|
|
38
|
+
/** Clamp `maxFailureRetries` into `[1, MAX]`. */
|
|
39
|
+
function clampMaxFailureRetries(raw) {
|
|
40
|
+
const n = typeof raw === "number" && Number.isFinite(raw) ? Math.floor(raw) : BLUEBUBBLES_CATCHUP_DEFAULT_MAX_FAILURE_RETRIES;
|
|
41
|
+
return Math.min(Math.max(n, 1), BLUEBUBBLES_CATCHUP_MAX_FAILURE_RETRIES);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Read a record's timestamp (epoch ms) across camel/snake shapes; 0 when absent.
|
|
45
|
+
* BlueBubbles `dateCreated` is already epoch ms, so the value is taken raw (the
|
|
46
|
+
* cursor compares it against other `dateCreated` values + `now`).
|
|
47
|
+
*/
|
|
48
|
+
function recordTimestampMs(rec) {
|
|
49
|
+
for (const k of ["dateCreated", "date_created", "date", "timestamp"]) {
|
|
50
|
+
const v = rec[k];
|
|
51
|
+
if (typeof v === "number" && Number.isFinite(v))
|
|
52
|
+
return v;
|
|
53
|
+
}
|
|
54
|
+
return 0;
|
|
55
|
+
}
|
|
56
|
+
/** Read a record's GUID (the retry-map key), or "" when absent. */
|
|
57
|
+
function recordGuid(rec) {
|
|
58
|
+
const g = rec.guid ?? rec.messageGuid;
|
|
59
|
+
return typeof g === "string" ? g.trim() : "";
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Run one bounded catch-up pass.
|
|
63
|
+
*
|
|
64
|
+
* With an `accountId`, queries strictly AFTER the persisted cursor (`lastSeenMs`),
|
|
65
|
+
* re-feeds each record through `feedRecord` (the connection's normalize+dedupe
|
|
66
|
+
* path), then advances the cursor: to "now" on a clean pass, held just before the
|
|
67
|
+
* earliest still-retrying failure, force-advanced past any GUID that has failed
|
|
68
|
+
* `maxFailureRetries` times, or to the page boundary when the fetch hit the
|
|
69
|
+
* per-run limit (so a long backlog drains across runs instead of stranding the
|
|
70
|
+
* tail). Without an `accountId`, falls back to the legacy fixed-lookback window
|
|
71
|
+
* with no persistence. Returns a summary; never throws.
|
|
72
|
+
*/
|
|
73
|
+
export async function runBlueBubblesCatchup(args) {
|
|
74
|
+
const cfg = args.config ?? {};
|
|
75
|
+
const nowMs = (args.now ?? Date.now)();
|
|
76
|
+
const lookbackMs = typeof cfg.lookbackMs === "number" && cfg.lookbackMs > 0 ? cfg.lookbackMs : BLUEBUBBLES_CATCHUP_DEFAULT_LOOKBACK_MS;
|
|
77
|
+
const limit = clampLimit(cfg.limit);
|
|
78
|
+
const maxFailureRetries = clampMaxFailureRetries(cfg.maxFailureRetries);
|
|
79
|
+
const accountId = (args.accountId ?? "").trim();
|
|
80
|
+
const usePersistedCursor = accountId.length > 0;
|
|
81
|
+
const store = args.cursorStore ?? filesystemCursorStore;
|
|
82
|
+
// Load the persisted cursor (only when an accountId is in play).
|
|
83
|
+
const existing = usePersistedCursor
|
|
84
|
+
? (() => {
|
|
85
|
+
try {
|
|
86
|
+
return store.load(accountId);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
})()
|
|
92
|
+
: null;
|
|
93
|
+
const prevRetries = existing?.failureRetries ?? {};
|
|
94
|
+
const cursorBefore = usePersistedCursor ? (existing?.lastSeenMs ?? null) : null;
|
|
95
|
+
// Window start: after a USABLE cursor (not future-dated), else the first-run lookback.
|
|
96
|
+
const cursorUsable = existing !== null && existing.lastSeenMs <= nowMs;
|
|
97
|
+
const windowStartMs = cursorUsable ? existing.lastSeenMs : nowMs - lookbackMs;
|
|
98
|
+
const summary = {
|
|
99
|
+
querySucceeded: false,
|
|
100
|
+
fetched: 0,
|
|
101
|
+
replayed: 0,
|
|
102
|
+
givenUp: 0,
|
|
103
|
+
skippedGivenUp: 0,
|
|
104
|
+
failed: 0,
|
|
105
|
+
cursorBefore,
|
|
106
|
+
cursorAfter: usePersistedCursor ? (cursorBefore ?? windowStartMs) : windowStartMs,
|
|
107
|
+
windowStartMs,
|
|
108
|
+
};
|
|
109
|
+
if (cfg.enabled === false)
|
|
110
|
+
return summary;
|
|
111
|
+
let records = [];
|
|
112
|
+
try {
|
|
113
|
+
const url = buildBlueBubblesApiUrl({ serverUrl: args.serverUrl, path: "message/query", password: args.password });
|
|
114
|
+
const res = await blueBubblesFetchWithTimeout(url, {
|
|
115
|
+
method: "POST",
|
|
116
|
+
headers: { "Content-Type": "application/json" },
|
|
117
|
+
body: JSON.stringify({
|
|
118
|
+
limit,
|
|
119
|
+
sort: "ASC",
|
|
120
|
+
after: windowStartMs,
|
|
121
|
+
// Match the fields the live webhook carries so normalize sees the same shape.
|
|
122
|
+
with: ["chat", "chat.participants", "attachment", "handle"],
|
|
123
|
+
}),
|
|
124
|
+
}, { ...(args.timeoutMs !== undefined ? { timeoutMs: args.timeoutMs } : {}), ...(args.fetchImpl ? { fetchImpl: args.fetchImpl } : {}), ...(args.allowPrivateNetwork === false ? { allowPrivateNetwork: false } : {}) });
|
|
125
|
+
if (!res.ok) {
|
|
126
|
+
args.log?.(`catchup: message/query returned HTTP ${res.status}`);
|
|
127
|
+
return summary; // leave cursor unchanged → next run retries the same window
|
|
128
|
+
}
|
|
129
|
+
const text = await res.text();
|
|
130
|
+
const body = text ? JSON.parse(text) : {};
|
|
131
|
+
const data = body.data;
|
|
132
|
+
if (!Array.isArray(data)) {
|
|
133
|
+
args.log?.("catchup: message/query returned no data array");
|
|
134
|
+
return summary;
|
|
135
|
+
}
|
|
136
|
+
records = data.filter((r) => !!r && typeof r === "object" && !Array.isArray(r));
|
|
137
|
+
summary.querySucceeded = true;
|
|
138
|
+
summary.fetched = records.length;
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
args.log?.(`catchup: message/query failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
142
|
+
return summary;
|
|
143
|
+
}
|
|
144
|
+
// Build a fresh retry map each run (GUIDs not seen this run are dropped — the
|
|
145
|
+
// cursor advanced past them). Hold the cursor before the earliest STILL-RETRYING
|
|
146
|
+
// failure; track the latest fetched ts for page-boundary advance.
|
|
147
|
+
const nextRetries = {};
|
|
148
|
+
let earliestFailureTs = null;
|
|
149
|
+
let latestFetchedTs = windowStartMs;
|
|
150
|
+
for (const record of records) {
|
|
151
|
+
const ts = recordTimestampMs(record);
|
|
152
|
+
if (ts > latestFetchedTs)
|
|
153
|
+
latestFetchedTs = ts;
|
|
154
|
+
const guid = recordGuid(record);
|
|
155
|
+
// A GUID already given up in a prior run is skipped on sight (no replay,
|
|
156
|
+
// doesn't hold the cursor) — preserve its count so give-up stays sticky.
|
|
157
|
+
const prevCount = guid ? (prevRetries[guid] ?? 0) : 0;
|
|
158
|
+
if (guid && prevCount >= maxFailureRetries) {
|
|
159
|
+
summary.skippedGivenUp++;
|
|
160
|
+
nextRetries[guid] = prevCount;
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
// Re-feed through the SAME normalize+dedupe path a live webhook uses. The
|
|
164
|
+
// connection's dedupe cache drops anything already delivered, so a replay
|
|
165
|
+
// can never double-deliver. A THROW marks this record as a failed replay.
|
|
166
|
+
try {
|
|
167
|
+
args.feedRecord(record);
|
|
168
|
+
summary.replayed++;
|
|
169
|
+
// Success clears any accrued retries for this GUID (we just don't copy it).
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
summary.failed++;
|
|
173
|
+
const nextCount = prevCount + 1;
|
|
174
|
+
if (guid && nextCount >= maxFailureRetries) {
|
|
175
|
+
// Crossed the ceiling: give up + force-advance past it (don't hold).
|
|
176
|
+
summary.givenUp++;
|
|
177
|
+
nextRetries[guid] = nextCount;
|
|
178
|
+
args.log?.(`catchup: giving up on guid=${guid} after ${nextCount} failures; future runs will skip it. ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
// Still retrying: hold the cursor just before the earliest such failure.
|
|
182
|
+
if (guid)
|
|
183
|
+
nextRetries[guid] = nextCount;
|
|
184
|
+
if (ts > 0 && (earliestFailureTs === null || ts < earliestFailureTs))
|
|
185
|
+
earliestFailureTs = ts;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
// Compute + persist the new cursor (only when persistence is in play).
|
|
190
|
+
if (usePersistedCursor) {
|
|
191
|
+
const isTruncated = summary.fetched >= limit;
|
|
192
|
+
let nextCursorMs = nowMs;
|
|
193
|
+
if (earliestFailureTs !== null) {
|
|
194
|
+
// Hold just before the earliest still-retrying failure.
|
|
195
|
+
nextCursorMs = Math.min(Math.max(earliestFailureTs - 1, cursorBefore ?? windowStartMs), nowMs);
|
|
196
|
+
}
|
|
197
|
+
else if (isTruncated) {
|
|
198
|
+
// Advance only to the page boundary so the unfetched tail is reachable next run.
|
|
199
|
+
nextCursorMs = Math.min(Math.max(latestFetchedTs, cursorBefore ?? windowStartMs), nowMs);
|
|
200
|
+
}
|
|
201
|
+
summary.cursorAfter = nextCursorMs;
|
|
202
|
+
const retriesToPersist = capFailureRetriesMap(nextRetries, BLUEBUBBLES_MAX_FAILURE_RETRY_MAP_SIZE);
|
|
203
|
+
try {
|
|
204
|
+
store.save(accountId, {
|
|
205
|
+
lastSeenMs: nextCursorMs,
|
|
206
|
+
updatedAt: nowMs,
|
|
207
|
+
...(Object.keys(retriesToPersist).length > 0 ? { failureRetries: retriesToPersist } : {}),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
catch (err) {
|
|
211
|
+
args.log?.(`catchup: cursor save failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
212
|
+
}
|
|
213
|
+
if (isTruncated) {
|
|
214
|
+
args.log?.(`catchup: WARNING fetched=${summary.fetched} hit limit=${limit}; cursor advanced only to page boundary, remaining picked up next run. Raise catchup.limit to drain larger backlogs.`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
args.log?.(`catchup: fetched=${summary.fetched} replayed=${summary.replayed} failed=${summary.failed} given_up=${summary.givenUp} skipped_givenUp=${summary.skippedGivenUp} window_ms=${nowMs - windowStartMs}`);
|
|
218
|
+
return summary;
|
|
219
|
+
}
|
|
220
|
+
//# sourceMappingURL=catchup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catchup.js","sourceRoot":"","sources":["../../../../src/agents/channels/bluebubbles/catchup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueBubbles chat-level REST actions — typing, mark-read, and group admin.
|
|
3
|
+
*
|
|
4
|
+
* These are the chat-scoped operations on top of the per-message send surface
|
|
5
|
+
* in `send.ts`. Like every BlueBubbles REST helper they go through
|
|
6
|
+
* `buildBlueBubblesApiUrl` + `blueBubblesFetchWithTimeout` (password in the
|
|
7
|
+
* query string, INJECTABLE `fetchImpl` as the test seam) and share the
|
|
8
|
+
* `BlueBubblesRestBase` shape.
|
|
9
|
+
*
|
|
10
|
+
* Surface:
|
|
11
|
+
* - `sendBlueBubblesTyping` POST/DELETE chat/{guid}/typing (Private API)
|
|
12
|
+
* - `markBlueBubblesChatRead` POST chat/{guid}/read (Private API)
|
|
13
|
+
* - `renameBlueBubblesChat` PUT chat/{guid} (Private API)
|
|
14
|
+
* - `addBlueBubblesParticipant` POST chat/{guid}/participant/add (Private API)
|
|
15
|
+
* - `removeBlueBubblesParticipant` POST chat/{guid}/participant/remove (Private API)
|
|
16
|
+
* - `leaveBlueBubblesChat` POST chat/{guid}/leave (Private API)
|
|
17
|
+
* - `setBlueBubblesGroupIcon` POST chat/{guid}/icon (multipart) (Private API)
|
|
18
|
+
*
|
|
19
|
+
* EVERY action here needs the BlueBubbles server's Private API (the AppleScript /
|
|
20
|
+
* helper bundle that drives Messages.app); a server with the Private API off
|
|
21
|
+
* cannot type, mark-read, rename, or change membership. Typing + mark-read are
|
|
22
|
+
* COSMETIC, so they silently no-op when the Private API is off (matching how the
|
|
23
|
+
* pipeline treats read-receipts/typing as best-effort); the group-admin ops
|
|
24
|
+
* THROW an operator-facing error so the agent tool can report the refusal.
|
|
25
|
+
*/
|
|
26
|
+
import type { BlueBubblesRestBase } from "./send.js";
|
|
27
|
+
/**
|
|
28
|
+
* Signal "typing…" (or stop) in a chat. `POST` starts the indicator, `DELETE`
|
|
29
|
+
* clears it. COSMETIC — silently no-ops when the Private API is off (a server
|
|
30
|
+
* without it simply cannot drive the typing bubble). Best-effort: a transport
|
|
31
|
+
* error propagates so the caller can swallow it (the pipeline treats typing as
|
|
32
|
+
* cosmetic).
|
|
33
|
+
*/
|
|
34
|
+
export declare function sendBlueBubblesTyping(base: BlueBubblesRestBase, params: {
|
|
35
|
+
chatGuid: string;
|
|
36
|
+
typing: boolean;
|
|
37
|
+
}): Promise<void>;
|
|
38
|
+
/**
|
|
39
|
+
* Mark a chat read (clears the unread badge + sends a read receipt). COSMETIC —
|
|
40
|
+
* silently no-ops when the Private API is off.
|
|
41
|
+
*/
|
|
42
|
+
export declare function markBlueBubblesChatRead(base: BlueBubblesRestBase, params: {
|
|
43
|
+
chatGuid: string;
|
|
44
|
+
}): Promise<void>;
|
|
45
|
+
/** Rename a group chat's display name (Private API). */
|
|
46
|
+
export declare function renameBlueBubblesChat(base: BlueBubblesRestBase, params: {
|
|
47
|
+
chatGuid: string;
|
|
48
|
+
displayName: string;
|
|
49
|
+
}): Promise<void>;
|
|
50
|
+
/** Add a participant (phone/email handle) to a group chat (Private API). */
|
|
51
|
+
export declare function addBlueBubblesParticipant(base: BlueBubblesRestBase, params: {
|
|
52
|
+
chatGuid: string;
|
|
53
|
+
address: string;
|
|
54
|
+
}): Promise<void>;
|
|
55
|
+
/** Remove a participant (phone/email handle) from a group chat (Private API). */
|
|
56
|
+
export declare function removeBlueBubblesParticipant(base: BlueBubblesRestBase, params: {
|
|
57
|
+
chatGuid: string;
|
|
58
|
+
address: string;
|
|
59
|
+
}): Promise<void>;
|
|
60
|
+
/** Leave a group chat (Private API). */
|
|
61
|
+
export declare function leaveBlueBubblesChat(base: BlueBubblesRestBase, params: {
|
|
62
|
+
chatGuid: string;
|
|
63
|
+
}): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* Set (or change) a group chat's icon/photo via a multipart upload (Private
|
|
66
|
+
* API). `bytes` is the image; `filename` is sanitised. A longer default timeout
|
|
67
|
+
* applies (uploads are heavier than a JSON call).
|
|
68
|
+
*/
|
|
69
|
+
export declare function setBlueBubblesGroupIcon(base: BlueBubblesRestBase, params: {
|
|
70
|
+
chatGuid: string;
|
|
71
|
+
bytes: Uint8Array;
|
|
72
|
+
filename?: string;
|
|
73
|
+
contentType?: string;
|
|
74
|
+
}): Promise<void>;
|
|
75
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/bluebubbles/chat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAWH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,WAAW,CAAC;AAkBrD;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CAC1C,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,CAAA;CAAE,GAC3C,OAAO,CAAC,IAAI,CAAC,CAYf;AAED;;;GAGG;AACH,wBAAsB,uBAAuB,CAC5C,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAWf;AAED,wDAAwD;AACxD,wBAAsB,qBAAqB,CAC1C,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,EAAE,MAAM,CAAA;CAAE,GAC/C,OAAO,CAAC,IAAI,CAAC,CAmBf;AAED,4EAA4E;AAC5E,wBAAsB,yBAAyB,CAC9C,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,iFAAiF;AACjF,wBAAsB,4BAA4B,CACjD,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAC3C,OAAO,CAAC,IAAI,CAAC,CAiBf;AAED,wCAAwC;AACxC,wBAAsB,oBAAoB,CACzC,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAA;CAAE,GAC1B,OAAO,CAAC,IAAI,CAAC,CAWf;AAQD;;;;GAIG;AACH,wBAAsB,uBAAuB,CAC5C,IAAI,EAAE,mBAAmB,EACzB,MAAM,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,UAAU,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,GACtF,OAAO,CAAC,IAAI,CAAC,CA0Bf"}
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueBubbles chat-level REST actions — typing, mark-read, and group admin.
|
|
3
|
+
*
|
|
4
|
+
* These are the chat-scoped operations on top of the per-message send surface
|
|
5
|
+
* in `send.ts`. Like every BlueBubbles REST helper they go through
|
|
6
|
+
* `buildBlueBubblesApiUrl` + `blueBubblesFetchWithTimeout` (password in the
|
|
7
|
+
* query string, INJECTABLE `fetchImpl` as the test seam) and share the
|
|
8
|
+
* `BlueBubblesRestBase` shape.
|
|
9
|
+
*
|
|
10
|
+
* Surface:
|
|
11
|
+
* - `sendBlueBubblesTyping` POST/DELETE chat/{guid}/typing (Private API)
|
|
12
|
+
* - `markBlueBubblesChatRead` POST chat/{guid}/read (Private API)
|
|
13
|
+
* - `renameBlueBubblesChat` PUT chat/{guid} (Private API)
|
|
14
|
+
* - `addBlueBubblesParticipant` POST chat/{guid}/participant/add (Private API)
|
|
15
|
+
* - `removeBlueBubblesParticipant` POST chat/{guid}/participant/remove (Private API)
|
|
16
|
+
* - `leaveBlueBubblesChat` POST chat/{guid}/leave (Private API)
|
|
17
|
+
* - `setBlueBubblesGroupIcon` POST chat/{guid}/icon (multipart) (Private API)
|
|
18
|
+
*
|
|
19
|
+
* EVERY action here needs the BlueBubbles server's Private API (the AppleScript /
|
|
20
|
+
* helper bundle that drives Messages.app); a server with the Private API off
|
|
21
|
+
* cannot type, mark-read, rename, or change membership. Typing + mark-read are
|
|
22
|
+
* COSMETIC, so they silently no-op when the Private API is off (matching how the
|
|
23
|
+
* pipeline treats read-receipts/typing as best-effort); the group-admin ops
|
|
24
|
+
* THROW an operator-facing error so the agent tool can report the refusal.
|
|
25
|
+
*/
|
|
26
|
+
import { randomUUID } from "node:crypto";
|
|
27
|
+
import path from "node:path";
|
|
28
|
+
import { blueBubblesFetchWithTimeout, buildBlueBubblesApiUrl, readBlueBubblesJson, } from "./types.js";
|
|
29
|
+
/** Shared fetch-option assembly so every helper threads timeout + fetchImpl + the SSRF knob identically. */
|
|
30
|
+
function fetchOpts(base) {
|
|
31
|
+
return {
|
|
32
|
+
...(base.timeoutMs !== undefined ? { timeoutMs: base.timeoutMs } : {}),
|
|
33
|
+
...(base.fetchImpl ? { fetchImpl: base.fetchImpl } : {}),
|
|
34
|
+
...(base.allowPrivateNetwork === false ? { allowPrivateNetwork: false } : {}),
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** Refuse a Private-API-only group action when the server has the Private API off. */
|
|
38
|
+
function assertPrivateApi(base, feature) {
|
|
39
|
+
if (base.privateApiEnabled === false) {
|
|
40
|
+
throw new Error(`BlueBubbles ${feature} requires the Private API to be enabled on the server`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Signal "typing…" (or stop) in a chat. `POST` starts the indicator, `DELETE`
|
|
45
|
+
* clears it. COSMETIC — silently no-ops when the Private API is off (a server
|
|
46
|
+
* without it simply cannot drive the typing bubble). Best-effort: a transport
|
|
47
|
+
* error propagates so the caller can swallow it (the pipeline treats typing as
|
|
48
|
+
* cosmetic).
|
|
49
|
+
*/
|
|
50
|
+
export async function sendBlueBubblesTyping(base, params) {
|
|
51
|
+
const chatGuid = (params.chatGuid ?? "").trim();
|
|
52
|
+
if (!chatGuid)
|
|
53
|
+
return;
|
|
54
|
+
// Private API drives the typing indicator; without it there is nothing to do.
|
|
55
|
+
if (base.privateApiEnabled === false)
|
|
56
|
+
return;
|
|
57
|
+
const url = buildBlueBubblesApiUrl({
|
|
58
|
+
serverUrl: base.serverUrl,
|
|
59
|
+
path: `chat/${encodeURIComponent(chatGuid)}/typing`,
|
|
60
|
+
password: base.password,
|
|
61
|
+
});
|
|
62
|
+
const res = await blueBubblesFetchWithTimeout(url, { method: params.typing ? "POST" : "DELETE" }, fetchOpts(base));
|
|
63
|
+
await readBlueBubblesJson(res, "chat/typing");
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Mark a chat read (clears the unread badge + sends a read receipt). COSMETIC —
|
|
67
|
+
* silently no-ops when the Private API is off.
|
|
68
|
+
*/
|
|
69
|
+
export async function markBlueBubblesChatRead(base, params) {
|
|
70
|
+
const chatGuid = (params.chatGuid ?? "").trim();
|
|
71
|
+
if (!chatGuid)
|
|
72
|
+
return;
|
|
73
|
+
if (base.privateApiEnabled === false)
|
|
74
|
+
return;
|
|
75
|
+
const url = buildBlueBubblesApiUrl({
|
|
76
|
+
serverUrl: base.serverUrl,
|
|
77
|
+
path: `chat/${encodeURIComponent(chatGuid)}/read`,
|
|
78
|
+
password: base.password,
|
|
79
|
+
});
|
|
80
|
+
const res = await blueBubblesFetchWithTimeout(url, { method: "POST" }, fetchOpts(base));
|
|
81
|
+
await readBlueBubblesJson(res, "chat/read");
|
|
82
|
+
}
|
|
83
|
+
/** Rename a group chat's display name (Private API). */
|
|
84
|
+
export async function renameBlueBubblesChat(base, params) {
|
|
85
|
+
assertPrivateApi(base, "group rename");
|
|
86
|
+
const chatGuid = (params.chatGuid ?? "").trim();
|
|
87
|
+
if (!chatGuid)
|
|
88
|
+
throw new Error("BlueBubbles group rename requires a chatGuid");
|
|
89
|
+
const url = buildBlueBubblesApiUrl({
|
|
90
|
+
serverUrl: base.serverUrl,
|
|
91
|
+
path: `chat/${encodeURIComponent(chatGuid)}`,
|
|
92
|
+
password: base.password,
|
|
93
|
+
});
|
|
94
|
+
const res = await blueBubblesFetchWithTimeout(url, {
|
|
95
|
+
method: "PUT",
|
|
96
|
+
headers: { "Content-Type": "application/json" },
|
|
97
|
+
body: JSON.stringify({ displayName: params.displayName ?? "" }),
|
|
98
|
+
}, fetchOpts(base));
|
|
99
|
+
await readBlueBubblesJson(res, "chat/rename");
|
|
100
|
+
}
|
|
101
|
+
/** Add a participant (phone/email handle) to a group chat (Private API). */
|
|
102
|
+
export async function addBlueBubblesParticipant(base, params) {
|
|
103
|
+
assertPrivateApi(base, "add participant");
|
|
104
|
+
const chatGuid = (params.chatGuid ?? "").trim();
|
|
105
|
+
const address = (params.address ?? "").trim();
|
|
106
|
+
if (!chatGuid)
|
|
107
|
+
throw new Error("BlueBubbles add participant requires a chatGuid");
|
|
108
|
+
if (!address)
|
|
109
|
+
throw new Error("BlueBubbles add participant requires an address");
|
|
110
|
+
const url = buildBlueBubblesApiUrl({
|
|
111
|
+
serverUrl: base.serverUrl,
|
|
112
|
+
path: `chat/${encodeURIComponent(chatGuid)}/participant/add`,
|
|
113
|
+
password: base.password,
|
|
114
|
+
});
|
|
115
|
+
const res = await blueBubblesFetchWithTimeout(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address }) }, fetchOpts(base));
|
|
116
|
+
await readBlueBubblesJson(res, "chat/participant/add");
|
|
117
|
+
}
|
|
118
|
+
/** Remove a participant (phone/email handle) from a group chat (Private API). */
|
|
119
|
+
export async function removeBlueBubblesParticipant(base, params) {
|
|
120
|
+
assertPrivateApi(base, "remove participant");
|
|
121
|
+
const chatGuid = (params.chatGuid ?? "").trim();
|
|
122
|
+
const address = (params.address ?? "").trim();
|
|
123
|
+
if (!chatGuid)
|
|
124
|
+
throw new Error("BlueBubbles remove participant requires a chatGuid");
|
|
125
|
+
if (!address)
|
|
126
|
+
throw new Error("BlueBubbles remove participant requires an address");
|
|
127
|
+
const url = buildBlueBubblesApiUrl({
|
|
128
|
+
serverUrl: base.serverUrl,
|
|
129
|
+
path: `chat/${encodeURIComponent(chatGuid)}/participant/remove`,
|
|
130
|
+
password: base.password,
|
|
131
|
+
});
|
|
132
|
+
const res = await blueBubblesFetchWithTimeout(url, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ address }) }, fetchOpts(base));
|
|
133
|
+
await readBlueBubblesJson(res, "chat/participant/remove");
|
|
134
|
+
}
|
|
135
|
+
/** Leave a group chat (Private API). */
|
|
136
|
+
export async function leaveBlueBubblesChat(base, params) {
|
|
137
|
+
assertPrivateApi(base, "leave group");
|
|
138
|
+
const chatGuid = (params.chatGuid ?? "").trim();
|
|
139
|
+
if (!chatGuid)
|
|
140
|
+
throw new Error("BlueBubbles leave group requires a chatGuid");
|
|
141
|
+
const url = buildBlueBubblesApiUrl({
|
|
142
|
+
serverUrl: base.serverUrl,
|
|
143
|
+
path: `chat/${encodeURIComponent(chatGuid)}/leave`,
|
|
144
|
+
password: base.password,
|
|
145
|
+
});
|
|
146
|
+
const res = await blueBubblesFetchWithTimeout(url, { method: "POST" }, fetchOpts(base));
|
|
147
|
+
await readBlueBubblesJson(res, "chat/leave");
|
|
148
|
+
}
|
|
149
|
+
/** Sanitise a filename for a multipart header (CWE-93 / header injection guard). */
|
|
150
|
+
function sanitizeIconFilename(name) {
|
|
151
|
+
const base = path.basename(name || "icon.png").replace(/[\r\n"\\]/g, "_").trim();
|
|
152
|
+
return base || "icon.png";
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Set (or change) a group chat's icon/photo via a multipart upload (Private
|
|
156
|
+
* API). `bytes` is the image; `filename` is sanitised. A longer default timeout
|
|
157
|
+
* applies (uploads are heavier than a JSON call).
|
|
158
|
+
*/
|
|
159
|
+
export async function setBlueBubblesGroupIcon(base, params) {
|
|
160
|
+
assertPrivateApi(base, "set group icon");
|
|
161
|
+
const chatGuid = (params.chatGuid ?? "").trim();
|
|
162
|
+
if (!chatGuid)
|
|
163
|
+
throw new Error("BlueBubbles set group icon requires a chatGuid");
|
|
164
|
+
if (!params.bytes || params.bytes.length === 0)
|
|
165
|
+
throw new Error("BlueBubbles set group icon requires image bytes");
|
|
166
|
+
const filename = sanitizeIconFilename(params.filename ?? "icon.png");
|
|
167
|
+
const form = new FormData();
|
|
168
|
+
const ab = params.bytes.buffer.slice(params.bytes.byteOffset, params.bytes.byteOffset + params.bytes.byteLength);
|
|
169
|
+
const blob = new Blob([ab], params.contentType ? { type: params.contentType } : {});
|
|
170
|
+
form.append("icon", blob, filename);
|
|
171
|
+
form.append("tempGuid", `temp-${Date.now()}-${randomUUID().slice(0, 8)}`);
|
|
172
|
+
const url = buildBlueBubblesApiUrl({
|
|
173
|
+
serverUrl: base.serverUrl,
|
|
174
|
+
path: `chat/${encodeURIComponent(chatGuid)}/icon`,
|
|
175
|
+
password: base.password,
|
|
176
|
+
});
|
|
177
|
+
const res = await blueBubblesFetchWithTimeout(url, { method: "POST", body: form },
|
|
178
|
+
// Uploads can be large — give a generous default timeout.
|
|
179
|
+
{ ...fetchOpts(base), timeoutMs: base.timeoutMs ?? 60_000 });
|
|
180
|
+
await readBlueBubblesJson(res, "chat/icon");
|
|
181
|
+
}
|
|
182
|
+
//# sourceMappingURL=chat.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.js","sourceRoot":"","sources":["../../../../src/agents/channels/bluebubbles/chat.ts"],"names":[],"mappings":"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"}
|