@delt/claude-alarm 1.3.0 → 1.4.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.
- package/dist/cli.js +178 -49
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +161 -32
- package/dist/codex/main.js.map +1 -1
- package/dist/dashboard/index.html +77 -46
- package/dist/hub/server.js +56 -31
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +56 -31
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/dashboard/index.html +77 -46
|
@@ -1286,13 +1286,20 @@
|
|
|
1286
1286
|
}
|
|
1287
1287
|
|
|
1288
1288
|
function showUploadRejected(msg) {
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1289
|
+
showNotDelivered(msg.sessionId, msg.withText ? 'Image and message not delivered' : 'Image not delivered', msg.reason);
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
function showMessageRejected(msg) {
|
|
1293
|
+
showNotDelivered(msg.sessionId, 'Message not delivered', msg.reason);
|
|
1294
|
+
}
|
|
1295
|
+
|
|
1296
|
+
function showNotDelivered(sessionId, label, reason) {
|
|
1297
|
+
state.waitingReply[sessionId] = false;
|
|
1298
|
+
if (state.selectedSession === sessionId) {
|
|
1292
1299
|
renderMessages();
|
|
1293
|
-
showMentionError(`${label}: ${
|
|
1300
|
+
showMentionError(`${label}: ${reason}`);
|
|
1294
1301
|
}
|
|
1295
|
-
state.notifications.unshift({ sessionId
|
|
1302
|
+
state.notifications.unshift({ sessionId, title: label, message: reason, level: 'warning', time: Date.now() });
|
|
1296
1303
|
renderNotifications();
|
|
1297
1304
|
}
|
|
1298
1305
|
|
|
@@ -1462,32 +1469,15 @@
|
|
|
1462
1469
|
case 'upload_rejected':
|
|
1463
1470
|
showUploadRejected(msg);
|
|
1464
1471
|
break;
|
|
1472
|
+
case 'message_rejected':
|
|
1473
|
+
showMessageRejected(msg);
|
|
1474
|
+
break;
|
|
1465
1475
|
case 'notification':
|
|
1466
1476
|
state.notifications.unshift({ sessionId: msg.sessionId, title: msg.title, message: msg.message, level: msg.level || 'info', time: msg.timestamp });
|
|
1467
1477
|
renderNotifications();
|
|
1468
1478
|
break;
|
|
1469
1479
|
case 'permission_request':
|
|
1470
|
-
|
|
1471
|
-
state.permissionRequests[msg.sessionId].unshift({
|
|
1472
|
-
requestId: msg.requestId,
|
|
1473
|
-
toolName: msg.toolName,
|
|
1474
|
-
description: msg.description,
|
|
1475
|
-
inputPreview: msg.inputPreview,
|
|
1476
|
-
timestamp: msg.timestamp,
|
|
1477
|
-
resolved: false,
|
|
1478
|
-
behavior: null,
|
|
1479
|
-
choices: msg.choices || null,
|
|
1480
|
-
sent: null,
|
|
1481
|
-
outcome: null,
|
|
1482
|
-
});
|
|
1483
|
-
// Auto-select session if none selected
|
|
1484
|
-
if (!state.selectedSession && state.sessions[msg.sessionId]) selectSession(msg.sessionId);
|
|
1485
|
-
if (state.selectedSession === msg.sessionId) { renderMessages(); renderPermissionBar(); }
|
|
1486
|
-
// Also show in notifications with permission info
|
|
1487
|
-
state.notifications.unshift({ sessionId: msg.sessionId, title: 'Permission Request', message: `${msg.toolName}: ${msg.description}`, level: 'warning', time: msg.timestamp, permRequestId: msg.requestId });
|
|
1488
|
-
renderNotifications();
|
|
1489
|
-
// Play notification sound effect via title flash
|
|
1490
|
-
flashTitle('Permission Request');
|
|
1480
|
+
showPermissionRequest(msg);
|
|
1491
1481
|
break;
|
|
1492
1482
|
case 'permission_response':
|
|
1493
1483
|
// Another source (Telegram, other dashboard) resolved a permission request
|
|
@@ -1501,11 +1491,7 @@
|
|
|
1501
1491
|
}
|
|
1502
1492
|
break;
|
|
1503
1493
|
case 'permission_pending':
|
|
1504
|
-
|
|
1505
|
-
handleMessage({ type: 'permission_request', ...m });
|
|
1506
|
-
}
|
|
1507
|
-
renderPermissionBar();
|
|
1508
|
-
renderNotifications();
|
|
1494
|
+
restorePendingRequests(msg.requests || []);
|
|
1509
1495
|
break;
|
|
1510
1496
|
case 'codex_adapters': {
|
|
1511
1497
|
state.codexAdapters = msg.adapters || [];
|
|
@@ -2128,6 +2114,46 @@
|
|
|
2128
2114
|
return missing;
|
|
2129
2115
|
}
|
|
2130
2116
|
|
|
2117
|
+
function addPermissionRequest(msg) {
|
|
2118
|
+
if (!state.permissionRequests[msg.sessionId]) state.permissionRequests[msg.sessionId] = [];
|
|
2119
|
+
state.permissionRequests[msg.sessionId].unshift({
|
|
2120
|
+
requestId: msg.requestId,
|
|
2121
|
+
toolName: msg.toolName,
|
|
2122
|
+
description: msg.description,
|
|
2123
|
+
inputPreview: msg.inputPreview,
|
|
2124
|
+
timestamp: msg.timestamp,
|
|
2125
|
+
resolved: false,
|
|
2126
|
+
behavior: null,
|
|
2127
|
+
choices: msg.choices || null,
|
|
2128
|
+
sent: null,
|
|
2129
|
+
outcome: null,
|
|
2130
|
+
});
|
|
2131
|
+
if (!state.selectedSession && state.sessions[msg.sessionId]) selectSession(msg.sessionId);
|
|
2132
|
+
if (state.selectedSession === msg.sessionId) { renderMessages(); renderPermissionBar(); }
|
|
2133
|
+
}
|
|
2134
|
+
|
|
2135
|
+
function showPermissionRequest(msg) {
|
|
2136
|
+
addPermissionRequest(msg);
|
|
2137
|
+
state.notifications.unshift({ sessionId: msg.sessionId, title: 'Permission Request', message: `${msg.toolName}: ${msg.description}`, level: 'warning', time: msg.timestamp, permRequestId: msg.requestId });
|
|
2138
|
+
renderNotifications();
|
|
2139
|
+
flashTitle('Permission Request');
|
|
2140
|
+
}
|
|
2141
|
+
|
|
2142
|
+
// A reconnect can bring back many requests at once; one row and one flash keep them from burying the notifications.
|
|
2143
|
+
function restorePendingRequests(requests) {
|
|
2144
|
+
const restored = applyPendingChoices(state.permissionRequests, requests);
|
|
2145
|
+
for (const m of restored) addPermissionRequest(m);
|
|
2146
|
+
if (restored.length) {
|
|
2147
|
+
const label = `${restored.length} approval request(s) waiting`;
|
|
2148
|
+
const listed = restored.slice(0, 3).map(m => `${m.toolName}: ${m.description}`).join(' · ');
|
|
2149
|
+
const message = restored.length > 3 ? `${listed} …and ${restored.length - 3} more` : listed;
|
|
2150
|
+
state.notifications.unshift({ sessionId: restored[0].sessionId, title: label, message, level: 'warning', time: Date.now() });
|
|
2151
|
+
flashTitle(label);
|
|
2152
|
+
}
|
|
2153
|
+
renderPermissionBar();
|
|
2154
|
+
renderNotifications();
|
|
2155
|
+
}
|
|
2156
|
+
|
|
2131
2157
|
function sendPermissionChoice(sessionId, requestId, choiceId) {
|
|
2132
2158
|
if (!state.ws || state.ws.readyState !== WebSocket.OPEN) return;
|
|
2133
2159
|
state.ws.send(JSON.stringify({ type: 'permission_response', sessionId, requestId, choiceId }));
|
|
@@ -2211,23 +2237,28 @@
|
|
|
2211
2237
|
}, true);
|
|
2212
2238
|
|
|
2213
2239
|
// Flash title for attention
|
|
2214
|
-
let
|
|
2240
|
+
let flash = null;
|
|
2215
2241
|
function flashTitle(msg) {
|
|
2216
|
-
|
|
2217
|
-
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
2224
|
-
|
|
2225
|
-
|
|
2226
|
-
document.title = original;
|
|
2227
|
-
|
|
2242
|
+
// While flashing, document.title may hold the flash text, so only a flash that starts from rest reads the real title.
|
|
2243
|
+
if (flash) {
|
|
2244
|
+
flash.msg = msg;
|
|
2245
|
+
return;
|
|
2246
|
+
}
|
|
2247
|
+
const current = { msg, original: document.title, on: true, interval: null, timeout: null };
|
|
2248
|
+
const stop = () => {
|
|
2249
|
+
clearInterval(current.interval);
|
|
2250
|
+
clearTimeout(current.timeout);
|
|
2251
|
+
window.removeEventListener('focus', stop);
|
|
2252
|
+
document.title = current.original;
|
|
2253
|
+
flash = null;
|
|
2228
2254
|
};
|
|
2229
|
-
|
|
2230
|
-
|
|
2255
|
+
current.interval = setInterval(() => {
|
|
2256
|
+
document.title = current.on ? `** ${current.msg} **` : current.original;
|
|
2257
|
+
current.on = !current.on;
|
|
2258
|
+
}, 500);
|
|
2259
|
+
current.timeout = setTimeout(stop, 30000);
|
|
2260
|
+
window.addEventListener('focus', stop);
|
|
2261
|
+
flash = current;
|
|
2231
2262
|
}
|
|
2232
2263
|
|
|
2233
2264
|
// Clear all notifications
|
package/dist/hub/server.js
CHANGED
|
@@ -249,6 +249,7 @@ var MAX_SELECTIONS = 20;
|
|
|
249
249
|
var MAX_VISIBLE_CHARS = 4e3;
|
|
250
250
|
var TRUNCATED = "\u2026(truncated)";
|
|
251
251
|
var MAX_PHOTO_BYTES = 10 * 1024 * 1024;
|
|
252
|
+
var NO_LONGER_CONNECTED = "the session is no longer connected";
|
|
252
253
|
function visibleLength(html) {
|
|
253
254
|
return html.replace(/<[^>]*>/g, "").replace(/&(?:amp|lt|gt);/g, "&").length;
|
|
254
255
|
}
|
|
@@ -422,13 +423,7 @@ ${TRUNCATED}`);
|
|
|
422
423
|
this.selections.delete(latest);
|
|
423
424
|
const session = this.pendingSession(pending, parseInt(selectMatch[1], 10) - 1);
|
|
424
425
|
if (session) {
|
|
425
|
-
|
|
426
|
-
if (pending.photoFileId) {
|
|
427
|
-
delivered = await this.deliverPhotoToSessionByFileId(session.id, pending.photoFileId, pending.caption);
|
|
428
|
-
} else if (pending.text) {
|
|
429
|
-
this.deliverToSession(session.id, pending.text);
|
|
430
|
-
}
|
|
431
|
-
if (delivered) this.sendMessage(`Sent to [${this.getLabel(session)}]`);
|
|
426
|
+
if (await this.deliverPending(session.id, pending)) this.sendMessage(`Sent to [${this.getLabel(session)}]`);
|
|
432
427
|
} else {
|
|
433
428
|
this.sendMessage("Invalid session number.");
|
|
434
429
|
}
|
|
@@ -470,9 +465,13 @@ ${TRUNCATED}`);
|
|
|
470
465
|
this.sendMessage("Multiple sessions active. Select one:", void 0, { inline_keyboard: rows });
|
|
471
466
|
}
|
|
472
467
|
deliverToSession(sessionId, content) {
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
468
|
+
const delivered = this.onMessageToSession?.(sessionId, content) === true;
|
|
469
|
+
if (!delivered) void this.sendMessage(`Not delivered: ${NO_LONGER_CONNECTED}`);
|
|
470
|
+
return delivered;
|
|
471
|
+
}
|
|
472
|
+
async deliverPending(sessionId, pending) {
|
|
473
|
+
if (pending.photoFileId) return this.deliverPhotoToSessionByFileId(sessionId, pending.photoFileId, pending.caption);
|
|
474
|
+
return pending.text ? this.deliverToSession(sessionId, pending.text) : false;
|
|
476
475
|
}
|
|
477
476
|
async deliverPhotoToSession(sessionId, photos, caption) {
|
|
478
477
|
const largest = photos[photos.length - 1];
|
|
@@ -504,8 +503,19 @@ ${TRUNCATED}`);
|
|
|
504
503
|
const filePath = path2.join(UPLOADS_DIR, filename);
|
|
505
504
|
fs.writeFileSync(filePath, buffer);
|
|
506
505
|
logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
|
|
507
|
-
|
|
508
|
-
|
|
506
|
+
let accepted = false;
|
|
507
|
+
try {
|
|
508
|
+
accepted = this.onImageToSession?.(sessionId, filePath, mimeType, caption) === true;
|
|
509
|
+
} catch (err) {
|
|
510
|
+
logger.warn(`Telegram photo hand-off failed: ${err.message}`);
|
|
511
|
+
}
|
|
512
|
+
if (!accepted) {
|
|
513
|
+
try {
|
|
514
|
+
fs.unlinkSync(filePath);
|
|
515
|
+
} catch {
|
|
516
|
+
}
|
|
517
|
+
this.photoNotDelivered(NO_LONGER_CONNECTED);
|
|
518
|
+
return false;
|
|
509
519
|
}
|
|
510
520
|
setTimeout(() => {
|
|
511
521
|
try {
|
|
@@ -513,7 +523,7 @@ ${TRUNCATED}`);
|
|
|
513
523
|
} catch {
|
|
514
524
|
}
|
|
515
525
|
}, 5 * 60 * 1e3).unref();
|
|
516
|
-
return
|
|
526
|
+
return true;
|
|
517
527
|
} catch (err) {
|
|
518
528
|
logger.warn(`Telegram photo download failed: ${err.message}`);
|
|
519
529
|
this.photoNotDelivered("the download failed");
|
|
@@ -708,15 +718,10 @@ ${label}`);
|
|
|
708
718
|
return;
|
|
709
719
|
}
|
|
710
720
|
this.selections.delete(selectionId);
|
|
711
|
-
if (pending
|
|
712
|
-
|
|
713
|
-
if (
|
|
714
|
-
|
|
715
|
-
if (query.message) await this.removeButtons(query.message.chat.id, query.message.message_id);
|
|
716
|
-
return;
|
|
717
|
-
}
|
|
718
|
-
} else if (pending.text) {
|
|
719
|
-
this.deliverToSession(session.id, pending.text);
|
|
721
|
+
if (!await this.deliverPending(session.id, pending)) {
|
|
722
|
+
await this.answerCallbackQuery(query.id, pending.photoFileId ? "Photo not delivered" : "Not delivered");
|
|
723
|
+
if (query.message) await this.removeButtons(query.message.chat.id, query.message.message_id);
|
|
724
|
+
return;
|
|
720
725
|
}
|
|
721
726
|
await this.answerCallbackQuery(query.id, `Sent to ${this.getLabel(session)}`);
|
|
722
727
|
if (query.message) {
|
|
@@ -1471,7 +1476,16 @@ var HubServer = class {
|
|
|
1471
1476
|
this.sessions.updateActivity(msg.sessionId);
|
|
1472
1477
|
const notifySession = this.sessions.get(msg.sessionId);
|
|
1473
1478
|
const notifyLabel = this.getSessionLabel(notifySession);
|
|
1474
|
-
|
|
1479
|
+
const notifyTitle = `[${notifyLabel}] ${msg.title}`;
|
|
1480
|
+
if (msg.to === "api") {
|
|
1481
|
+
logger.info(`${notifyTitle}: ${msg.message}`);
|
|
1482
|
+
break;
|
|
1483
|
+
}
|
|
1484
|
+
if (msg.to === "telegram") {
|
|
1485
|
+
void this.telegramBot?.sendNotification(msg.sessionId, notifyLabel, notifyTitle, msg.message);
|
|
1486
|
+
break;
|
|
1487
|
+
}
|
|
1488
|
+
if (msg.to !== "dashboard") this.notifier.notifyWithSession(msg.sessionId, notifyLabel, notifyTitle, msg.message, msg.level ?? "info");
|
|
1475
1489
|
this.broadcastToDashboards({
|
|
1476
1490
|
type: "notification",
|
|
1477
1491
|
sessionId: msg.sessionId,
|
|
@@ -1673,6 +1687,11 @@ var HubServer = class {
|
|
|
1673
1687
|
const channelWs = this.channelSockets.get(msg.sessionId);
|
|
1674
1688
|
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
1675
1689
|
channelWs.send(JSON.stringify({ ...msg, source: "dashboard" }));
|
|
1690
|
+
} else {
|
|
1691
|
+
const reason = "the session is not connected";
|
|
1692
|
+
logger.warn(`Message rejected for ${msg.sessionId}: ${reason}`);
|
|
1693
|
+
const rejected = { type: "message_rejected", sessionId: msg.sessionId, reason };
|
|
1694
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(rejected));
|
|
1676
1695
|
}
|
|
1677
1696
|
} else if (msg.type === "image_upload") {
|
|
1678
1697
|
this.handleImageUpload(ws, msg);
|
|
@@ -1798,19 +1817,25 @@ var HubServer = class {
|
|
|
1798
1817
|
this.telegramBot.getSessions = () => this.sessions.getAll();
|
|
1799
1818
|
this.telegramBot.onMessageToSession = (sessionId, content) => {
|
|
1800
1819
|
const channelWs = this.channelSockets.get(sessionId);
|
|
1801
|
-
if (channelWs?.readyState
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
1820
|
+
if (channelWs?.readyState !== WebSocket.OPEN) {
|
|
1821
|
+
logger.warn(`Telegram message not delivered to ${sessionId}: the session is not connected`);
|
|
1822
|
+
return false;
|
|
1805
1823
|
}
|
|
1824
|
+
const msg = { type: "message_to_session", sessionId, content, source: "telegram" };
|
|
1825
|
+
channelWs.send(JSON.stringify(msg));
|
|
1826
|
+
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
1827
|
+
return true;
|
|
1806
1828
|
};
|
|
1807
1829
|
this.telegramBot.onImageToSession = (sessionId, imagePath, mimeType, caption) => {
|
|
1808
1830
|
const channelWs = this.channelSockets.get(sessionId);
|
|
1809
|
-
if (channelWs?.readyState
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
1831
|
+
if (channelWs?.readyState !== WebSocket.OPEN) {
|
|
1832
|
+
logger.warn(`Telegram photo not delivered to ${sessionId}: the session is not connected`);
|
|
1833
|
+
return false;
|
|
1813
1834
|
}
|
|
1835
|
+
const msg = { type: "image_to_session", sessionId, imagePath, mimeType, content: caption, source: "telegram" };
|
|
1836
|
+
channelWs.send(JSON.stringify(msg));
|
|
1837
|
+
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
1838
|
+
return true;
|
|
1814
1839
|
};
|
|
1815
1840
|
this.telegramBot.onPermissionVerdict = (sessionId, requestId, behavior) => {
|
|
1816
1841
|
if (this.forwardPermissionResponse({ sessionId, requestId, behavior })) {
|