Add server-side client activity tracking for smart notifications

- Add client_heartbeat message type for clients to report activity
- Add shouldNotify field to attention_required events
- Track client activity (focusedAgentId, lastActivityAt) per session
- WebSocketServer checks all sessions to determine shouldNotify
- If any client is active on the agent (within 60s), shouldNotify=false
- Add onAgentAttention callback to AgentManager for clean separation
This commit is contained in:
Mohamed Boudra
2026-01-13 11:50:13 +07:00
parent 2fa7ec17cb
commit 7f7f5c53e6
6 changed files with 325 additions and 3 deletions
@@ -45,12 +45,19 @@ export type PersistedAgentQueryOptions = ListPersistedAgentsOptions & {
provider?: AgentProvider;
};
export type AgentAttentionCallback = (params: {
agentId: string;
provider: AgentProvider;
reason: "finished" | "error" | "permission";
}) => void;
export type AgentManagerOptions = {
clients?: Partial<Record<AgentProvider, AgentClient>>;
maxTimelineItems?: number;
idFactory?: () => string;
registry?: AgentRegistry;
agentControlMcp?: AgentControlMcpConfig;
onAgentAttention?: AgentAttentionCallback;
};
export type WaitForAgentOptions = {
@@ -176,6 +183,7 @@ export class AgentManager {
private readonly registry?: AgentRegistry;
private readonly previousStatuses = new Map<string, AgentLifecycleStatus>();
private readonly agentControlMcp?: AgentControlMcpConfig;
private onAgentAttention?: AgentAttentionCallback;
constructor(options?: AgentManagerOptions) {
this.maxTimelineItems =
@@ -183,6 +191,7 @@ export class AgentManager {
this.idFactory = options?.idFactory ?? (() => randomUUID());
this.registry = options?.registry;
this.agentControlMcp = options?.agentControlMcp;
this.onAgentAttention = options?.onAgentAttention;
if (options?.clients) {
for (const [provider, client] of Object.entries(options.clients)) {
if (client) {
@@ -196,6 +205,10 @@ export class AgentManager {
this.clients.set(provider, client);
}
setAgentAttentionCallback(callback: AgentAttentionCallback): void {
this.onAgentAttention = callback;
}
subscribe(callback: AgentSubscriber, options?: SubscribeOptions): () => void {
const record: SubscriptionRecord = {
callback,
@@ -985,11 +998,10 @@ export class AgentManager {
agent: ManagedAgent,
reason: "finished" | "error" | "permission"
): void {
this.dispatchStream(agent.id, {
type: "attention_required",
this.onAgentAttention?.({
agentId: agent.id,
provider: agent.provider,
reason,
timestamp: new Date().toISOString(),
});
}