@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.
Files changed (257) hide show
  1. package/README.md +19 -0
  2. package/dist/agents/agent-loop.d.ts +13 -0
  3. package/dist/agents/agent-loop.d.ts.map +1 -1
  4. package/dist/agents/agent-loop.js +5 -0
  5. package/dist/agents/agent-loop.js.map +1 -1
  6. package/dist/agents/channels/access-control/group-tool-policy.d.ts +73 -0
  7. package/dist/agents/channels/access-control/group-tool-policy.d.ts.map +1 -0
  8. package/dist/agents/channels/access-control/group-tool-policy.js +193 -0
  9. package/dist/agents/channels/access-control/group-tool-policy.js.map +1 -0
  10. package/dist/agents/channels/access-control/index.d.ts +1 -0
  11. package/dist/agents/channels/access-control/index.d.ts.map +1 -1
  12. package/dist/agents/channels/access-control/index.js +1 -0
  13. package/dist/agents/channels/access-control/index.js.map +1 -1
  14. package/dist/agents/channels/bluebubbles/account-config.d.ts +253 -0
  15. package/dist/agents/channels/bluebubbles/account-config.d.ts.map +1 -0
  16. package/dist/agents/channels/bluebubbles/account-config.js +486 -0
  17. package/dist/agents/channels/bluebubbles/account-config.js.map +1 -0
  18. package/dist/agents/channels/bluebubbles/account-registry.d.ts +33 -0
  19. package/dist/agents/channels/bluebubbles/account-registry.d.ts.map +1 -0
  20. package/dist/agents/channels/bluebubbles/account-registry.js +46 -0
  21. package/dist/agents/channels/bluebubbles/account-registry.js.map +1 -0
  22. package/dist/agents/channels/bluebubbles/adapter.d.ts +45 -0
  23. package/dist/agents/channels/bluebubbles/adapter.d.ts.map +1 -0
  24. package/dist/agents/channels/bluebubbles/adapter.js +366 -0
  25. package/dist/agents/channels/bluebubbles/adapter.js.map +1 -0
  26. package/dist/agents/channels/bluebubbles/catchup-cursor.d.ts +52 -0
  27. package/dist/agents/channels/bluebubbles/catchup-cursor.d.ts.map +1 -0
  28. package/dist/agents/channels/bluebubbles/catchup-cursor.js +118 -0
  29. package/dist/agents/channels/bluebubbles/catchup-cursor.js.map +1 -0
  30. package/dist/agents/channels/bluebubbles/catchup.d.ts +114 -0
  31. package/dist/agents/channels/bluebubbles/catchup.d.ts.map +1 -0
  32. package/dist/agents/channels/bluebubbles/catchup.js +220 -0
  33. package/dist/agents/channels/bluebubbles/catchup.js.map +1 -0
  34. package/dist/agents/channels/bluebubbles/chat.d.ts +75 -0
  35. package/dist/agents/channels/bluebubbles/chat.d.ts.map +1 -0
  36. package/dist/agents/channels/bluebubbles/chat.js +182 -0
  37. package/dist/agents/channels/bluebubbles/chat.js.map +1 -0
  38. package/dist/agents/channels/bluebubbles/connection.d.ts +95 -0
  39. package/dist/agents/channels/bluebubbles/connection.d.ts.map +1 -0
  40. package/dist/agents/channels/bluebubbles/connection.js +413 -0
  41. package/dist/agents/channels/bluebubbles/connection.js.map +1 -0
  42. package/dist/agents/channels/bluebubbles/contact-names.d.ts +79 -0
  43. package/dist/agents/channels/bluebubbles/contact-names.d.ts.map +1 -0
  44. package/dist/agents/channels/bluebubbles/contact-names.js +188 -0
  45. package/dist/agents/channels/bluebubbles/contact-names.js.map +1 -0
  46. package/dist/agents/channels/bluebubbles/debounce.d.ts +78 -0
  47. package/dist/agents/channels/bluebubbles/debounce.d.ts.map +1 -0
  48. package/dist/agents/channels/bluebubbles/debounce.js +173 -0
  49. package/dist/agents/channels/bluebubbles/debounce.js.map +1 -0
  50. package/dist/agents/channels/bluebubbles/dedupe.d.ts +25 -0
  51. package/dist/agents/channels/bluebubbles/dedupe.d.ts.map +1 -0
  52. package/dist/agents/channels/bluebubbles/dedupe.js +35 -0
  53. package/dist/agents/channels/bluebubbles/dedupe.js.map +1 -0
  54. package/dist/agents/channels/bluebubbles/effects.d.ts +17 -0
  55. package/dist/agents/channels/bluebubbles/effects.d.ts.map +1 -0
  56. package/dist/agents/channels/bluebubbles/effects.js +57 -0
  57. package/dist/agents/channels/bluebubbles/effects.js.map +1 -0
  58. package/dist/agents/channels/bluebubbles/history.d.ts +56 -0
  59. package/dist/agents/channels/bluebubbles/history.d.ts.map +1 -0
  60. package/dist/agents/channels/bluebubbles/history.js +140 -0
  61. package/dist/agents/channels/bluebubbles/history.js.map +1 -0
  62. package/dist/agents/channels/bluebubbles/index.d.ts +18 -0
  63. package/dist/agents/channels/bluebubbles/index.d.ts.map +1 -0
  64. package/dist/agents/channels/bluebubbles/index.js +18 -0
  65. package/dist/agents/channels/bluebubbles/index.js.map +1 -0
  66. package/dist/agents/channels/bluebubbles/media.d.ts +104 -0
  67. package/dist/agents/channels/bluebubbles/media.d.ts.map +1 -0
  68. package/dist/agents/channels/bluebubbles/media.js +222 -0
  69. package/dist/agents/channels/bluebubbles/media.js.map +1 -0
  70. package/dist/agents/channels/bluebubbles/messaging.d.ts +13 -0
  71. package/dist/agents/channels/bluebubbles/messaging.d.ts.map +1 -0
  72. package/dist/agents/channels/bluebubbles/messaging.js +36 -0
  73. package/dist/agents/channels/bluebubbles/messaging.js.map +1 -0
  74. package/dist/agents/channels/bluebubbles/module.d.ts +24 -0
  75. package/dist/agents/channels/bluebubbles/module.d.ts.map +1 -0
  76. package/dist/agents/channels/bluebubbles/module.js +52 -0
  77. package/dist/agents/channels/bluebubbles/module.js.map +1 -0
  78. package/dist/agents/channels/bluebubbles/normalize.d.ts +111 -0
  79. package/dist/agents/channels/bluebubbles/normalize.d.ts.map +1 -0
  80. package/dist/agents/channels/bluebubbles/normalize.js +239 -0
  81. package/dist/agents/channels/bluebubbles/normalize.js.map +1 -0
  82. package/dist/agents/channels/bluebubbles/plugin.d.ts +46 -0
  83. package/dist/agents/channels/bluebubbles/plugin.d.ts.map +1 -0
  84. package/dist/agents/channels/bluebubbles/plugin.js +242 -0
  85. package/dist/agents/channels/bluebubbles/plugin.js.map +1 -0
  86. package/dist/agents/channels/bluebubbles/probe.d.ts +79 -0
  87. package/dist/agents/channels/bluebubbles/probe.d.ts.map +1 -0
  88. package/dist/agents/channels/bluebubbles/probe.js +138 -0
  89. package/dist/agents/channels/bluebubbles/probe.js.map +1 -0
  90. package/dist/agents/channels/bluebubbles/reactions.d.ts +46 -0
  91. package/dist/agents/channels/bluebubbles/reactions.d.ts.map +1 -0
  92. package/dist/agents/channels/bluebubbles/reactions.js +207 -0
  93. package/dist/agents/channels/bluebubbles/reactions.js.map +1 -0
  94. package/dist/agents/channels/bluebubbles/send.d.ts +132 -0
  95. package/dist/agents/channels/bluebubbles/send.d.ts.map +1 -0
  96. package/dist/agents/channels/bluebubbles/send.js +327 -0
  97. package/dist/agents/channels/bluebubbles/send.js.map +1 -0
  98. package/dist/agents/channels/bluebubbles/status-issues.d.ts +57 -0
  99. package/dist/agents/channels/bluebubbles/status-issues.d.ts.map +1 -0
  100. package/dist/agents/channels/bluebubbles/status-issues.js +93 -0
  101. package/dist/agents/channels/bluebubbles/status-issues.js.map +1 -0
  102. package/dist/agents/channels/bluebubbles/types.d.ts +69 -0
  103. package/dist/agents/channels/bluebubbles/types.d.ts.map +1 -0
  104. package/dist/agents/channels/bluebubbles/types.js +118 -0
  105. package/dist/agents/channels/bluebubbles/types.js.map +1 -0
  106. package/dist/agents/channels/bluebubbles/webhook.d.ts +97 -0
  107. package/dist/agents/channels/bluebubbles/webhook.d.ts.map +1 -0
  108. package/dist/agents/channels/bluebubbles/webhook.js +249 -0
  109. package/dist/agents/channels/bluebubbles/webhook.js.map +1 -0
  110. package/dist/agents/channels/bundled-channel-metas.d.ts +13 -0
  111. package/dist/agents/channels/bundled-channel-metas.d.ts.map +1 -1
  112. package/dist/agents/channels/bundled-channel-metas.js +33 -0
  113. package/dist/agents/channels/bundled-channel-metas.js.map +1 -1
  114. package/dist/agents/channels/imessage/account-config.d.ts +178 -0
  115. package/dist/agents/channels/imessage/account-config.d.ts.map +1 -0
  116. package/dist/agents/channels/imessage/account-config.js +371 -0
  117. package/dist/agents/channels/imessage/account-config.js.map +1 -0
  118. package/dist/agents/channels/imessage/adapter.d.ts +36 -0
  119. package/dist/agents/channels/imessage/adapter.d.ts.map +1 -0
  120. package/dist/agents/channels/imessage/adapter.js +286 -0
  121. package/dist/agents/channels/imessage/adapter.js.map +1 -0
  122. package/dist/agents/channels/imessage/client.d.ts +99 -0
  123. package/dist/agents/channels/imessage/client.d.ts.map +1 -0
  124. package/dist/agents/channels/imessage/client.js +231 -0
  125. package/dist/agents/channels/imessage/client.js.map +1 -0
  126. package/dist/agents/channels/imessage/connection.d.ts +101 -0
  127. package/dist/agents/channels/imessage/connection.d.ts.map +1 -0
  128. package/dist/agents/channels/imessage/connection.js +367 -0
  129. package/dist/agents/channels/imessage/connection.js.map +1 -0
  130. package/dist/agents/channels/imessage/format.d.ts +45 -0
  131. package/dist/agents/channels/imessage/format.d.ts.map +1 -0
  132. package/dist/agents/channels/imessage/format.js +170 -0
  133. package/dist/agents/channels/imessage/format.js.map +1 -0
  134. package/dist/agents/channels/imessage/history.d.ts +49 -0
  135. package/dist/agents/channels/imessage/history.d.ts.map +1 -0
  136. package/dist/agents/channels/imessage/history.js +74 -0
  137. package/dist/agents/channels/imessage/history.js.map +1 -0
  138. package/dist/agents/channels/imessage/index.d.ts +25 -0
  139. package/dist/agents/channels/imessage/index.d.ts.map +1 -0
  140. package/dist/agents/channels/imessage/index.js +25 -0
  141. package/dist/agents/channels/imessage/index.js.map +1 -0
  142. package/dist/agents/channels/imessage/media.d.ts +92 -0
  143. package/dist/agents/channels/imessage/media.d.ts.map +1 -0
  144. package/dist/agents/channels/imessage/media.js +196 -0
  145. package/dist/agents/channels/imessage/media.js.map +1 -0
  146. package/dist/agents/channels/imessage/messaging.d.ts +14 -0
  147. package/dist/agents/channels/imessage/messaging.d.ts.map +1 -0
  148. package/dist/agents/channels/imessage/messaging.js +37 -0
  149. package/dist/agents/channels/imessage/messaging.js.map +1 -0
  150. package/dist/agents/channels/imessage/module.d.ts +16 -0
  151. package/dist/agents/channels/imessage/module.d.ts.map +1 -0
  152. package/dist/agents/channels/imessage/module.js +23 -0
  153. package/dist/agents/channels/imessage/module.js.map +1 -0
  154. package/dist/agents/channels/imessage/monitor.d.ts +207 -0
  155. package/dist/agents/channels/imessage/monitor.d.ts.map +1 -0
  156. package/dist/agents/channels/imessage/monitor.js +504 -0
  157. package/dist/agents/channels/imessage/monitor.js.map +1 -0
  158. package/dist/agents/channels/imessage/plugin.d.ts +53 -0
  159. package/dist/agents/channels/imessage/plugin.d.ts.map +1 -0
  160. package/dist/agents/channels/imessage/plugin.js +215 -0
  161. package/dist/agents/channels/imessage/plugin.js.map +1 -0
  162. package/dist/agents/channels/imessage/probe.d.ts +68 -0
  163. package/dist/agents/channels/imessage/probe.d.ts.map +1 -0
  164. package/dist/agents/channels/imessage/probe.js +134 -0
  165. package/dist/agents/channels/imessage/probe.js.map +1 -0
  166. package/dist/agents/channels/imessage/remote-attachments.d.ts +61 -0
  167. package/dist/agents/channels/imessage/remote-attachments.d.ts.map +1 -0
  168. package/dist/agents/channels/imessage/remote-attachments.js +131 -0
  169. package/dist/agents/channels/imessage/remote-attachments.js.map +1 -0
  170. package/dist/agents/channels/imessage/send.d.ts +61 -0
  171. package/dist/agents/channels/imessage/send.d.ts.map +1 -0
  172. package/dist/agents/channels/imessage/send.js +108 -0
  173. package/dist/agents/channels/imessage/send.js.map +1 -0
  174. package/dist/agents/channels/imessage/targets.d.ts +91 -0
  175. package/dist/agents/channels/imessage/targets.d.ts.map +1 -0
  176. package/dist/agents/channels/imessage/targets.js +245 -0
  177. package/dist/agents/channels/imessage/targets.js.map +1 -0
  178. package/dist/agents/channels/imessage/watch-error.d.ts +23 -0
  179. package/dist/agents/channels/imessage/watch-error.d.ts.map +1 -0
  180. package/dist/agents/channels/imessage/watch-error.js +69 -0
  181. package/dist/agents/channels/imessage/watch-error.js.map +1 -0
  182. package/dist/agents/channels/inbound-pipeline.d.ts +11 -0
  183. package/dist/agents/channels/inbound-pipeline.d.ts.map +1 -1
  184. package/dist/agents/channels/inbound-pipeline.js +20 -1
  185. package/dist/agents/channels/inbound-pipeline.js.map +1 -1
  186. package/dist/agents/channels/manager.d.ts +9 -0
  187. package/dist/agents/channels/manager.d.ts.map +1 -1
  188. package/dist/agents/channels/manager.js.map +1 -1
  189. package/dist/agents/channels/sdk.d.ts +4 -0
  190. package/dist/agents/channels/sdk.d.ts.map +1 -1
  191. package/dist/agents/channels/sdk.js +4 -0
  192. package/dist/agents/channels/sdk.js.map +1 -1
  193. package/dist/agents/extensions/modules/index.d.ts.map +1 -1
  194. package/dist/agents/extensions/modules/index.js +12 -0
  195. package/dist/agents/extensions/modules/index.js.map +1 -1
  196. package/dist/agents/session-wiring.d.ts +31 -0
  197. package/dist/agents/session-wiring.d.ts.map +1 -1
  198. package/dist/agents/session-wiring.js +45 -2
  199. package/dist/agents/session-wiring.js.map +1 -1
  200. package/dist/agents/tools/bluebubbles-action-tool.d.ts +66 -0
  201. package/dist/agents/tools/bluebubbles-action-tool.d.ts.map +1 -0
  202. package/dist/agents/tools/bluebubbles-action-tool.js +234 -0
  203. package/dist/agents/tools/bluebubbles-action-tool.js.map +1 -0
  204. package/dist/agents/tools/registry.d.ts.map +1 -1
  205. package/dist/agents/tools/registry.js +18 -0
  206. package/dist/agents/tools/registry.js.map +1 -1
  207. package/dist/buildstamp.json +1 -1
  208. package/dist/cli/commands/expose.d.ts +40 -0
  209. package/dist/cli/commands/expose.d.ts.map +1 -0
  210. package/dist/cli/commands/expose.js +200 -0
  211. package/dist/cli/commands/expose.js.map +1 -0
  212. package/dist/cli/program/build-program.d.ts.map +1 -1
  213. package/dist/cli/program/build-program.js +61 -0
  214. package/dist/cli/program/build-program.js.map +1 -1
  215. package/dist/config/io.d.ts +41 -0
  216. package/dist/config/io.d.ts.map +1 -1
  217. package/dist/config/io.js.map +1 -1
  218. package/dist/core/server.d.ts.map +1 -1
  219. package/dist/core/server.js +48 -2
  220. package/dist/core/server.js.map +1 -1
  221. package/dist/core/tunnel/auth-proxy.d.ts +55 -0
  222. package/dist/core/tunnel/auth-proxy.d.ts.map +1 -0
  223. package/dist/core/tunnel/auth-proxy.js +179 -0
  224. package/dist/core/tunnel/auth-proxy.js.map +1 -0
  225. package/dist/core/tunnel/manager.d.ts +42 -0
  226. package/dist/core/tunnel/manager.d.ts.map +1 -0
  227. package/dist/core/tunnel/manager.js +102 -0
  228. package/dist/core/tunnel/manager.js.map +1 -0
  229. package/dist/core/tunnel/providers/bore.d.ts +18 -0
  230. package/dist/core/tunnel/providers/bore.d.ts.map +1 -0
  231. package/dist/core/tunnel/providers/bore.js +117 -0
  232. package/dist/core/tunnel/providers/bore.js.map +1 -0
  233. package/dist/core/tunnel/providers/cloudflared.d.ts +24 -0
  234. package/dist/core/tunnel/providers/cloudflared.d.ts.map +1 -0
  235. package/dist/core/tunnel/providers/cloudflared.js +179 -0
  236. package/dist/core/tunnel/providers/cloudflared.js.map +1 -0
  237. package/dist/core/tunnel/providers/custom.d.ts +21 -0
  238. package/dist/core/tunnel/providers/custom.d.ts.map +1 -0
  239. package/dist/core/tunnel/providers/custom.js +124 -0
  240. package/dist/core/tunnel/providers/custom.js.map +1 -0
  241. package/dist/core/tunnel/registry.d.ts +15 -0
  242. package/dist/core/tunnel/registry.d.ts.map +1 -0
  243. package/dist/core/tunnel/registry.js +26 -0
  244. package/dist/core/tunnel/registry.js.map +1 -0
  245. package/dist/core/tunnel/state.d.ts +39 -0
  246. package/dist/core/tunnel/state.d.ts.map +1 -0
  247. package/dist/core/tunnel/state.js +57 -0
  248. package/dist/core/tunnel/state.js.map +1 -0
  249. package/dist/core/tunnel/types.d.ts +61 -0
  250. package/dist/core/tunnel/types.d.ts.map +1 -0
  251. package/dist/core/tunnel/types.js +20 -0
  252. package/dist/core/tunnel/types.js.map +1 -0
  253. package/dist/infra/net/fetch-guard.d.ts +24 -2
  254. package/dist/infra/net/fetch-guard.d.ts.map +1 -1
  255. package/dist/infra/net/fetch-guard.js +78 -31
  256. package/dist/infra/net/fetch-guard.js.map +1 -1
  257. 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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"}