+6
main.ts
+6
main.ts
···
1
1
import { logStats } from "./tasks/logStats.ts";
2
+
import { logTasks } from "./tasks/logTasks.ts";
2
3
import { msFrom, msRandomOffset, msUntilDailyWindow } from "./utils/time.ts";
3
4
import { sendSleepMessage } from "./tasks/sendSleepMessage.ts";
4
5
import { sendWakeMessage } from "./tasks/sendWakeMessage.ts";
···
8
9
import { checkNotifications } from "./tasks/checkNotifications.ts";
9
10
10
11
setTimeout(logStats, msRandomOffset(msFrom.minutes(1), msFrom.minutes(5)));
12
+
13
+
setTimeout(
14
+
logTasks,
15
+
msRandomOffset(msFrom.minutes(30), msFrom.minutes(60)),
16
+
);
11
17
setTimeout(
12
18
sendSleepMessage,
13
19
msUntilDailyWindow(agentContext.sleepTime, 0, msFrom.minutes(20)),
+7
-5
tasks/checkBluesky.ts
+7
-5
tasks/checkBluesky.ts
···
16
16
const newDelay = msRandomOffset(msFrom.minutes(5), msFrom.minutes(10));
17
17
18
18
console.log(
19
-
`${agentContext.agentBskyName} is busy, will try checking bluesky again in ${
19
+
`🔹 ${agentContext.agentBskyName} is busy, will try checking bluesky again in ${
20
20
newDelay * 60 * 1000
21
21
} minutes…`,
22
22
);
···
27
27
28
28
if (!agentContext.proactiveEnabled) {
29
29
console.log(
30
-
`proactively checking bluesky is disabled. Provide a minimum and/or maximum delay in \`.env\` to enable this task…`,
30
+
`🔹 proactively checking bluesky is disabled. Provide a minimum and/or maximum delay in \`.env\` to enable this task…`,
31
31
);
32
32
releaseTaskThread();
33
33
return;
···
43
43
if (delay !== 0) {
44
44
setTimeout(checkBluesky, delay);
45
45
console.log(
46
-
`${agentContext.agentBskyName} is current asleep. scheduling next bluesky session for ${
46
+
`🔹 ${agentContext.agentBskyName} is current asleep. scheduling next bluesky session for ${
47
47
(delay / 1000 / 60 / 60).toFixed(2)
48
48
} hours from now…`,
49
49
);
···
53
53
54
54
try {
55
55
const prompt = checkBlueskyPrompt;
56
-
console.log("starting a proactive bluesky session…");
56
+
console.log("🔹 starting a proactive bluesky session…");
57
57
await messageAgent(prompt);
58
58
} catch (error) {
59
59
console.error("error in checkBluesky:", error);
60
60
} finally {
61
-
console.log("finished proactive bluesky session. waiting for new tasks…");
61
+
console.log(
62
+
"🔹 finished proactive bluesky session. waiting for new tasks…",
63
+
);
62
64
agentContext.proactiveCount++;
63
65
// schedules next proactive bluesky session
64
66
setTimeout(
+7
-5
tasks/checkNotifications.ts
+7
-5
tasks/checkNotifications.ts
···
15
15
if (!claimTaskThread()) {
16
16
const newDelay = msRandomOffset(msFrom.minutes(5), msFrom.minutes(10));
17
17
console.log(
18
-
`${agentContext.agentBskyName} is busy, checking for notifications again in ${
18
+
`🔹 ${agentContext.agentBskyName} is busy, checking for notifications again in ${
19
19
(newDelay * 1000) * 60
20
20
} minutes…`,
21
21
);
···
32
32
if (delay !== 0) {
33
33
setTimeout(checkNotifications, delay);
34
34
console.log(
35
-
`${agentContext.agentBskyName} is current asleep. scheduling next notification check for ${
35
+
`🔹 ${agentContext.agentBskyName} is current asleep. scheduling next notification check for ${
36
36
(delay / 1000 / 60 / 60).toFixed(2)
37
37
} hours from now…`,
38
38
);
···
54
54
55
55
if (unreadNotifications.length > 0) {
56
56
console.log(
57
-
`found ${unreadNotifications.length} notification(s), processing…`,
57
+
`🔹 found ${unreadNotifications.length} notification(s), processing…`,
58
58
);
59
59
60
60
// resets delay for future notification checks since
···
69
69
let notificationCounter = 1;
70
70
for (const notification of unreadNotifications) {
71
71
console.log(
72
-
`processing notification #${notificationCounter} [${notification.reason} from @${notification.author.handle}]`,
72
+
`🔹 processing notification #${notificationCounter} of #${unreadNotifications.length} [${notification.reason} from @${notification.author.handle}]`,
73
73
);
74
74
await processNotification(notification);
75
75
notificationCounter++;
···
90
90
));
91
91
92
92
console.log(
93
-
"no notifications…",
93
+
"🔹 no notifications…",
94
94
`checking again in ${
95
95
(agentContext.notifDelayCurrent / 1000).toFixed(2)
96
96
} seconds`,
···
101
101
// since something went wrong, lets check for notifications again sooner
102
102
agentContext.notifDelayCurrent = agentContext.notifDelayMinimum;
103
103
} finally {
104
+
// increment check count
105
+
agentContext.checkCount++;
104
106
// actually schedules next time to check for notifications
105
107
setTimeout(checkNotifications, agentContext.notifDelayCurrent);
106
108
// ends work
+74
-33
tasks/logStats.ts
+74
-33
tasks/logStats.ts
···
11
11
12
12
export const logStats = () => {
13
13
if (!claimTaskThread()) {
14
-
const newDelay = msRandomOffset(msFrom.minutes(5), msFrom.minutes(10));
14
+
const newDelay = msFrom.minutes(2);
15
15
console.log(
16
-
`${agentContext.agentBskyName} is busy, attempting to log counts again in ${
16
+
`Stat log attempt failed, ${agentContext.agentBskyName} is busy. Next attempt in ${
17
17
(newDelay / 1000) / 60
18
18
} minutes…`,
19
19
);
20
20
// session is busy, logging stats in 5~10 minutes.
21
21
setTimeout(logStats, newDelay);
22
-
return;
23
-
}
24
-
25
-
if (!agentContext.reflectionEnabled) {
26
-
console.log(
27
-
`${agentContext.agentBskyName} reflection is disabled, skipping logStats…`,
28
-
);
29
-
releaseTaskThread();
30
22
return;
31
23
}
32
24
···
46
38
return;
47
39
}
48
40
49
-
console.log(
50
-
`
51
-
===
52
-
# current session interaction counts since last reflection:
53
-
${agentContext.likeCount} ${
54
-
agentContext.likeCount === 1 ? "like" : "likes"
55
-
}, ${agentContext.repostCount} ${
56
-
agentContext.repostCount === 1 ? "repost" : "reposts"
57
-
}, ${agentContext.followCount} ${
58
-
agentContext.followCount === 1 ? "new follower" : "new followers"
59
-
}, ${agentContext.mentionCount} ${
60
-
agentContext.mentionCount === 1 ? "mention" : "mentions"
61
-
}, ${agentContext.replyCount} ${
62
-
agentContext.replyCount === 1 ? "reply" : "replies"
63
-
}, and ${agentContext.quoteCount} ${
64
-
agentContext.quoteCount === 1 ? "quote" : "quotes"
65
-
}.
41
+
// Check if there are any notifications
42
+
const totalNotifications = agentContext.mentionCount +
43
+
agentContext.likeCount +
44
+
agentContext.repostCount +
45
+
agentContext.quoteCount +
46
+
agentContext.replyCount +
47
+
agentContext.followCount;
66
48
67
-
${agentContext.agentBskyName} has reflected ${agentContext.reflectionCount} time${
68
-
agentContext.reflectionCount > 0 ? "s" : ""
69
-
} since last server start. interaction counts reset after each reflection session.
70
-
===
71
-
`,
49
+
const nextCheckDelay = msRandomOffset(
50
+
msFrom.minutes(5),
51
+
msFrom.minutes(15),
72
52
);
73
-
setTimeout(logStats, msRandomOffset(msFrom.minutes(5), msFrom.minutes(15)));
53
+
const nextCheckMinutes = ((nextCheckDelay / 1000) / 60).toFixed(1);
54
+
55
+
if (totalNotifications <= 0) {
56
+
console.log(
57
+
`no engagement stats yet... next check in ${nextCheckMinutes} minutes…`,
58
+
);
59
+
} else {
60
+
const counts = [];
61
+
62
+
if (agentContext.mentionCount > 0) {
63
+
counts.push(
64
+
`${agentContext.mentionCount} ${
65
+
agentContext.mentionCount === 1 ? "mention" : "mentions"
66
+
}`,
67
+
);
68
+
}
69
+
if (agentContext.likeCount > 0) {
70
+
counts.push(
71
+
`${agentContext.likeCount} ${
72
+
agentContext.likeCount === 1 ? "like" : "likes"
73
+
}`,
74
+
);
75
+
}
76
+
if (agentContext.repostCount > 0) {
77
+
counts.push(
78
+
`${agentContext.repostCount} ${
79
+
agentContext.repostCount === 1 ? "repost" : "reposts"
80
+
}`,
81
+
);
82
+
}
83
+
if (agentContext.quoteCount > 0) {
84
+
counts.push(
85
+
`${agentContext.quoteCount} ${
86
+
agentContext.quoteCount === 1 ? "quote" : "quotes"
87
+
}`,
88
+
);
89
+
}
90
+
if (agentContext.replyCount > 0) {
91
+
counts.push(
92
+
`${agentContext.replyCount} ${
93
+
agentContext.replyCount === 1 ? "reply" : "replies"
94
+
}`,
95
+
);
96
+
}
97
+
if (agentContext.followCount > 0) {
98
+
counts.push(
99
+
`${agentContext.followCount} new ${
100
+
agentContext.followCount === 1 ? "follower" : "followers"
101
+
}`,
102
+
);
103
+
}
104
+
105
+
const message = counts.join(", ");
106
+
const suffix = agentContext.reflectionEnabled
107
+
? " since last reflection"
108
+
: "";
109
+
console.log(
110
+
`
111
+
stats: ${message}${suffix}. next check in ${nextCheckMinutes} minutes…`,
112
+
);
113
+
}
114
+
setTimeout(logStats, nextCheckDelay);
74
115
releaseTaskThread();
75
116
};
+99
tasks/logTasks.ts
+99
tasks/logTasks.ts
···
1
+
import {
2
+
agentContext,
3
+
claimTaskThread,
4
+
releaseTaskThread,
5
+
} from "../utils/agentContext.ts";
6
+
import {
7
+
formatUptime,
8
+
msFrom,
9
+
msRandomOffset,
10
+
msUntilNextWakeWindow,
11
+
} from "../utils/time.ts";
12
+
13
+
// Capture server start time when module is loaded
14
+
const serverStartTime = Date.now();
15
+
16
+
export const logTasks = () => {
17
+
if (!claimTaskThread()) {
18
+
const newDelay = msFrom.minutes(2);
19
+
console.log(
20
+
`🔹 Task log attempt failed, ${agentContext.agentBskyName} is busy. Next attempt in ${
21
+
(newDelay / 1000) / 60
22
+
} minutes…`,
23
+
);
24
+
setTimeout(logTasks, newDelay);
25
+
return;
26
+
}
27
+
28
+
const delay = msUntilNextWakeWindow(
29
+
msFrom.minutes(30),
30
+
msFrom.hours(1),
31
+
);
32
+
33
+
if (delay !== 0) {
34
+
setTimeout(logTasks, delay);
35
+
console.log(
36
+
`🔹 ${agentContext.agentBskyName} is current asleep. scheduling next task log for ${
37
+
(delay / 1000 / 60 / 60).toFixed(2)
38
+
} hours from now…`,
39
+
);
40
+
releaseTaskThread();
41
+
return;
42
+
}
43
+
44
+
// Check if there's any activity
45
+
const totalActivity = agentContext.reflectionCount +
46
+
agentContext.checkCount +
47
+
agentContext.processingCount +
48
+
agentContext.proactiveCount;
49
+
50
+
const uptime = Date.now() - serverStartTime;
51
+
const uptimeFormatted = formatUptime(uptime);
52
+
53
+
const nextCheckDelay = msRandomOffset(
54
+
msFrom.minutes(30),
55
+
msFrom.hours(1),
56
+
);
57
+
const nextCheckMinutes = ((nextCheckDelay / 1000) / 60).toFixed(1);
58
+
59
+
if (totalActivity <= 0) {
60
+
console.log(
61
+
`🔹 no activity yet... uptime: ${uptimeFormatted}. next log in ${nextCheckMinutes} minutes`,
62
+
);
63
+
} else {
64
+
const actions = [];
65
+
66
+
if (agentContext.reflectionCount > 0) {
67
+
const times = agentContext.reflectionCount === 1
68
+
? "once"
69
+
: `${agentContext.reflectionCount} times`;
70
+
actions.push(`reflected ${times}`);
71
+
}
72
+
if (agentContext.checkCount > 0) {
73
+
const times = agentContext.checkCount === 1
74
+
? "once"
75
+
: `${agentContext.checkCount} times`;
76
+
actions.push(`checked notifications ${times}`);
77
+
}
78
+
if (agentContext.processingCount > 0) {
79
+
const times = agentContext.processingCount === 1
80
+
? "once"
81
+
: `${agentContext.processingCount} times`;
82
+
actions.push(`found and processed notifications ${times}`);
83
+
}
84
+
if (agentContext.proactiveCount > 0) {
85
+
const times = agentContext.proactiveCount === 1
86
+
? "once"
87
+
: `${agentContext.proactiveCount} times`;
88
+
actions.push(`proactively used bluesky ${times}`);
89
+
}
90
+
91
+
const message = actions.join(", ");
92
+
console.log(
93
+
`🔹 ${message}. uptime: ${uptimeFormatted}. next log in ${nextCheckMinutes} minutes`,
94
+
);
95
+
}
96
+
97
+
setTimeout(logTasks, nextCheckDelay);
98
+
releaseTaskThread();
99
+
};
+5
-5
tasks/runReflection.ts
+5
-5
tasks/runReflection.ts
···
17
17
const newDelay = msRandomOffset(msFrom.minutes(5), msFrom.minutes(10));
18
18
19
19
console.log(
20
-
`${agentContext.agentBskyName} is busy, will try reflecting again in ${
20
+
`🔹 ${agentContext.agentBskyName} is busy, will try reflecting again in ${
21
21
(newDelay / 1000) / 60
22
22
} minutes…`,
23
23
);
···
28
28
29
29
if (!agentContext.reflectionEnabled) {
30
30
console.log(
31
-
`Reflection is currently disabled. Provide a minimum and/or maximum delay duration in \`.env\` to enable reflections…`,
31
+
`🔹 Reflection is currently disabled. Provide a minimum and/or maximum delay duration in \`.env\` to enable reflections…`,
32
32
);
33
33
releaseTaskThread();
34
34
return;
···
44
44
if (delay !== 0) {
45
45
setTimeout(runReflection, delay);
46
46
console.log(
47
-
`${agentContext.agentBskyName} is current asleep. scheduling next reflection for ${
47
+
`🔹 ${agentContext.agentBskyName} is current asleep. scheduling next reflection for ${
48
48
(delay / 1000 / 60 / 60).toFixed(2)
49
49
} hours from now…`,
50
50
);
···
53
53
}
54
54
55
55
try {
56
-
console.log("starting reflection prompt…");
56
+
console.log("🔹 starting reflection prompt…");
57
57
await messageAgent(reflectionPrompt);
58
58
} catch (error) {
59
59
console.error("Error in reflectionCheck:", error);
···
61
61
resetAgentContextCounts();
62
62
agentContext.reflectionCount++;
63
63
console.log(
64
-
"finished reflection prompt. returning to checking for notifications…",
64
+
"🔹 finished reflection prompt. returning to checking for notifications…",
65
65
);
66
66
// schedules the next reflection, random between the min and max delay
67
67
setTimeout(
+5
-5
tasks/sendSleepMessage.ts
+5
-5
tasks/sendSleepMessage.ts
···
16
16
if (!claimTaskThread()) {
17
17
const newDelay = msRandomOffset(msFrom.minutes(5), msFrom.minutes(10));
18
18
console.log(
19
-
`${agentContext.agentBskyName} is busy, sending sleep message again in ${
19
+
`🔹 ${agentContext.agentBskyName} is busy, sending sleep message again in ${
20
20
(newDelay / 1000) / 60
21
21
} minutes…`,
22
22
);
···
27
27
28
28
if (!agentContext.sleepEnabled) {
29
29
console.log(
30
-
`${agentContext.agentBskyName} is not enabled for sleep mode. Opting out of sleep messaging…`,
30
+
`🔹 ${agentContext.agentBskyName} is not enabled for sleep mode. Opting out of sleep messaging…`,
31
31
);
32
32
releaseTaskThread();
33
33
return;
···
36
36
const now = getNow();
37
37
38
38
if (now.hour >= agentContext.sleepTime) {
39
-
console.log(`attempting to wind down ${agentContext.agentBskyName}`);
39
+
console.log(`🔹 attempting to wind down ${agentContext.agentBskyName}`);
40
40
} else {
41
41
const delay = msUntilDailyWindow(
42
42
agentContext.sleepTime,
···
45
45
);
46
46
setTimeout(sendSleepMessage, delay);
47
47
console.log(
48
-
`It's too early to wind down ${agentContext.agentBskyName}. scheduling wind down for ${
48
+
`🔹 It's too early to wind down ${agentContext.agentBskyName}. scheduling wind down for ${
49
49
(delay / 1000 / 60 / 60).toFixed(2)
50
50
} hours from now…`,
51
51
);
···
58
58
} catch (error) {
59
59
console.error("error in sendSleepMessage: ", error);
60
60
} finally {
61
-
console.log("wind down attempt processed, scheduling next wind down…");
61
+
console.log("🔹 wind down attempt processed, scheduling next wind down…");
62
62
setTimeout(
63
63
sendSleepMessage,
64
64
msUntilDailyWindow(agentContext.sleepTime, 0, msFrom.minutes(20)),
+6
-6
tasks/sendWakeMessage.ts
+6
-6
tasks/sendWakeMessage.ts
···
12
12
if (!claimTaskThread()) {
13
13
const newDelay = msRandomOffset(msFrom.minutes(5), msFrom.minutes(10));
14
14
console.log(
15
-
`${agentContext.agentBskyName} is busy, sending wake message again in ${
15
+
`🔹 ${agentContext.agentBskyName} is busy, sending wake message again in ${
16
16
(newDelay / 1000) / 60
17
17
} minutes…`,
18
18
);
···
23
23
24
24
if (!agentContext.sleepEnabled) {
25
25
console.log(
26
-
`${agentContext.agentBskyName} is not enabled for sleep mode. Opting out of wake messaging…`,
26
+
`🔹 ${agentContext.agentBskyName} is not enabled for sleep mode. Opting out of wake messaging…`,
27
27
);
28
28
releaseTaskThread();
29
29
return;
···
32
32
const now = getNow();
33
33
34
34
if (now.hour >= agentContext.wakeTime && now.hour < agentContext.sleepTime) {
35
-
console.log(`attempting to wake up ${agentContext.agentBskyName}`);
35
+
console.log(`🔹 attempting to wake up ${agentContext.agentBskyName}`);
36
36
} else {
37
37
const delay = msUntilDailyWindow(
38
38
agentContext.wakeTime,
···
41
41
);
42
42
setTimeout(sendWakeMessage, delay);
43
43
console.log(
44
-
`${agentContext.agentBskyName} should still be asleep. Scheduling wake message for ${
44
+
`🔹 ${agentContext.agentBskyName} should still be asleep. Scheduling wake message for ${
45
45
(delay / 1000 / 60 / 60).toFixed(2)
46
46
} hours from now…`,
47
47
);
···
54
54
} catch (error) {
55
55
console.error("error in sendWakeMessage: ", error);
56
56
} finally {
57
-
console.log("wake attempt processed, scheduling next wake prompt…");
57
+
console.log("🔹 wake attempt processed, scheduling next wake prompt…");
58
58
setTimeout(
59
59
sendWakeMessage,
60
60
msUntilDailyWindow(agentContext.wakeTime, 0, msFrom.minutes(80)),
61
61
);
62
-
console.log("exiting wake process");
62
+
console.log("🔹 exiting wake process");
63
63
releaseTaskThread();
64
64
}
65
65
};
+5
-5
utils/agentContext.ts
+5
-5
utils/agentContext.ts
···
522
522
for (const service of services) {
523
523
if (service.length > 200) {
524
524
throw Error(
525
-
`External service name too long: "${service.substring(0, 50)}..." (max 200 characters)`,
525
+
`External service name too long: "${
526
+
service.substring(0, 50)
527
+
}..." (max 200 characters)`,
526
528
);
527
529
}
528
530
}
···
538
540
};
539
541
540
542
const populateAgentContext = async (): Promise<agentContextObject> => {
541
-
console.log("building new agentContext object…");
543
+
console.log("🔹 building new agentContext object…");
542
544
const context: agentContextObject = {
543
545
// state
544
546
busy: false,
···
602
604
context.externalServices = externalServices;
603
605
}
604
606
console.log(
605
-
`\`agentContext\` object built for ${context.agentBskyName}, BEGIN TASK…`,
607
+
`🔹 \`agentContext\` object built for ${context.agentBskyName}, BEGINING TASKS…`,
606
608
);
607
609
return context;
608
610
};
···
626
628
agentContext.mentionCount = 0;
627
629
agentContext.replyCount = 0;
628
630
agentContext.quoteCount = 0;
629
-
agentContext.checkCount = 0;
630
-
agentContext.processingCount = 0;
631
631
};
+5
-3
utils/declaration.ts
+5
-3
utils/declaration.ts
···
149
149
rkey: "self",
150
150
});
151
151
exists = true;
152
-
console.log("Existing autonomy declaration found - updating...");
152
+
console.log("🔹 Existing autonomy declaration found - updating...");
153
153
} catch (error: any) {
154
154
// Handle "record not found" errors (status 400 with error: "RecordNotFound")
155
155
const isNotFound =
···
159
159
error?.message?.includes("Could not locate record");
160
160
161
161
if (isNotFound) {
162
-
console.log("No existing autonomy declaration found - creating new...");
162
+
console.log(
163
+
"🔹 No existing autonomy declaration found - creating new...",
164
+
);
163
165
} else {
164
166
// Re-throw if it's not a "not found" error
165
167
throw error;
···
175
177
});
176
178
177
179
console.log(
178
-
`Autonomy declaration ${exists ? "updated" : "created"} successfully:`,
180
+
`🔹 Autonomy declaration ${exists ? "updated" : "created"} successfully:`,
179
181
result,
180
182
);
181
183
return result;
+3
-3
utils/messageAgent.ts
+3
-3
utils/messageAgent.ts
···
21
21
};
22
22
23
23
// Helper function to truncate long strings to 500 characters
24
-
const truncateString = (str: string, maxLength = 500): string => {
24
+
const truncateString = (str: string, maxLength = 140): string => {
25
25
if (str.length <= maxLength) {
26
26
return str;
27
27
}
···
54
54
55
55
for await (const response of reachAgent) {
56
56
if (response.messageType === "reasoning_message") {
57
-
console.log(`💭 reasoning…`);
57
+
// console.log(`💭 reasoning…`);
58
58
} else if (response.messageType === "assistant_message") {
59
59
console.log(`💬 ${agentContext.agentBskyName}: ${response.content}`);
60
60
} else if (response.messageType === "tool_call_message") {
···
76
76
}
77
77
} else {
78
78
console.log(
79
-
"Letta agent ID was not a set variable, skipping notification processing…",
79
+
"🔹 Letta agent ID was not a set variable, skipping notification processing…",
80
80
);
81
81
}
82
82
};
+4
-4
utils/processNotification.ts
+4
-4
utils/processNotification.ts
···
44
44
45
45
if (!handler) {
46
46
console.log(
47
-
`kind "${kind}" does not have a system prompt associated with it, moving on…`,
47
+
`🔹 kind "${kind}" does not have a system prompt associated with it, moving on…`,
48
48
);
49
49
console.log("notification response: ", notification);
50
50
return;
···
54
54
const prompt = await handler.promptFn(notification);
55
55
await messageAgent(prompt);
56
56
console.log(
57
-
`sent ${kind} notification from ${author} to ${agentProject}. moving on…`,
57
+
`🔹 sent ${kind} notification from ${author} to ${agentProject}. moving on…`,
58
58
);
59
59
} catch (error) {
60
60
console.log(
61
-
`Error processing ${kind} notification from ${author}: `,
61
+
`🔹 Error processing ${kind} notification from ${author}: `,
62
62
error,
63
63
);
64
64
} finally {
65
-
(agentContext as any)[handler]++;
65
+
(agentContext as any)[handler.counter]++;
66
66
}
67
67
};
+25
utils/time.ts
+25
utils/time.ts
···
127
127
export const getNow = () => {
128
128
return Temporal.Now.zonedDateTimeISO(agentContext.timeZone);
129
129
};
130
+
131
+
/**
132
+
* Format uptime from milliseconds into a human-readable string
133
+
* @param ms - uptime in milliseconds
134
+
* @returns Formatted string like "2 days, 3 hours, 15 minutes" or "3 hours, 15 minutes"
135
+
*/
136
+
export const formatUptime = (ms: number): string => {
137
+
const days = Math.floor(ms / (1000 * 60 * 60 * 24));
138
+
const hours = Math.floor((ms % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
139
+
const minutes = Math.floor((ms % (1000 * 60 * 60)) / (1000 * 60));
140
+
141
+
const parts: string[] = [];
142
+
143
+
if (days > 0) {
144
+
parts.push(`${days} ${days === 1 ? "day" : "days"}`);
145
+
}
146
+
if (hours > 0) {
147
+
parts.push(`${hours} ${hours === 1 ? "hour" : "hours"}`);
148
+
}
149
+
if (minutes > 0 || parts.length === 0) {
150
+
parts.push(`${minutes} ${minutes === 1 ? "minute" : "minutes"}`);
151
+
}
152
+
153
+
return parts.join(", ");
154
+
};