naisys
Version:
NAISYS - Autonomous AI agent runner with built-in context management and cost tracking
72 lines • 3.02 kB
JavaScript
import { unique } from "@naisys/common";
import { AgentsStatusSchema, HubEvents, NAISYS_HEARTBEAT_INTERVAL_MS, } from "@naisys/hub-protocol";
export function createHeartbeatService(hubClient, agentManager, userService) {
// In hub mode, listen for agent status pushes from the hub
if (hubClient) {
hubClient.registerEvent(HubEvents.AGENTS_STATUS, (data) => {
const parsed = AgentsStatusSchema.parse(data);
userService.setActiveUsers(parsed.hostActiveAgents);
});
agentManager.onHeartbeatNeeded = sendHeartbeat;
}
function sendHeartbeat() {
// Ephemerals ride under the parent's userId so the synthetic id never
// enters hub-user tracking. The subagentId still distinguishes the row.
// Skip an entry rather than ever emitting a synthetic id as `userId`.
const activeSessions = [];
// Top-level agents only — subagents inherit via the parent's shell env.
const runtimeApiKeys = [];
const seenKeyUserIds = new Set();
for (const a of agentManager.runningAgents) {
const user = userService.getUserById(a.agentUserId);
let userId;
let subagentId;
if (user?.isEphemeral) {
if (user.leadUserId == null)
continue; // orphan ephemeral — shouldn't happen
userId = user.leadUserId;
subagentId = a.agentUserId;
}
else {
// Negative agentUserId without an isEphemeral user record means we're
// racing teardown (user record cleared before splice). Drop it.
if (a.agentUserId < 0)
continue;
userId = a.agentUserId;
}
activeSessions.push({
userId,
runId: a.getRunId(),
subagentId,
sessionId: a.getSessionId(),
paused: a.isPaused(),
state: a.getState(),
tokenCount: a.getTokenCount(),
});
if (subagentId === undefined && !seenKeyUserIds.has(userId)) {
runtimeApiKeys.push({
userId,
runtimeApiKey: a.naisysApiService.getKey(),
});
seenKeyUserIds.add(userId);
}
}
if (hubClient) {
hubClient.sendMessage(HubEvents.HEARTBEAT, {
activeSessions,
runtimeApiKeys: runtimeApiKeys.length > 0 ? runtimeApiKeys : undefined,
});
}
else {
const uniqueUserIds = unique(activeSessions.map((s) => s.userId));
userService.setActiveUsers({ "": uniqueUserIds });
}
}
// Start periodic heartbeat
const interval = setInterval(sendHeartbeat, NAISYS_HEARTBEAT_INTERVAL_MS);
function cleanup() {
clearInterval(interval);
}
return { cleanup };
}
//# sourceMappingURL=heartbeatService.js.map