feat(gateway): add sender presence tracking
This commit is contained in:
@@ -64,6 +64,57 @@ describe('system handlers', () => {
|
||||
{ name: 'cron', type: 'automation', status: 'configured', description: 'Cron scheduler', itemCount: 2 },
|
||||
]);
|
||||
});
|
||||
|
||||
it('system.presence returns empty result when getPresence is not provided', async () => {
|
||||
const req: GatewayRequest = { id: 4, method: 'system.presence' };
|
||||
const result = await handlers['system.presence'](req) as GatewayResponse;
|
||||
expect(result.id).toBe(4);
|
||||
expect((result.result as any).presence).toEqual([]);
|
||||
expect((result.result as any).summary).toEqual({ total: 0, online: 0, offline: 0 });
|
||||
});
|
||||
|
||||
it('system.presence returns filtered presence entries', async () => {
|
||||
const handlers = createSystemHandlers({
|
||||
...deps,
|
||||
getPresence: ({ channel, status, limit } = {}) => {
|
||||
const all = [
|
||||
{
|
||||
channel: 'telegram',
|
||||
senderId: '1',
|
||||
senderName: 'alice',
|
||||
firstSeenAt: 1000,
|
||||
lastSeenAt: 2000,
|
||||
messageCount: 3,
|
||||
status: 'online' as const,
|
||||
},
|
||||
{
|
||||
channel: 'discord',
|
||||
senderId: '2',
|
||||
senderName: 'bob',
|
||||
firstSeenAt: 1000,
|
||||
lastSeenAt: 1500,
|
||||
messageCount: 1,
|
||||
status: 'offline' as const,
|
||||
},
|
||||
];
|
||||
|
||||
return all
|
||||
.filter((entry) => !channel || entry.channel === channel)
|
||||
.filter((entry) => !status || entry.status === status)
|
||||
.slice(0, limit ?? 100);
|
||||
},
|
||||
});
|
||||
|
||||
const req: GatewayRequest = {
|
||||
id: 5,
|
||||
method: 'system.presence',
|
||||
params: { channel: 'telegram', status: 'online', limit: 10 },
|
||||
};
|
||||
const result = await handlers['system.presence'](req) as GatewayResponse;
|
||||
expect((result.result as any).presence).toHaveLength(1);
|
||||
expect((result.result as any).presence[0].channel).toBe('telegram');
|
||||
expect((result.result as any).summary).toEqual({ total: 1, online: 1, offline: 0 });
|
||||
});
|
||||
});
|
||||
|
||||
describe('system.tokenUsage handler', () => {
|
||||
|
||||
@@ -11,6 +11,16 @@ export interface TokenUsageEntry {
|
||||
total: { inputTokens: number; outputTokens: number; calls: number; estimatedCost: number };
|
||||
}
|
||||
|
||||
export interface PresenceEntry {
|
||||
channel: string;
|
||||
senderId: string;
|
||||
senderName?: string;
|
||||
firstSeenAt: number;
|
||||
lastSeenAt: number;
|
||||
messageCount: number;
|
||||
status: 'online' | 'offline';
|
||||
}
|
||||
|
||||
export interface SystemHandlerDeps {
|
||||
startTime: number;
|
||||
version: string;
|
||||
@@ -31,6 +41,8 @@ export interface SystemHandlerDeps {
|
||||
getActiveRequests?: () => ActiveRequestInfo[];
|
||||
/** Optional callback to retrieve all services (channels + automation). */
|
||||
getServices?: () => ServiceInfo[];
|
||||
/** Optional callback to retrieve tracked sender presence. */
|
||||
getPresence?: (opts?: { channel?: string; status?: 'online' | 'offline'; limit?: number }) => PresenceEntry[];
|
||||
}
|
||||
|
||||
export function createSystemHandlers(deps: SystemHandlerDeps) {
|
||||
@@ -78,6 +90,28 @@ export function createSystemHandlers(deps: SystemHandlerDeps) {
|
||||
return makeResponse(request.id, { services: deps.getServices() });
|
||||
},
|
||||
|
||||
'system.presence': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
if (!deps.getPresence) {
|
||||
return makeResponse(request.id, { presence: [], summary: { total: 0, online: 0, offline: 0 } });
|
||||
}
|
||||
|
||||
const params = request.params as { channel?: string; status?: 'online' | 'offline'; limit?: number } | undefined;
|
||||
const presence = deps.getPresence({
|
||||
channel: params?.channel,
|
||||
status: params?.status,
|
||||
limit: params?.limit,
|
||||
});
|
||||
const online = presence.filter((entry) => entry.status === 'online').length;
|
||||
return makeResponse(request.id, {
|
||||
presence,
|
||||
summary: {
|
||||
total: presence.length,
|
||||
online,
|
||||
offline: presence.length - online,
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
'system.usage': async (request: GatewayRequest): Promise<OutboundMessage> => {
|
||||
const uptime = Math.floor((Date.now() - deps.startTime) / 1000);
|
||||
const usage = deps.getUsage?.() ?? { totalSessions: 0, activeConnections: 0 };
|
||||
|
||||
@@ -121,6 +121,9 @@ export class GatewayServer {
|
||||
getServices: this.config.config && this.config.channelRegistry
|
||||
? () => discoverServices(this.config.config!, this.config.channelRegistry!)
|
||||
: undefined,
|
||||
getPresence: this.config.channelRegistry
|
||||
? (opts) => this.config.channelRegistry!.getPresence(opts)
|
||||
: undefined,
|
||||
getUsage: () => ({
|
||||
totalSessions: this.config.sessionManager.listSessions().length,
|
||||
activeConnections: this.sessionBridge.connectionCount,
|
||||
|
||||
Reference in New Issue
Block a user