fix(server,app,desktop): route in-app notifications by presence cascade
Replaces the per-client stale/visibility policy with one shared
presence cascade so a backgrounded Firefox tab can no longer steal
in-app notifications from an active Electron desktop window.
Server (`agent-attention-policy.ts`): exports `PRESENCE_THRESHOLD_MS`
(3 min) and `computeNotificationPlan({ allStates, agentId, reason,
nowMs })` returning `{ inAppRecipientIndex, shouldPush }`. Cascade:
focus suppression requires presence; otherwise the most-recent
present client wins (lower-index tiebreak); push fires only when no
client is present, and never on `error`. `broadcastAgentAttention`
calls the policy once per event.
Electron (`daemon-manager.ts`, `idle.ts`): registers
`desktop_get_system_idle_time` returning `powerMonitor
.getSystemIdleTime() * 1000`. Renderer wrapper
`getDesktopSystemIdleTimeMs()` is best-effort: rejected IPC, null,
NaN, Infinity, or negative values return null without throwing.
Hook (`use-client-activity.ts`): Electron-only effect polls every 5s
and updates `lastActivityAtRef` only forward, so OS-wide activity
keeps a backgrounded Electron window present while in-window events
still update immediately.
`ClientHeartbeatMessageSchema` is unchanged; old/new clients and
daemons remain wire-compatible.
Tests: 52 focused tests across policy unit, dispatch, e2e, hook,
desktop wrapper, and permissions. Phase-3 dispatch test +
Phase-5 stale-poll test verified by mutation (breaking the focus
check / the `>` guard makes the exact regression fail).
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
import { type ChildProcess } from "node:child_process";
|
||||
import { readFileSync } from "node:fs";
|
||||
import path from "node:path";
|
||||
import { app, ipcMain } from "electron";
|
||||
import { app, ipcMain, powerMonitor } from "electron";
|
||||
import log from "electron-log/main";
|
||||
import { resolvePaseoHome, spawnProcess } from "@getpaseo/server";
|
||||
import {
|
||||
@@ -444,6 +444,10 @@ function resolveCurrentUpdateVersion(): string {
|
||||
return resolveDesktopAppVersion();
|
||||
}
|
||||
|
||||
function getSystemIdleTimeMs(): number {
|
||||
return powerMonitor.getSystemIdleTime() * 1000;
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// IPC registration
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -456,6 +460,7 @@ export function createDaemonCommandHandlers(): Record<string, DesktopCommandHand
|
||||
restart_desktop_daemon: () => restartDaemon(),
|
||||
desktop_daemon_logs: () => getDaemonLogs(),
|
||||
desktop_daemon_pairing: () => getDaemonPairing(),
|
||||
desktop_get_system_idle_time: () => getSystemIdleTimeMs(),
|
||||
cli_daemon_status: () => getCliDaemonStatus(),
|
||||
write_attachment_base64: (args) => writeAttachmentBase64(args ?? {}),
|
||||
copy_attachment_file: (args) => copyAttachmentFileToManagedStorage(args ?? {}),
|
||||
|
||||
Reference in New Issue
Block a user