Gateway: per-watch notification switches, model list for new threads, and Ask before acting
Gateway / check (push) Successful in 9s
Gateway / image (push) Successful in 21s

This commit is contained in:
gib committed 2026-11-02 12:02:53 -05:00
1 parent a5222e14e8
commit 3cc7e40852
14 files changed
+472 -20

No files matched your search

+30 -4
View File
@@ -1,11 +1,35 @@
// Delivers pushes to paired watches through APNs, and remembers Apple's last answer so the watch's
// status screen can show whether pushes are getting through. Without APNs settings it only logs.
// status screen can show whether pushes are getting through. Each watch only gets the kinds of push
// it asked for. Without APNs settings it only logs.
import type { Apns } from "./apns.ts";
import type { PushResult } from "./contract.ts";
import type { NotificationPrefs, PushResult } from "./contract.ts";
import type { Db, DeviceRow } from "./db.ts";
import type { Push } from "./notify.ts";
const ALL_ON: NotificationPrefs = { needsMe: true, finished: true, failed: true, subagents: true };
/** A watch's choices, with every kind it never set left on. */
export function notificationPrefs(device: DeviceRow): NotificationPrefs {
const saved: Partial<NotificationPrefs> = device.notifications ? JSON.parse(device.notifications) : {};
return { ...ALL_ON, ...saved };
}
/** Whether a watch with these choices wants this push. Host problems and tests always go out. */
export function wants(prefs: NotificationPrefs, push: Push): boolean {
switch (push.category) {
case "approval":
case "input":
return push.fromSubagent ? prefs.subagents : prefs.needsMe;
case "finished":
return prefs.finished;
case "failed":
return prefs.failed;
default:
return true;
}
}
export class Pusher {
/** Apple's answer to the most recent push sent to any watch. */
last: PushResult | null = null;
@@ -20,9 +44,11 @@ export class Pusher {
return this.apns !== null;
}
/** Sends to every paired watch that registered for pushes. */
/** Sends to every paired watch that registered for pushes and wants this kind. */
sendAll = async (push: Push): Promise<void> => {
const devices = this.db.devices.active().filter((device) => device.apns_token && device.apns_env);
const devices = this.db.devices
.active()
.filter((device) => device.apns_token && device.apns_env && wants(notificationPrefs(device), push));
if (!this.apns || devices.length === 0) {
this.log(`push (not sent): [${push.category}] ${push.title}: ${push.body}`);
return;