a tool to help your Letta AI agents navigate bluesky
1import {
2 agentContext,
3 claimTaskThread,
4 releaseTaskThread,
5} from "../utils/agentContext.ts";
6import { msFrom, msUntilNextWakeWindow } from "../utils/time.ts";
7import { bsky } from "../utils/bsky.ts";
8import { processNotification } from "../utils/processNotification.ts";
9
10export const checkNotifications = async () => {
11 if (!claimTaskThread()) {
12 const newDelay = msFrom.minutes(2);
13 console.log(
14 `🔹 ${agentContext.agentBskyName} is busy, checking for notifications again in ${
15 (newDelay * 1000) * 60
16 } minutes…`,
17 );
18 // agentContext is busy, try to check notifications in 2 minutes.
19 setTimeout(checkNotifications, newDelay);
20 return;
21 }
22
23 const delay = msUntilNextWakeWindow(
24 msFrom.minutes(30),
25 msFrom.minutes(45),
26 );
27
28 if (delay !== 0) {
29 setTimeout(checkNotifications, delay);
30 console.log(
31 `🔹 ${agentContext.agentBskyName} is currently asleep. scheduling next notification check for ${
32 (delay / 1000 / 60 / 60).toFixed(2)
33 } hours from now…`,
34 );
35 agentContext.notifDelayCurrent = agentContext.notifDelayMinimum;
36 releaseTaskThread();
37 return;
38 }
39
40 try {
41 const allNotifications = await bsky.listNotifications({
42 reasons: agentContext.supportedNotifTypes,
43 limit: 50,
44 });
45
46 const startedProcessingTime: string = new Date().toISOString();
47
48 const unreadNotifications = allNotifications.data.notifications.filter(
49 (notif) => !notif.isRead,
50 );
51
52 if (unreadNotifications.length > 0) {
53 console.log(
54 `🔹 found ${unreadNotifications.length} notification(s), processing…`,
55 );
56
57 // resets delay for future notification checks since
58 // it's likely agent actions might incur new ones
59 agentContext.notifDelayCurrent = Math.max(
60 agentContext.notifDelayCurrent / 4,
61 agentContext.notifDelayMinimum,
62 );
63
64 // loop through all notifications until complete
65 //
66 let notificationCounter = 1;
67 for (const notification of unreadNotifications) {
68 console.log(
69 `🔹 processing notification #${notificationCounter} of #${unreadNotifications.length} [${notification.reason} from @${notification.author.handle}]`,
70 );
71 await processNotification(notification);
72 notificationCounter++;
73 }
74
75 // marks all notifications that were processed as seen
76 // based on time from when retrieved instead of finished
77 await bsky.updateSeenNotifications(startedProcessingTime);
78 console.log(
79 `🔹 done processing ${unreadNotifications.length} notification${
80 unreadNotifications.length > 1 ? "s" : ""
81 }…`,
82 );
83 // increases counter for notification processing session
84 agentContext.processingCount++;
85 } else {
86 // increases delay to check notifications again later
87 agentContext.notifDelayCurrent = Math.round(Math.min(
88 agentContext.notifDelayCurrent *
89 agentContext.notifDelayMultiplier,
90 agentContext.notifDelayMaximum,
91 ));
92
93 console.log(
94 "🔹 no notifications…",
95 `checking again in ${
96 (agentContext.notifDelayCurrent / 1000).toFixed(2)
97 } seconds`,
98 );
99 }
100 } catch (error) {
101 console.error("Error in checkNotifications:", error);
102 // since something went wrong, lets check for notifications again sooner
103 agentContext.notifDelayCurrent = agentContext.notifDelayMinimum;
104 } finally {
105 // increment check count
106 agentContext.checkCount++;
107 // actually schedules next time to check for notifications
108 setTimeout(checkNotifications, agentContext.notifDelayCurrent);
109 // ends work
110 releaseTaskThread();
111 }
112};