feat: initial public release (MAESTRO v0.1.0)
Open-source release of MAESTRO, an agent orchestration platform that runs LLM-driven tasks through sandboxed tools, with a web UI. Apache-2.0. See README.md and docs/ (getting-started, configuration, architecture).
This commit is contained in:
+137
@@ -0,0 +1,137 @@
|
||||
// Service Worker for MAESTRO. Two roles:
|
||||
// 1. PWA install eligibility (a fetch handler must be registered).
|
||||
// 2. V2 Web Push delivery — receives push events from the server and
|
||||
// either suppresses them when a visible tab already handled the
|
||||
// notification (ACK protocol) or shows an OS notification.
|
||||
//
|
||||
// Spec: docs/superpowers/specs/2026-05-28-browser-notifications-v2-webpush.md.
|
||||
|
||||
const VERSION = 'v2';
|
||||
const ACK_TIMEOUT_MS = 200;
|
||||
|
||||
self.addEventListener('install', (event) => {
|
||||
self.skipWaiting();
|
||||
});
|
||||
|
||||
self.addEventListener('activate', (event) => {
|
||||
event.waitUntil(self.clients.claim());
|
||||
});
|
||||
|
||||
self.addEventListener('fetch', (event) => {
|
||||
const req = event.request;
|
||||
if (req.method !== 'GET') return;
|
||||
|
||||
const url = new URL(req.url);
|
||||
// Only handle same-origin /ui/ requests; let the network handle everything else.
|
||||
if (url.origin !== self.location.origin) return;
|
||||
if (!url.pathname.startsWith('/ui/')) return;
|
||||
|
||||
// Network-first, fall back to cache only when offline. Never serve stale
|
||||
// HTML/JS by default — users always get the latest deploy when online.
|
||||
event.respondWith(
|
||||
fetch(req)
|
||||
.then((res) => {
|
||||
// Opportunistically cache successful responses for offline fallback.
|
||||
if (res.ok) {
|
||||
const clone = res.clone();
|
||||
caches.open(VERSION).then((cache) => cache.put(req, clone)).catch(() => {});
|
||||
}
|
||||
return res;
|
||||
})
|
||||
.catch(() => caches.match(req).then((hit) => hit || Response.error()))
|
||||
);
|
||||
});
|
||||
|
||||
// ── Push notification handler ───────────────────────────────────────────
|
||||
// Strategy: when one or more open tabs exist, broadcast a "notify-request"
|
||||
// and wait up to ACK_TIMEOUT_MS for any visible tab to ACK. ACK means the
|
||||
// page is showing the user state changes in-app, so we suppress the OS
|
||||
// notification. No ACK → tab is hidden or closed (race conditions included)
|
||||
// → we show the OS notification. This is more robust than visibilityState
|
||||
// polling because the page decides whether IT will handle the user-facing
|
||||
// surfacing.
|
||||
|
||||
self.addEventListener('push', (event) => {
|
||||
if (!event.data) return;
|
||||
let payload;
|
||||
try {
|
||||
payload = event.data.json();
|
||||
} catch {
|
||||
payload = { title: 'MAESTRO', body: event.data.text(), tag: 'maestro' };
|
||||
}
|
||||
|
||||
const showOptions = {
|
||||
body: payload.body ?? '',
|
||||
tag: payload.tag,
|
||||
icon: payload.icon ?? '/ui/icon-192.png',
|
||||
badge: '/ui/icon-192.png',
|
||||
data: payload.data ?? {},
|
||||
};
|
||||
|
||||
event.waitUntil((async () => {
|
||||
const clients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true,
|
||||
});
|
||||
if (clients.length === 0) {
|
||||
await self.registration.showNotification(payload.title ?? 'MAESTRO', showOptions);
|
||||
return;
|
||||
}
|
||||
const handled = await waitForAck(clients, payload);
|
||||
if (!handled) {
|
||||
await self.registration.showNotification(payload.title ?? 'MAESTRO', showOptions);
|
||||
}
|
||||
})());
|
||||
});
|
||||
|
||||
function waitForAck(clients, payload) {
|
||||
return new Promise((resolve) => {
|
||||
let settled = false;
|
||||
const handler = (e) => {
|
||||
if (settled) return;
|
||||
if (e.data && e.data.type === 'notification-handled' && e.data.tag === payload.tag) {
|
||||
settled = true;
|
||||
self.removeEventListener('message', handler);
|
||||
resolve(true);
|
||||
}
|
||||
};
|
||||
self.addEventListener('message', handler);
|
||||
setTimeout(() => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
self.removeEventListener('message', handler);
|
||||
resolve(false);
|
||||
}, ACK_TIMEOUT_MS);
|
||||
for (const client of clients) {
|
||||
client.postMessage({ type: 'notify-request', tag: payload.tag, payload });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ── Notification click handler ──────────────────────────────────────────
|
||||
// Focus an existing /ui/ tab if present (and tell it which task to open),
|
||||
// otherwise open a new one at /ui/?task=N.
|
||||
|
||||
self.addEventListener('notificationclick', (event) => {
|
||||
event.notification.close();
|
||||
const taskId = event.notification.data && event.notification.data.taskId;
|
||||
const targetUrl = taskId ? `/ui/?task=${taskId}` : '/ui/';
|
||||
event.waitUntil((async () => {
|
||||
const clients = await self.clients.matchAll({
|
||||
type: 'window',
|
||||
includeUncontrolled: true,
|
||||
});
|
||||
for (const client of clients) {
|
||||
if (client.url.includes('/ui/')) {
|
||||
try {
|
||||
await client.focus();
|
||||
} catch {
|
||||
// some browsers throw if focus is not allowed — fall through
|
||||
}
|
||||
client.postMessage({ type: 'open-task', taskId });
|
||||
return;
|
||||
}
|
||||
}
|
||||
await self.clients.openWindow(targetUrl);
|
||||
})());
|
||||
});
|
||||
Reference in New Issue
Block a user