agentwake 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/.cursor/hooks.json +15 -0
  2. package/.env.example +19 -0
  3. package/README.md +137 -0
  4. package/dist/adapters/claude-hook-adapter.d.ts +3 -0
  5. package/dist/adapters/claude-hook-adapter.d.ts.map +1 -0
  6. package/dist/adapters/claude-hook-adapter.js +25 -0
  7. package/dist/adapters/claude-hook-adapter.js.map +1 -0
  8. package/dist/adapters/cursor-hook-adapter.d.ts +3 -0
  9. package/dist/adapters/cursor-hook-adapter.d.ts.map +1 -0
  10. package/dist/adapters/cursor-hook-adapter.js +144 -0
  11. package/dist/adapters/cursor-hook-adapter.js.map +1 -0
  12. package/dist/adapters/cursor-terminal-hook.d.ts +20 -0
  13. package/dist/adapters/cursor-terminal-hook.d.ts.map +1 -0
  14. package/dist/adapters/cursor-terminal-hook.js +120 -0
  15. package/dist/adapters/cursor-terminal-hook.js.map +1 -0
  16. package/dist/adapters/hook-common.d.ts +6 -0
  17. package/dist/adapters/hook-common.d.ts.map +1 -0
  18. package/dist/adapters/hook-common.js +69 -0
  19. package/dist/adapters/hook-common.js.map +1 -0
  20. package/dist/adapters/qoder-log-adapter.d.ts +19 -0
  21. package/dist/adapters/qoder-log-adapter.d.ts.map +1 -0
  22. package/dist/adapters/qoder-log-adapter.js +320 -0
  23. package/dist/adapters/qoder-log-adapter.js.map +1 -0
  24. package/dist/bootstrap.d.ts +19 -0
  25. package/dist/bootstrap.d.ts.map +1 -0
  26. package/dist/bootstrap.js +106 -0
  27. package/dist/bootstrap.js.map +1 -0
  28. package/dist/cli.d.ts +3 -0
  29. package/dist/cli.d.ts.map +1 -0
  30. package/dist/cli.js +187 -0
  31. package/dist/cli.js.map +1 -0
  32. package/dist/config.d.ts +21 -0
  33. package/dist/config.d.ts.map +1 -0
  34. package/dist/config.js +63 -0
  35. package/dist/config.js.map +1 -0
  36. package/dist/domain/notify-event.d.ts +17 -0
  37. package/dist/domain/notify-event.d.ts.map +1 -0
  38. package/dist/domain/notify-event.js +10 -0
  39. package/dist/domain/notify-event.js.map +1 -0
  40. package/dist/gateway/adapter-registry.d.ts +9 -0
  41. package/dist/gateway/adapter-registry.d.ts.map +1 -0
  42. package/dist/gateway/adapter-registry.js +26 -0
  43. package/dist/gateway/adapter-registry.js.map +1 -0
  44. package/dist/gateway/adapter.d.ts +14 -0
  45. package/dist/gateway/adapter.d.ts.map +1 -0
  46. package/dist/gateway/adapter.js +3 -0
  47. package/dist/gateway/adapter.js.map +1 -0
  48. package/dist/gateway/event-router.d.ts +20 -0
  49. package/dist/gateway/event-router.d.ts.map +1 -0
  50. package/dist/gateway/event-router.js +72 -0
  51. package/dist/gateway/event-router.js.map +1 -0
  52. package/dist/main.d.ts +2 -0
  53. package/dist/main.d.ts.map +1 -0
  54. package/dist/main.js +9 -0
  55. package/dist/main.js.map +1 -0
  56. package/dist/notifiers/desktop-notifier.d.ts +11 -0
  57. package/dist/notifiers/desktop-notifier.d.ts.map +1 -0
  58. package/dist/notifiers/desktop-notifier.js +78 -0
  59. package/dist/notifiers/desktop-notifier.js.map +1 -0
  60. package/dist/notifiers/mobile-ws-notifier.d.ts +13 -0
  61. package/dist/notifiers/mobile-ws-notifier.d.ts.map +1 -0
  62. package/dist/notifiers/mobile-ws-notifier.js +65 -0
  63. package/dist/notifiers/mobile-ws-notifier.js.map +1 -0
  64. package/dist/notifiers/notifier.d.ts +6 -0
  65. package/dist/notifiers/notifier.d.ts.map +1 -0
  66. package/dist/notifiers/notifier.js +3 -0
  67. package/dist/notifiers/notifier.js.map +1 -0
  68. package/dist/notifiers/pwa-push-notifier.d.ts +12 -0
  69. package/dist/notifiers/pwa-push-notifier.d.ts.map +1 -0
  70. package/dist/notifiers/pwa-push-notifier.js +69 -0
  71. package/dist/notifiers/pwa-push-notifier.js.map +1 -0
  72. package/dist/run-gateway.d.ts +3 -0
  73. package/dist/run-gateway.d.ts.map +1 -0
  74. package/dist/run-gateway.js +66 -0
  75. package/dist/run-gateway.js.map +1 -0
  76. package/dist/utils/approval-match.d.ts +2 -0
  77. package/dist/utils/approval-match.d.ts.map +1 -0
  78. package/dist/utils/approval-match.js +32 -0
  79. package/dist/utils/approval-match.js.map +1 -0
  80. package/dist/utils/logger.d.ts +7 -0
  81. package/dist/utils/logger.d.ts.map +1 -0
  82. package/dist/utils/logger.js +19 -0
  83. package/dist/utils/logger.js.map +1 -0
  84. package/package.json +60 -0
  85. package/scripts/cursor-hook-forwarder.mjs +269 -0
  86. package/web/app.js +350 -0
  87. package/web/icons/icon-192.svg +6 -0
  88. package/web/icons/icon-512.svg +6 -0
  89. package/web/index.html +117 -0
  90. package/web/manifest.webmanifest +25 -0
  91. package/web/sw.js +32 -0
package/web/app.js ADDED
@@ -0,0 +1,350 @@
1
+ const APP_VERSION = "debug11";
2
+ const statusEl = document.getElementById("status");
3
+ const eventsEl = document.getElementById("events");
4
+ const notificationBtn = document.getElementById("btn-notification");
5
+ const installCheckBtn = document.getElementById("btn-install-check");
6
+ const debugLogEl = document.getElementById("debug-log");
7
+
8
+ let wsPath = "/ws";
9
+ let wsRetryTimer = null;
10
+ let wsConnected = false;
11
+ let lastTimestamp = 0;
12
+ const seenEventKeys = new Set();
13
+ const debugLines = [];
14
+ let swRegistration = null;
15
+ let swRegisterError = null;
16
+
17
+ function debugLog(message, meta) {
18
+ const ts = new Date().toISOString();
19
+ const payload = meta ? `${message} ${JSON.stringify(meta)}` : message;
20
+ const line = `[${ts}] ${payload}`;
21
+ console.log("[AgentWake]", message, meta || "");
22
+ debugLines.push(line);
23
+ if (debugLines.length > 80) {
24
+ debugLines.splice(0, debugLines.length - 80);
25
+ }
26
+ if (debugLogEl) {
27
+ debugLogEl.textContent = debugLines.join("\n");
28
+ }
29
+ }
30
+
31
+ function hapticFeedback() {
32
+ if (typeof navigator !== "undefined" && navigator.vibrate) {
33
+ try {
34
+ navigator.vibrate(50); // 50ms 轻微震动反馈
35
+ } catch (e) {
36
+ // 忽略可能产生的震动错误
37
+ }
38
+ }
39
+ }
40
+
41
+ function getEventFingerprint(evt) {
42
+ return `${evt.dedupeKey}#${evt.timestamp}`;
43
+ }
44
+
45
+ function setStatus(text) {
46
+ if (statusEl) {
47
+ statusEl.textContent = text;
48
+ }
49
+ debugLog("status", { text });
50
+ }
51
+
52
+ function appendEvent(evt) {
53
+ const fingerprint = getEventFingerprint(evt);
54
+ if (seenEventKeys.has(fingerprint)) {
55
+ debugLog("skip duplicate event", { fingerprint });
56
+ return;
57
+ }
58
+ seenEventKeys.add(fingerprint);
59
+ lastTimestamp = Math.max(lastTimestamp, Number(evt.timestamp || 0));
60
+ debugLog("append event", {
61
+ fingerprint,
62
+ source: evt.source,
63
+ title: evt.title,
64
+ timestamp: evt.timestamp,
65
+ lastTimestamp,
66
+ });
67
+
68
+ const node = document.createElement("div");
69
+ node.className = "event";
70
+ node.innerHTML = `<div><strong>${evt.title}</strong></div><div>${evt.body}</div><div class="meta">${new Date(evt.timestamp).toLocaleString()} · ${evt.source}</div>`;
71
+ if (eventsEl) {
72
+ eventsEl.prepend(node);
73
+ }
74
+ }
75
+
76
+ async function showSystemNotification(evt) {
77
+ if (typeof Notification !== "function") {
78
+ debugLog("notification api unavailable");
79
+ return;
80
+ }
81
+ if (Notification.permission !== "granted") {
82
+ debugLog("notification skipped", { permission: Notification.permission });
83
+ return;
84
+ }
85
+ if (swRegistration && typeof swRegistration.showNotification === "function") {
86
+ try {
87
+ await swRegistration.showNotification(evt.title, { body: evt.body, tag: "agentwake-web" });
88
+ debugLog("notification sent by sw", { title: evt.title });
89
+ return;
90
+ } catch (error) {
91
+ debugLog("sw showNotification failed", { error: String(error) });
92
+ }
93
+ }
94
+ try {
95
+ new Notification(evt.title, { body: evt.body });
96
+ debugLog("notification sent", { title: evt.title });
97
+ } catch (error) {
98
+ debugLog("notification construct failed", { error: String(error) });
99
+ setStatus("系统通知构造失败,请先安装 PWA 并启用 Push");
100
+ }
101
+ }
102
+
103
+ async function connectWs() {
104
+ const proto = location.protocol === "https:" ? "wss" : "ws";
105
+ const wsUrl = `${proto}://${location.host}${wsPath}`;
106
+ const ws = new WebSocket(wsUrl);
107
+ debugLog("ws connecting", { wsUrl });
108
+ setStatus(`WebSocket 连接中:${wsUrl}`);
109
+
110
+ ws.addEventListener("open", () => {
111
+ wsConnected = true;
112
+ debugLog("ws open", { wsUrl });
113
+ setStatus("WebSocket 已连接");
114
+ if (wsRetryTimer) {
115
+ clearTimeout(wsRetryTimer);
116
+ wsRetryTimer = null;
117
+ }
118
+ });
119
+
120
+ ws.addEventListener("error", (event) => {
121
+ debugLog("ws error", { wsUrl, type: event.type });
122
+ setStatus(`WebSocket 连接失败:${wsUrl}`);
123
+ });
124
+
125
+ ws.addEventListener("close", () => {
126
+ wsConnected = false;
127
+ debugLog("ws close", { wsUrl });
128
+ setStatus(`WebSocket 已断开,3秒后重连:${wsUrl}`);
129
+ if (!wsRetryTimer) {
130
+ wsRetryTimer = setTimeout(() => {
131
+ wsRetryTimer = null;
132
+ void connectWs();
133
+ }, 3000);
134
+ }
135
+ });
136
+
137
+ ws.addEventListener("message", (msg) => {
138
+ debugLog("ws message", {
139
+ wsUrl,
140
+ dataType: msg.data instanceof Blob ? "blob" : typeof msg.data,
141
+ });
142
+ if (msg.data instanceof Blob) {
143
+ msg.data
144
+ .text()
145
+ .then((text) => {
146
+ try {
147
+ debugLog("ws blob text", { length: text.length });
148
+ handleWsMessage(JSON.parse(text));
149
+ } catch (error) {
150
+ debugLog("ws blob parse failed", { error: String(error) });
151
+ setStatus("WebSocket 消息解析失败");
152
+ }
153
+ })
154
+ .catch((error) => {
155
+ debugLog("ws blob read failed", { error: String(error) });
156
+ setStatus("WebSocket 消息读取失败");
157
+ });
158
+ return;
159
+ }
160
+
161
+ try {
162
+ handleWsMessage(JSON.parse(String(msg.data)));
163
+ } catch (error) {
164
+ debugLog("ws message parse failed", { error: String(error) });
165
+ setStatus("WebSocket 消息解析失败");
166
+ }
167
+ });
168
+ }
169
+
170
+ function handleWsMessage(parsed) {
171
+ debugLog("handle ws message", { type: parsed?.type });
172
+ if (parsed.type === "hello") {
173
+ setStatus("WebSocket 握手成功");
174
+ return;
175
+ }
176
+
177
+ if (parsed.type === "notify-event") {
178
+ appendEvent(parsed.payload);
179
+ void showSystemNotification(parsed.payload);
180
+ }
181
+ }
182
+
183
+ async function pollEvents() {
184
+ const pollUrl = `/api/events?since=${lastTimestamp}`;
185
+ debugLog("poll start", { pollUrl, wsConnected, lastTimestamp });
186
+ try {
187
+ const resp = await fetch(pollUrl);
188
+ if (!resp.ok) {
189
+ debugLog("poll non-200", { status: resp.status });
190
+ return;
191
+ }
192
+ const data = await resp.json();
193
+ debugLog("poll success", { count: Array.isArray(data?.events) ? data.events.length : -1 });
194
+ if (!Array.isArray(data?.events)) {
195
+ debugLog("poll invalid payload");
196
+ return;
197
+ }
198
+
199
+ for (const evt of data.events) {
200
+ appendEvent(evt);
201
+ if (!wsConnected) {
202
+ void showSystemNotification(evt);
203
+ }
204
+ }
205
+ if (!wsConnected) {
206
+ setStatus(`WebSocket 未连接,轮询补偿中(已拉取 ${data.events.length} 条)`);
207
+ }
208
+ } catch {
209
+ debugLog("poll failed");
210
+ if (!wsConnected) {
211
+ setStatus("WebSocket 未连接,轮询也失败");
212
+ }
213
+ }
214
+ }
215
+
216
+ async function onRequestPermissionClick() {
217
+ const result = await Notification.requestPermission();
218
+ debugLog("notification permission", { result });
219
+ setStatus(`通知权限:${result}`);
220
+ }
221
+
222
+ async function setupServiceWorker() {
223
+ if (!("serviceWorker" in navigator)) {
224
+ debugLog("sw not supported");
225
+ return null;
226
+ }
227
+ try {
228
+ swRegistration = await navigator.serviceWorker.register("/sw.js");
229
+ swRegisterError = null;
230
+ debugLog("sw register success", { scope: swRegistration.scope });
231
+ return swRegistration;
232
+ } catch (error) {
233
+ swRegisterError = String(error);
234
+ debugLog("sw register failed", { error: String(error) });
235
+ return null;
236
+ }
237
+ }
238
+
239
+ async function onInstallCheckClick() {
240
+ const manifestResp = await fetch("/manifest.webmanifest", { cache: "no-store" }).catch(() => null);
241
+ const manifestOk = Boolean(manifestResp?.ok);
242
+ const isAndroid = /Android/i.test(navigator.userAgent || "");
243
+ const checklist = {
244
+ appVersion: APP_VERSION,
245
+ isSecureContext: window.isSecureContext,
246
+ protocol: location.protocol,
247
+ hasServiceWorkerApi: "serviceWorker" in navigator,
248
+ swRegistered: Boolean(swRegistration),
249
+ swRegisterError,
250
+ hasManifest: manifestOk,
251
+ isAndroid,
252
+ };
253
+ debugLog("installability check", checklist);
254
+
255
+ if (!window.isSecureContext) {
256
+ setStatus("安装失败主因:当前不是安全上下文(HTTPS 证书在手机上未受信任)");
257
+ return;
258
+ }
259
+ if (!manifestOk) {
260
+ setStatus("安装失败主因:manifest 无法访问");
261
+ return;
262
+ }
263
+ if (!("serviceWorker" in navigator)) {
264
+ setStatus("安装失败主因:浏览器不支持 Service Worker");
265
+ return;
266
+ }
267
+ if (!swRegistration) {
268
+ setStatus("安装失败主因:Service Worker 注册失败,请查看调试日志");
269
+ return;
270
+ }
271
+ if (isAndroid) {
272
+ setStatus("可手动安装:Chrome 菜单 ⋮ -> 安装应用/添加到主屏幕");
273
+ return;
274
+ }
275
+ setStatus("安装条件基本满足,可继续安装");
276
+ }
277
+
278
+ if (notificationBtn) {
279
+ notificationBtn.addEventListener("click", () => {
280
+ hapticFeedback();
281
+ void onRequestPermissionClick();
282
+ });
283
+ } else {
284
+ debugLog("btn missing", { id: "btn-notification" });
285
+ }
286
+
287
+ if (installCheckBtn) {
288
+ installCheckBtn.addEventListener("click", () => {
289
+ hapticFeedback();
290
+ void onInstallCheckClick();
291
+ });
292
+ } else {
293
+ debugLog("btn missing", { id: "btn-install-check" });
294
+ }
295
+
296
+ document.addEventListener("click", (event) => {
297
+ const target = event.target;
298
+ if (!(target instanceof Element)) {
299
+ return;
300
+ }
301
+
302
+ if (target.tagName === "BUTTON" || target.closest("button")) {
303
+ hapticFeedback();
304
+ }
305
+
306
+ if (target.id === "btn-notification") {
307
+ debugLog("delegated click", { id: target.id });
308
+ void onRequestPermissionClick();
309
+ }
310
+ if (target.id === "btn-install-check") {
311
+ debugLog("delegated click", { id: target.id });
312
+ void onInstallCheckClick();
313
+ }
314
+ });
315
+
316
+ async function init() {
317
+ debugLog("init start", {
318
+ appVersion: APP_VERSION,
319
+ href: location.href,
320
+ protocol: location.protocol,
321
+ host: location.host,
322
+ notificationPermission: Notification.permission,
323
+ });
324
+ const runtime = await fetch("/api/runtime").then((r) => r.json()).catch(() => null);
325
+ debugLog("runtime loaded", { runtime });
326
+ if (runtime?.wsPath) {
327
+ wsPath = runtime.wsPath;
328
+ debugLog("wsPath updated", { wsPath });
329
+ }
330
+ void setupServiceWorker();
331
+ void connectWs();
332
+ setInterval(() => {
333
+ void pollEvents();
334
+ }, 2000);
335
+ void pollEvents();
336
+ debugLog("init done");
337
+ }
338
+
339
+ window.addEventListener("error", (event) => {
340
+ debugLog("window error", { message: event.message, filename: event.filename, lineno: event.lineno });
341
+ });
342
+
343
+ window.addEventListener("unhandledrejection", (event) => {
344
+ debugLog("unhandled rejection", { reason: String(event.reason) });
345
+ });
346
+
347
+ void init().catch((error) => {
348
+ debugLog("init failed", { error: String(error) });
349
+ setStatus("初始化失败,请查看调试日志");
350
+ });
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 192 192" role="img" aria-label="AgentWake">
2
+ <rect width="192" height="192" rx="36" fill="#101827" />
3
+ <circle cx="96" cy="96" r="56" fill="#2563eb" />
4
+ <path d="M66 104h60l-30 36-30-36z" fill="#e5e7eb" />
5
+ <path d="M70 88a26 26 0 1 1 52 0" fill="none" stroke="#e5e7eb" stroke-width="10" stroke-linecap="round" />
6
+ </svg>
@@ -0,0 +1,6 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512" role="img" aria-label="AgentWake">
2
+ <rect width="512" height="512" rx="96" fill="#101827" />
3
+ <circle cx="256" cy="256" r="150" fill="#2563eb" />
4
+ <path d="M176 280h160l-80 100-80-100z" fill="#e5e7eb" />
5
+ <path d="M186 238a70 70 0 1 1 140 0" fill="none" stroke="#e5e7eb" stroke-width="28" stroke-linecap="round" />
6
+ </svg>
package/web/index.html ADDED
@@ -0,0 +1,117 @@
1
+ <!doctype html>
2
+ <html lang="zh-CN">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
+ <meta name="theme-color" content="#101827" />
7
+ <meta name="apple-mobile-web-app-capable" content="yes" />
8
+ <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent" />
9
+ <link rel="apple-touch-icon" href="/icons/icon-192.svg" />
10
+ <link rel="manifest" href="/manifest.webmanifest" />
11
+ <title>AgentWake Mobile</title>
12
+ <style>
13
+ :root {
14
+ --safe-top: env(safe-area-inset-top, 0px);
15
+ --safe-bottom: env(safe-area-inset-bottom, 0px);
16
+ }
17
+ body {
18
+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
19
+ margin: 0;
20
+ padding: calc(16px + var(--safe-top)) 16px calc(16px + var(--safe-bottom)) 16px;
21
+ background: #101827;
22
+ color: #e5e7eb;
23
+ -webkit-tap-highlight-color: transparent;
24
+ overscroll-behavior-y: none;
25
+ }
26
+ .card {
27
+ background: #1f2937;
28
+ border-radius: 12px;
29
+ padding: 16px;
30
+ margin-bottom: 16px;
31
+ box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
32
+ }
33
+ .button-group {
34
+ display: flex;
35
+ flex-wrap: wrap;
36
+ gap: 10px;
37
+ margin-bottom: 12px;
38
+ }
39
+ button {
40
+ background: #2563eb;
41
+ color: white;
42
+ border: 0;
43
+ border-radius: 8px;
44
+ padding: 12px 16px;
45
+ font-size: 15px;
46
+ font-weight: 500;
47
+ cursor: pointer;
48
+ user-select: none;
49
+ flex: 1 1 auto;
50
+ min-height: 44px; /* iOS 推荐最小点击高度 */
51
+ transition: background 0.2s, transform 0.1s;
52
+ }
53
+ button:active {
54
+ background: #1d4ed8;
55
+ transform: scale(0.98);
56
+ }
57
+ #events {
58
+ max-height: 50vh;
59
+ overflow-y: auto;
60
+ -webkit-overflow-scrolling: touch;
61
+ margin-top: 8px;
62
+ }
63
+ .event {
64
+ border-bottom: 1px solid #374151;
65
+ padding: 12px 0;
66
+ }
67
+ .event:last-child {
68
+ border-bottom: none;
69
+ }
70
+ .meta {
71
+ color: #9ca3af;
72
+ font-size: 12px;
73
+ margin-top: 4px;
74
+ }
75
+ #status {
76
+ font-size: 13px;
77
+ text-align: center;
78
+ margin-top: 12px;
79
+ padding: 8px;
80
+ background: #374151;
81
+ border-radius: 6px;
82
+ }
83
+ h2, h3 { margin-top: 0; margin-bottom: 12px; }
84
+ #debug-log {
85
+ max-height: 20vh;
86
+ overflow-y: auto;
87
+ white-space: pre-wrap;
88
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
89
+ font-size: 11px;
90
+ color: #93c5fd;
91
+ background: #111827;
92
+ padding: 8px;
93
+ border-radius: 6px;
94
+ -webkit-overflow-scrolling: touch;
95
+ }
96
+ </style>
97
+ </head>
98
+ <body>
99
+ <h2>AgentWake 移动通知</h2>
100
+ <div class="card">
101
+ <div class="button-group">
102
+ <button id="btn-notification">授权系统通知</button>
103
+ <button id="btn-install-check">检查安装条件</button>
104
+ </div>
105
+ <div id="status" class="meta">初始化中...</div>
106
+ </div>
107
+ <div class="card">
108
+ <h3>实时事件</h3>
109
+ <div id="events"></div>
110
+ </div>
111
+ <div class="card">
112
+ <h3>调试日志</h3>
113
+ <div id="debug-log"></div>
114
+ </div>
115
+ <script src="/app.js?v=debug11"></script>
116
+ </body>
117
+ </html>
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "AgentWake Mobile",
3
+ "short_name": "AgentWake",
4
+ "id": "/",
5
+ "start_url": "/",
6
+ "scope": "/",
7
+ "display": "standalone",
8
+ "orientation": "portrait",
9
+ "background_color": "#101827",
10
+ "theme_color": "#101827",
11
+ "icons": [
12
+ {
13
+ "src": "/icons/icon-192.svg",
14
+ "sizes": "192x192",
15
+ "type": "image/svg+xml",
16
+ "purpose": "any maskable"
17
+ },
18
+ {
19
+ "src": "/icons/icon-512.svg",
20
+ "sizes": "512x512",
21
+ "type": "image/svg+xml",
22
+ "purpose": "any maskable"
23
+ }
24
+ ]
25
+ }
package/web/sw.js ADDED
@@ -0,0 +1,32 @@
1
+ self.addEventListener("install", () => {
2
+ self.skipWaiting();
3
+ });
4
+
5
+ self.addEventListener("activate", (event) => {
6
+ event.waitUntil(self.clients.claim());
7
+ });
8
+
9
+ self.addEventListener("push", (event) => {
10
+ if (!event.data) {
11
+ return;
12
+ }
13
+ let payload = { title: "AgentWake", body: "New event" };
14
+ try {
15
+ payload = event.data.json();
16
+ } catch {
17
+ payload = { title: "AgentWake", body: event.data.text() };
18
+ }
19
+
20
+ event.waitUntil(
21
+ self.registration.showNotification(payload.title, {
22
+ body: payload.body,
23
+ data: payload.event || null,
24
+ tag: "agentwake-notification",
25
+ }),
26
+ );
27
+ });
28
+
29
+ self.addEventListener("notificationclick", (event) => {
30
+ event.notification.close();
31
+ event.waitUntil(self.clients.openWindow("/"));
32
+ });