@spinabot/brigade 1.7.0 → 1.8.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/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/core/server.d.ts.map +1 -1
- package/dist/core/server.js +48 -2
- package/dist/core/server.js.map +1 -1
- 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 +1 -1
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueBubbles channel adapter.
|
|
3
|
+
*
|
|
4
|
+
* Implements the Brigade `ChannelAdapter` contract on top of the BlueBubbles
|
|
5
|
+
* macOS server (REST-out, `connection.ts`) with inbound delivered via the gateway
|
|
6
|
+
* webhook route (`feedWebhookEvent` → the connection's normalize+dedupe path).
|
|
7
|
+
* BlueBubbles authenticates with a server password (sealed like Slack/Discord
|
|
8
|
+
* tokens); enablement is `channels.bluebubbles.enabled: true` plus a resolvable
|
|
9
|
+
* serverUrl + password.
|
|
10
|
+
*
|
|
11
|
+
* Unlike the native `imessage` channel, BlueBubbles supports REAL message actions
|
|
12
|
+
* via the server's Private API — reactions / edit / unsend — so this adapter
|
|
13
|
+
* advertises those capabilities (gated on `actions.*` config AND the live
|
|
14
|
+
* Private-API status from the probe) and implements `handleAction`.
|
|
15
|
+
*
|
|
16
|
+
* TEST SEAM: `connectImpl` overrides how the connection is built — production
|
|
17
|
+
* leaves it undefined and `connectBlueBubbles` is used with the real (injectable)
|
|
18
|
+
* fetch; a unit test injects a fake connection so send / action / inbound run
|
|
19
|
+
* with no live server.
|
|
20
|
+
*/
|
|
21
|
+
import { loadConfig } from "../../../core/config.js";
|
|
22
|
+
import { chunkText, } from "../sdk.js";
|
|
23
|
+
import { bluebubblesChannelEnabled, isBlueBubblesOpAllowed, listBlueBubblesAccountIds, resolveBlueBubblesAccount, resolveBlueBubblesPassword, resolveBlueBubblesServerUrl, BLUEBUBBLES_CHANNEL_ID, BLUEBUBBLES_DEFAULT_ACCOUNT_ID, } from "./account-config.js";
|
|
24
|
+
import { connectBlueBubbles, } from "./connection.js";
|
|
25
|
+
import { isMacOSEditUnsupported, probeBlueBubbles } from "./probe.js";
|
|
26
|
+
/** Practical per-message text limit for chunked sends (before bubble-splitting). */
|
|
27
|
+
const BLUEBUBBLES_TEXT_LIMIT = 10_000;
|
|
28
|
+
/**
|
|
29
|
+
* Synthesise the agent-facing note for an inbound tapback. A tapback carries no
|
|
30
|
+
* text of its own, so the note ("<emoji> tapback on message <id>") is what the
|
|
31
|
+
* central pipeline routes through dispatchTurn so the agent has context.
|
|
32
|
+
*/
|
|
33
|
+
export function buildBlueBubblesReactionNote(emoji, targetMessageId) {
|
|
34
|
+
const target = targetMessageId.trim();
|
|
35
|
+
return target
|
|
36
|
+
? `Reacted ${emoji} to message ${target}.`
|
|
37
|
+
: `Reacted ${emoji} to a message.`;
|
|
38
|
+
}
|
|
39
|
+
/** Build the capability flags for the account's action toggles + Private-API status. */
|
|
40
|
+
function buildCapabilities(actions, privateApi) {
|
|
41
|
+
const richAvailable = privateApi === true;
|
|
42
|
+
return {
|
|
43
|
+
chatTypes: ["direct", "group"],
|
|
44
|
+
media: true,
|
|
45
|
+
reply: true,
|
|
46
|
+
reactions: actions.reactions && richAvailable,
|
|
47
|
+
edit: actions.edit && richAvailable,
|
|
48
|
+
unsend: actions.unsend && richAvailable,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
async function loadStartConfig() {
|
|
52
|
+
return loadConfig();
|
|
53
|
+
}
|
|
54
|
+
export function createBlueBubblesAdapter(opts = {}) {
|
|
55
|
+
const accountId = opts.accountId?.trim() || BLUEBUBBLES_DEFAULT_ACCOUNT_ID;
|
|
56
|
+
const connectImpl = opts.connectImpl ?? connectBlueBubbles;
|
|
57
|
+
const probeImpl = opts.probeImpl ?? probeBlueBubbles;
|
|
58
|
+
let connection = null;
|
|
59
|
+
let lastConfig = null;
|
|
60
|
+
let lastEnv = process.env;
|
|
61
|
+
let started = false;
|
|
62
|
+
let privateApi = null;
|
|
63
|
+
let macOSMajor = null;
|
|
64
|
+
let selfHandle = "";
|
|
65
|
+
let actions = {
|
|
66
|
+
reactions: true,
|
|
67
|
+
edit: true,
|
|
68
|
+
unsend: true,
|
|
69
|
+
effects: true,
|
|
70
|
+
groupAdmin: true,
|
|
71
|
+
};
|
|
72
|
+
const adapter = {
|
|
73
|
+
id: BLUEBUBBLES_CHANNEL_ID,
|
|
74
|
+
label: "BlueBubbles",
|
|
75
|
+
isConfigured(cfg, env) {
|
|
76
|
+
lastConfig = cfg;
|
|
77
|
+
lastEnv = env ?? process.env;
|
|
78
|
+
if (!bluebubblesChannelEnabled(cfg))
|
|
79
|
+
return false;
|
|
80
|
+
// Need a resolvable server URL + password.
|
|
81
|
+
if (!resolveBlueBubblesServerUrl(cfg, accountId, env ?? process.env))
|
|
82
|
+
return false;
|
|
83
|
+
if (!resolveBlueBubblesPassword(cfg, accountId, env ?? process.env))
|
|
84
|
+
return false;
|
|
85
|
+
// Multi-account: the plugin path owns lifecycle when >1 account; the
|
|
86
|
+
// legacy single adapter steps aside.
|
|
87
|
+
const isLegacyAdapter = accountId === BLUEBUBBLES_DEFAULT_ACCOUNT_ID;
|
|
88
|
+
if (isLegacyAdapter && listBlueBubblesAccountIds(cfg).length > 1)
|
|
89
|
+
return false;
|
|
90
|
+
return true;
|
|
91
|
+
},
|
|
92
|
+
async start(ctx) {
|
|
93
|
+
const cfg = lastConfig ?? (await loadStartConfig());
|
|
94
|
+
const account = resolveBlueBubblesAccount(cfg, accountId, lastEnv);
|
|
95
|
+
actions = account.actions;
|
|
96
|
+
selfHandle = account.selfHandle;
|
|
97
|
+
// Probe once at start to detect Private-API status (gates rich actions +
|
|
98
|
+
// honest capabilities). Never throws.
|
|
99
|
+
try {
|
|
100
|
+
const probe = await probeImpl({
|
|
101
|
+
serverUrl: account.serverUrl,
|
|
102
|
+
password: account.password,
|
|
103
|
+
timeoutMs: account.probeTimeoutMs,
|
|
104
|
+
});
|
|
105
|
+
privateApi = probe.privateApi;
|
|
106
|
+
macOSMajor = probe.macOSMajor ?? null;
|
|
107
|
+
if (!probe.ok)
|
|
108
|
+
ctx.log(`BlueBubbles probe failed: ${probe.error ?? "unknown"}`);
|
|
109
|
+
}
|
|
110
|
+
catch {
|
|
111
|
+
privateApi = null;
|
|
112
|
+
macOSMajor = null;
|
|
113
|
+
}
|
|
114
|
+
try {
|
|
115
|
+
connection = connectImpl({
|
|
116
|
+
account,
|
|
117
|
+
log: ctx.log,
|
|
118
|
+
privateApi,
|
|
119
|
+
macOSMajor,
|
|
120
|
+
onMessage: (msg) => {
|
|
121
|
+
void ctx.onInbound({
|
|
122
|
+
channel: BLUEBUBBLES_CHANNEL_ID,
|
|
123
|
+
accountId,
|
|
124
|
+
conversationId: msg.conversationId,
|
|
125
|
+
...(msg.messageGuid ? { messageId: msg.messageGuid } : {}),
|
|
126
|
+
...(msg.timestampMs !== undefined ? { messageTimestampMs: msg.timestampMs } : {}),
|
|
127
|
+
from: msg.from,
|
|
128
|
+
...(msg.fromName !== undefined ? { fromName: msg.fromName } : {}),
|
|
129
|
+
text: msg.text,
|
|
130
|
+
chatType: msg.isGroup ? "group" : "direct",
|
|
131
|
+
isGroup: msg.isGroup,
|
|
132
|
+
...(msg.replyToGuid ? { replyTo: { messageId: msg.replyToGuid } } : {}),
|
|
133
|
+
...(msg.mentions && msg.mentions.length > 0 ? { mentions: msg.mentions } : {}),
|
|
134
|
+
...(msg.resolveMedia ? { resolveMedia: msg.resolveMedia } : {}),
|
|
135
|
+
raw: msg.raw,
|
|
136
|
+
});
|
|
137
|
+
},
|
|
138
|
+
// Inbound tapback → synthesise a short note + a `reaction` event and
|
|
139
|
+
// route it through the SAME inbound pipeline as a normal message so
|
|
140
|
+
// the access gate + routing apply uniformly. Only "added" tapbacks
|
|
141
|
+
// wake the agent (a removal is noise); the connection already gates
|
|
142
|
+
// this on `actions.reactions`.
|
|
143
|
+
onTapback: (note) => {
|
|
144
|
+
if (note.action !== "added")
|
|
145
|
+
return;
|
|
146
|
+
const targetId = note.targetGuid ?? "";
|
|
147
|
+
void ctx.onInbound({
|
|
148
|
+
channel: BLUEBUBBLES_CHANNEL_ID,
|
|
149
|
+
accountId,
|
|
150
|
+
conversationId: note.conversationId,
|
|
151
|
+
from: note.from,
|
|
152
|
+
text: buildBlueBubblesReactionNote(note.emoji, targetId),
|
|
153
|
+
chatType: note.isGroup ? "group" : "direct",
|
|
154
|
+
isGroup: note.isGroup,
|
|
155
|
+
reaction: { emojis: [note.emoji], targetMessageId: targetId },
|
|
156
|
+
});
|
|
157
|
+
},
|
|
158
|
+
});
|
|
159
|
+
started = true;
|
|
160
|
+
ctx.log("BlueBubbles connected");
|
|
161
|
+
ctx.onConnected?.();
|
|
162
|
+
// On (re)connect, backfill recently-missed messages so nothing is lost
|
|
163
|
+
// across a Brigade restart. Each fetched message replays through the
|
|
164
|
+
// SAME normalize+dedupe path as a live webhook, so a message already
|
|
165
|
+
// delivered live is dropped at dedupe (no double-delivery). Fire-and-
|
|
166
|
+
// forget + never throws — a backfill failure never blocks the channel.
|
|
167
|
+
void connection
|
|
168
|
+
?.runCatchup()
|
|
169
|
+
.then((summary) => {
|
|
170
|
+
if (summary.replayed > 0 || summary.fetched > 0) {
|
|
171
|
+
ctx.log(`BlueBubbles catch-up: fetched ${summary.fetched}, replayed ${summary.replayed}`);
|
|
172
|
+
}
|
|
173
|
+
})
|
|
174
|
+
.catch((err) => ctx.log(`BlueBubbles catch-up failed: ${err instanceof Error ? err.message : String(err)}`));
|
|
175
|
+
}
|
|
176
|
+
catch (err) {
|
|
177
|
+
started = false;
|
|
178
|
+
ctx.log(`BlueBubbles failed to start: ${err instanceof Error ? err.message : String(err)}`);
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
async stop() {
|
|
182
|
+
connection?.close();
|
|
183
|
+
connection = null;
|
|
184
|
+
started = false;
|
|
185
|
+
},
|
|
186
|
+
health() {
|
|
187
|
+
if (!connection || !started) {
|
|
188
|
+
return { ok: false, kind: "starting", reason: "BlueBubbles adapter is not started yet." };
|
|
189
|
+
}
|
|
190
|
+
return { ok: true };
|
|
191
|
+
},
|
|
192
|
+
feedWebhookEvent(eventType, payload) {
|
|
193
|
+
if (!connection)
|
|
194
|
+
return;
|
|
195
|
+
connection.feedWebhookEvent(eventType, payload);
|
|
196
|
+
},
|
|
197
|
+
async sendText(conversationId, text, opts) {
|
|
198
|
+
if (!connection)
|
|
199
|
+
throw new Error("BlueBubbles channel is not started");
|
|
200
|
+
// Chunk very long replies first (bubble-split happens inside the connection).
|
|
201
|
+
const chunks = chunkText(text, { limit: BLUEBUBBLES_TEXT_LIMIT });
|
|
202
|
+
let first = true;
|
|
203
|
+
let lastMessageId;
|
|
204
|
+
for (const chunk of chunks) {
|
|
205
|
+
if (!chunk.trim())
|
|
206
|
+
continue;
|
|
207
|
+
const replyOpt = first && opts?.replyToId ? { replyToId: opts.replyToId } : {};
|
|
208
|
+
const sent = await connection.sendText(conversationId, chunk, { ...replyOpt });
|
|
209
|
+
if (sent.messageId)
|
|
210
|
+
lastMessageId = sent.messageId;
|
|
211
|
+
first = false;
|
|
212
|
+
}
|
|
213
|
+
return lastMessageId ? { messageId: lastMessageId } : undefined;
|
|
214
|
+
},
|
|
215
|
+
async sendMedia(conversationId, media) {
|
|
216
|
+
if (!connection)
|
|
217
|
+
throw new Error("BlueBubbles channel is not started");
|
|
218
|
+
// Per-op gate: the operator can disable attachment sends independently.
|
|
219
|
+
if (!isBlueBubblesOpAllowed(actions, "sendAttachment")) {
|
|
220
|
+
throw new Error("BlueBubbles attachment sends are disabled (channels.bluebubbles.actions.sendAttachment = false)");
|
|
221
|
+
}
|
|
222
|
+
const sent = await connection.sendMedia(conversationId, media);
|
|
223
|
+
return sent.messageId ? { messageId: sent.messageId } : undefined;
|
|
224
|
+
},
|
|
225
|
+
// Read-receipt hook the pipeline calls after the access gate admits a
|
|
226
|
+
// sender (so a challenged stranger never sees a read receipt first).
|
|
227
|
+
// Cosmetic + best-effort: no-ops when the Private API is off, swallows
|
|
228
|
+
// transport errors (the pipeline already wraps this in a try/catch).
|
|
229
|
+
async markRead(conversationId) {
|
|
230
|
+
if (!connection)
|
|
231
|
+
return;
|
|
232
|
+
await connection.markRead(conversationId);
|
|
233
|
+
},
|
|
234
|
+
// Typing-indicator hook the pipeline calls around a turn so the user sees
|
|
235
|
+
// "typing…" while the agent thinks. Cosmetic; no-ops when the Private API
|
|
236
|
+
// is off. `composing` → start, `paused` → stop.
|
|
237
|
+
async setComposing(conversationId, state) {
|
|
238
|
+
if (!connection)
|
|
239
|
+
return;
|
|
240
|
+
await connection.setTyping(conversationId, state === "composing");
|
|
241
|
+
},
|
|
242
|
+
// The bot's own iMessage handle (when the operator configured
|
|
243
|
+
// `channels.bluebubbles.selfHandle`) — lets the central pipeline's group
|
|
244
|
+
// mention-gating match `msg.mentions` against the bot. Undefined when unset.
|
|
245
|
+
selfId() {
|
|
246
|
+
return selfHandle || undefined;
|
|
247
|
+
},
|
|
248
|
+
connectedAt() {
|
|
249
|
+
return connection?.connectedAt() ?? null;
|
|
250
|
+
},
|
|
251
|
+
// The BlueBubbles bot runs AS the operator's signed-in Messages.app, so the
|
|
252
|
+
// pairing card uses the "account" label.
|
|
253
|
+
pairing: { idLabel: "account" },
|
|
254
|
+
setup: {
|
|
255
|
+
credentialKeys: [
|
|
256
|
+
{
|
|
257
|
+
key: "serverUrl",
|
|
258
|
+
prompt: "BlueBubbles server URL (e.g. http://192.168.1.5:1234). Find it in the BlueBubbles Server app under Connection.",
|
|
259
|
+
secret: false,
|
|
260
|
+
envVar: "BLUEBUBBLES_SERVER_URL",
|
|
261
|
+
},
|
|
262
|
+
{
|
|
263
|
+
key: "password",
|
|
264
|
+
prompt: "BlueBubbles server password (BlueBubbles Server app → Settings).",
|
|
265
|
+
secret: true,
|
|
266
|
+
envVar: "BLUEBUBBLES_PASSWORD",
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
key: "webhookPath",
|
|
270
|
+
prompt: "Inbound webhook path (optional; default /bluebubbles/webhook). After setup, add your gateway URL + this path in BlueBubbles Server → Settings → Webhooks and enable it (e.g. https://your-gateway-host:3000/bluebubbles/webhook).",
|
|
271
|
+
secret: false,
|
|
272
|
+
},
|
|
273
|
+
{
|
|
274
|
+
key: "dmPolicy",
|
|
275
|
+
prompt: "Who may DM this account? pairing (owner + approved; strangers challenged — default) / allowlist (only allowFrom) / open (anyone) / disabled (drop all DMs). Leave blank for pairing.",
|
|
276
|
+
secret: false,
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
key: "allowFrom",
|
|
280
|
+
prompt: "Allowlist of senders for allowlist mode — handles or chat targets, comma-separated (e.g. +15555550123, user@example.com, chat_id:123). Leave blank for none.",
|
|
281
|
+
secret: false,
|
|
282
|
+
},
|
|
283
|
+
],
|
|
284
|
+
validateInput(key, value) {
|
|
285
|
+
const v = value.trim();
|
|
286
|
+
if (key === "serverUrl" && v && !/^https?:\/\//i.test(v) && !/^[\w.-]+(:\d+)?$/.test(v)) {
|
|
287
|
+
return "Server URL must be a host or http(s):// URL.";
|
|
288
|
+
}
|
|
289
|
+
if (key === "webhookPath" && v && !v.startsWith("/")) {
|
|
290
|
+
return "Webhook path must start with /.";
|
|
291
|
+
}
|
|
292
|
+
if (key === "dmPolicy" && v && !["pairing", "allowlist", "open", "disabled"].includes(v.toLowerCase())) {
|
|
293
|
+
return "dmPolicy must be one of: pairing, allowlist, open, disabled.";
|
|
294
|
+
}
|
|
295
|
+
return null;
|
|
296
|
+
},
|
|
297
|
+
buildAccountConfig(values) {
|
|
298
|
+
const out = { enabled: true };
|
|
299
|
+
const serverUrl = (values.serverUrl ?? "").trim();
|
|
300
|
+
if (serverUrl)
|
|
301
|
+
out.serverUrl = serverUrl;
|
|
302
|
+
const webhookPath = (values.webhookPath ?? "").trim();
|
|
303
|
+
if (webhookPath)
|
|
304
|
+
out.webhookPath = webhookPath;
|
|
305
|
+
const dmPolicy = (values.dmPolicy ?? "").trim().toLowerCase();
|
|
306
|
+
if (dmPolicy && dmPolicy !== "pairing")
|
|
307
|
+
out.dmPolicy = dmPolicy;
|
|
308
|
+
const allowFrom = (values.allowFrom ?? "")
|
|
309
|
+
.split(/[\n,]+/g)
|
|
310
|
+
.map((s) => s.trim())
|
|
311
|
+
.filter(Boolean);
|
|
312
|
+
if (allowFrom.length > 0)
|
|
313
|
+
out.allowFrom = allowFrom;
|
|
314
|
+
return out;
|
|
315
|
+
},
|
|
316
|
+
},
|
|
317
|
+
get capabilities() {
|
|
318
|
+
return buildCapabilities(actions, privateApi);
|
|
319
|
+
},
|
|
320
|
+
async handleAction(params) {
|
|
321
|
+
if (!connection)
|
|
322
|
+
return { ok: false, error: "BlueBubbles channel is not started" };
|
|
323
|
+
const action = params.action;
|
|
324
|
+
try {
|
|
325
|
+
switch (action.kind) {
|
|
326
|
+
case "react": {
|
|
327
|
+
if (!actions.reactions || privateApi !== true) {
|
|
328
|
+
return { ok: false, error: "BlueBubbles reactions require the Private API" };
|
|
329
|
+
}
|
|
330
|
+
await connection.react({
|
|
331
|
+
conversationId: params.conversationId,
|
|
332
|
+
messageId: action.messageId,
|
|
333
|
+
reaction: action.emoji,
|
|
334
|
+
});
|
|
335
|
+
return { ok: true };
|
|
336
|
+
}
|
|
337
|
+
case "edit": {
|
|
338
|
+
if (!actions.edit || privateApi !== true) {
|
|
339
|
+
return { ok: false, error: "BlueBubbles message edit requires the Private API" };
|
|
340
|
+
}
|
|
341
|
+
// macOS 26+ removed iMessage message EDIT — refuse cleanly.
|
|
342
|
+
if (isMacOSEditUnsupported(macOSMajor)) {
|
|
343
|
+
return { ok: false, error: "message edit isn't supported on macOS 26+" };
|
|
344
|
+
}
|
|
345
|
+
await connection.edit({ messageId: action.messageId, text: action.text });
|
|
346
|
+
return { ok: true };
|
|
347
|
+
}
|
|
348
|
+
case "delete": {
|
|
349
|
+
if (!actions.unsend || privateApi !== true) {
|
|
350
|
+
return { ok: false, error: "BlueBubbles unsend requires the Private API" };
|
|
351
|
+
}
|
|
352
|
+
await connection.unsend({ messageId: action.messageId });
|
|
353
|
+
return { ok: true };
|
|
354
|
+
}
|
|
355
|
+
default:
|
|
356
|
+
return { ok: false, error: `BlueBubbles does not support the "${action.kind}" action` };
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
catch (err) {
|
|
360
|
+
return { ok: false, error: err instanceof Error ? err.message : String(err) };
|
|
361
|
+
}
|
|
362
|
+
},
|
|
363
|
+
};
|
|
364
|
+
return adapter;
|
|
365
|
+
}
|
|
366
|
+
//# sourceMappingURL=adapter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"adapter.js","sourceRoot":"","sources":["../../../../src/agents/channels/bluebubbles/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueBubbles catch-up cursor persistence.
|
|
3
|
+
*
|
|
4
|
+
* Catch-up recovers messages delivered while Brigade was down. A FIXED lookback
|
|
5
|
+
* window re-scans the same span every startup (re-deduping the overlap each
|
|
6
|
+
* time) and, worse, permanently loses anything older than the window when an
|
|
7
|
+
* outage runs long. A persisted per-account CURSOR (`lastSeenMs`) fixes both:
|
|
8
|
+
* each run queries strictly AFTER the last run's high-water mark, advances the
|
|
9
|
+
* cursor to "now" on success, holds it just before a still-retrying failure, and
|
|
10
|
+
* force-advances past a message that has failed `maxFailureRetries` times (so one
|
|
11
|
+
* malformed record can't wedge catch-up forever).
|
|
12
|
+
*
|
|
13
|
+
* The cursor is small per-account JSON under the channel's OWN state dir
|
|
14
|
+
* (`~/.brigade/channels/bluebubbles/accounts/<id>/catchup-cursor.json`) — no
|
|
15
|
+
* central store. Reads/writes are best-effort + total: a missing/garbage file
|
|
16
|
+
* loads as `null` (→ first-run lookback), a write failure is swallowed (the next
|
|
17
|
+
* run simply re-scans). `fs` is INJECTABLE for tests.
|
|
18
|
+
*/
|
|
19
|
+
/** The persisted cursor shape. */
|
|
20
|
+
export interface BlueBubblesCatchupCursor {
|
|
21
|
+
/** High-water mark — the newest message timestamp this account has processed (epoch ms). */
|
|
22
|
+
lastSeenMs: number;
|
|
23
|
+
/** When the cursor was last written (epoch ms). */
|
|
24
|
+
updatedAt: number;
|
|
25
|
+
/**
|
|
26
|
+
* Per-message-GUID consecutive-failure counter, preserved across runs. An
|
|
27
|
+
* entry with `count >= maxFailureRetries` means catch-up has GIVEN UP on that
|
|
28
|
+
* GUID: it is skipped on sight and no longer holds the cursor back. A
|
|
29
|
+
* successful replay drops the entry. Optional so older cursor files load.
|
|
30
|
+
*/
|
|
31
|
+
failureRetries?: Record<string, number>;
|
|
32
|
+
}
|
|
33
|
+
/** Defense-in-depth cap on the retry map size (a storm of unique failing GUIDs). */
|
|
34
|
+
export declare const BLUEBUBBLES_MAX_FAILURE_RETRY_MAP_SIZE = 5000;
|
|
35
|
+
/** A read/write seam so tests can run the cursor logic with no disk. */
|
|
36
|
+
export interface BlueBubblesCursorStore {
|
|
37
|
+
load(accountId: string): BlueBubblesCatchupCursor | null;
|
|
38
|
+
save(accountId: string, cursor: BlueBubblesCatchupCursor): void;
|
|
39
|
+
}
|
|
40
|
+
/** Resolve the per-account cursor file path under the channel's state dir. */
|
|
41
|
+
export declare function resolveCatchupCursorPath(accountId: string): string;
|
|
42
|
+
/** Drop non-positive / non-finite / non-string entries from a loaded retry map. */
|
|
43
|
+
export declare function sanitizeFailureRetries(raw: unknown): Record<string, number>;
|
|
44
|
+
/**
|
|
45
|
+
* Cap the retry map to the `maxSize` highest-count entries (closest to give-up),
|
|
46
|
+
* deterministic tiebreak on GUID. A defense-in-depth bound, not the primary prune
|
|
47
|
+
* (entries not seen in a run are dropped by the caller building a fresh map).
|
|
48
|
+
*/
|
|
49
|
+
export declare function capFailureRetriesMap(map: Record<string, number>, maxSize: number): Record<string, number>;
|
|
50
|
+
/** The default filesystem-backed cursor store (under the channel state dir). */
|
|
51
|
+
export declare const filesystemCursorStore: BlueBubblesCursorStore;
|
|
52
|
+
//# sourceMappingURL=catchup-cursor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catchup-cursor.d.ts","sourceRoot":"","sources":["../../../../src/agents/channels/bluebubbles/catchup-cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAQH,kCAAkC;AAClC,MAAM,WAAW,wBAAwB;IACxC,4FAA4F;IAC5F,UAAU,EAAE,MAAM,CAAC;IACnB,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;IAClB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,oFAAoF;AACpF,eAAO,MAAM,sCAAsC,OAAQ,CAAC;AAE5D,wEAAwE;AACxE,MAAM,WAAW,sBAAsB;IACtC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,wBAAwB,GAAG,IAAI,CAAC;IACzD,IAAI,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,wBAAwB,GAAG,IAAI,CAAC;CAChE;AAED,8EAA8E;AAC9E,wBAAgB,wBAAwB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAGlE;AAED,mFAAmF;AACnF,wBAAgB,sBAAsB,CAAC,GAAG,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS3E;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAUzG;AAED,gFAAgF;AAChF,eAAO,MAAM,qBAAqB,EAAE,sBAgDnC,CAAC"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* BlueBubbles catch-up cursor persistence.
|
|
3
|
+
*
|
|
4
|
+
* Catch-up recovers messages delivered while Brigade was down. A FIXED lookback
|
|
5
|
+
* window re-scans the same span every startup (re-deduping the overlap each
|
|
6
|
+
* time) and, worse, permanently loses anything older than the window when an
|
|
7
|
+
* outage runs long. A persisted per-account CURSOR (`lastSeenMs`) fixes both:
|
|
8
|
+
* each run queries strictly AFTER the last run's high-water mark, advances the
|
|
9
|
+
* cursor to "now" on success, holds it just before a still-retrying failure, and
|
|
10
|
+
* force-advances past a message that has failed `maxFailureRetries` times (so one
|
|
11
|
+
* malformed record can't wedge catch-up forever).
|
|
12
|
+
*
|
|
13
|
+
* The cursor is small per-account JSON under the channel's OWN state dir
|
|
14
|
+
* (`~/.brigade/channels/bluebubbles/accounts/<id>/catchup-cursor.json`) — no
|
|
15
|
+
* central store. Reads/writes are best-effort + total: a missing/garbage file
|
|
16
|
+
* loads as `null` (→ first-run lookback), a write failure is swallowed (the next
|
|
17
|
+
* run simply re-scans). `fs` is INJECTABLE for tests.
|
|
18
|
+
*/
|
|
19
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import { ensureDir, resolveChannelStateDir } from "../sdk.js";
|
|
22
|
+
import { BLUEBUBBLES_CHANNEL_ID } from "./account-config.js";
|
|
23
|
+
/** Defense-in-depth cap on the retry map size (a storm of unique failing GUIDs). */
|
|
24
|
+
export const BLUEBUBBLES_MAX_FAILURE_RETRY_MAP_SIZE = 5_000;
|
|
25
|
+
/** Resolve the per-account cursor file path under the channel's state dir. */
|
|
26
|
+
export function resolveCatchupCursorPath(accountId) {
|
|
27
|
+
const safe = accountId.replace(/[^a-zA-Z0-9_-]/g, "_") || "default";
|
|
28
|
+
return path.join(resolveChannelStateDir(BLUEBUBBLES_CHANNEL_ID), "accounts", safe, "catchup-cursor.json");
|
|
29
|
+
}
|
|
30
|
+
/** Drop non-positive / non-finite / non-string entries from a loaded retry map. */
|
|
31
|
+
export function sanitizeFailureRetries(raw) {
|
|
32
|
+
if (!raw || typeof raw !== "object")
|
|
33
|
+
return {};
|
|
34
|
+
const out = {};
|
|
35
|
+
for (const [guid, count] of Object.entries(raw)) {
|
|
36
|
+
if (!guid || typeof guid !== "string")
|
|
37
|
+
continue;
|
|
38
|
+
if (typeof count !== "number" || !Number.isFinite(count) || count <= 0)
|
|
39
|
+
continue;
|
|
40
|
+
out[guid] = Math.floor(count);
|
|
41
|
+
}
|
|
42
|
+
return out;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Cap the retry map to the `maxSize` highest-count entries (closest to give-up),
|
|
46
|
+
* deterministic tiebreak on GUID. A defense-in-depth bound, not the primary prune
|
|
47
|
+
* (entries not seen in a run are dropped by the caller building a fresh map).
|
|
48
|
+
*/
|
|
49
|
+
export function capFailureRetriesMap(map, maxSize) {
|
|
50
|
+
const entries = Object.entries(map);
|
|
51
|
+
if (entries.length <= maxSize)
|
|
52
|
+
return map;
|
|
53
|
+
entries.sort((a, b) => b[1] - a[1] || a[0].localeCompare(b[0]));
|
|
54
|
+
const capped = {};
|
|
55
|
+
for (let i = 0; i < maxSize; i++) {
|
|
56
|
+
const [guid, count] = entries[i];
|
|
57
|
+
capped[guid] = count;
|
|
58
|
+
}
|
|
59
|
+
return capped;
|
|
60
|
+
}
|
|
61
|
+
/** The default filesystem-backed cursor store (under the channel state dir). */
|
|
62
|
+
export const filesystemCursorStore = {
|
|
63
|
+
load(accountId) {
|
|
64
|
+
const file = resolveCatchupCursorPath(accountId);
|
|
65
|
+
let text;
|
|
66
|
+
try {
|
|
67
|
+
text = readFileSync(file, "utf8");
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return null; // missing → first run
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
const value = JSON.parse(text);
|
|
74
|
+
if (!value || typeof value !== "object")
|
|
75
|
+
return null;
|
|
76
|
+
if (typeof value.lastSeenMs !== "number" || !Number.isFinite(value.lastSeenMs))
|
|
77
|
+
return null;
|
|
78
|
+
const failureRetries = sanitizeFailureRetries(value.failureRetries);
|
|
79
|
+
const hasRetries = Object.keys(failureRetries).length > 0;
|
|
80
|
+
return {
|
|
81
|
+
lastSeenMs: value.lastSeenMs,
|
|
82
|
+
updatedAt: typeof value.updatedAt === "number" ? value.updatedAt : 0,
|
|
83
|
+
...(hasRetries ? { failureRetries } : {}),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null; // garbage → first run
|
|
88
|
+
}
|
|
89
|
+
},
|
|
90
|
+
save(accountId, cursor) {
|
|
91
|
+
const file = resolveCatchupCursorPath(accountId);
|
|
92
|
+
try {
|
|
93
|
+
ensureDir(path.dirname(file));
|
|
94
|
+
}
|
|
95
|
+
catch {
|
|
96
|
+
try {
|
|
97
|
+
mkdirSync(path.dirname(file), { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return; // can't make the dir → best-effort skip
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const sanitized = sanitizeFailureRetries(cursor.failureRetries);
|
|
104
|
+
const hasRetries = Object.keys(sanitized).length > 0;
|
|
105
|
+
const payload = {
|
|
106
|
+
lastSeenMs: cursor.lastSeenMs,
|
|
107
|
+
updatedAt: cursor.updatedAt,
|
|
108
|
+
...(hasRetries ? { failureRetries: sanitized } : {}),
|
|
109
|
+
};
|
|
110
|
+
try {
|
|
111
|
+
writeFileSync(file, JSON.stringify(payload), "utf8");
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* best-effort — next run re-scans */
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
//# sourceMappingURL=catchup-cursor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catchup-cursor.js","sourceRoot":"","sources":["../../../../src/agents/channels/bluebubbles/catchup-cursor.ts"],"names":[],"mappings":"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"}
|