@delt/claude-alarm 1.3.0 → 1.4.1
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 +288 -60
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +220 -46
- package/dist/codex/main.js.map +1 -1
- package/dist/dashboard/index.html +77 -46
- package/dist/hub/server.js +83 -40
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +8 -2
- package/dist/index.js +83 -40
- 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) {
|
|
@@ -841,6 +846,11 @@ ${bodyLines.join("\n")}`;
|
|
|
841
846
|
import { spawn } from "child_process";
|
|
842
847
|
import fs2 from "fs";
|
|
843
848
|
import path3 from "path";
|
|
849
|
+
|
|
850
|
+
// src/codex/shutdown.ts
|
|
851
|
+
var SUPERVISOR_STOP_GRACE_MS = 3e4;
|
|
852
|
+
|
|
853
|
+
// src/hub/codex-supervisor.ts
|
|
844
854
|
var HEALTHY_RUN_MS = 6e4;
|
|
845
855
|
function resolveAdapterScript(baseDir) {
|
|
846
856
|
return [path3.join(baseDir, "..", "codex", "main.js"), path3.join(baseDir, "codex", "main.js")].find((p) => fs2.existsSync(p));
|
|
@@ -856,7 +866,7 @@ var CodexSupervisor = class {
|
|
|
856
866
|
this.spawnFn = opts.spawnFn ?? spawn;
|
|
857
867
|
this.minDelayMs = opts.minDelayMs ?? 2e3;
|
|
858
868
|
this.maxDelayMs = opts.maxDelayMs ?? 6e4;
|
|
859
|
-
this.stopGraceMs = opts.stopGraceMs ??
|
|
869
|
+
this.stopGraceMs = opts.stopGraceMs ?? SUPERVISOR_STOP_GRACE_MS;
|
|
860
870
|
this.env = opts.env;
|
|
861
871
|
this.delay = this.minDelayMs;
|
|
862
872
|
}
|
|
@@ -879,11 +889,22 @@ var CodexSupervisor = class {
|
|
|
879
889
|
this.timer = void 0;
|
|
880
890
|
const child = this.child;
|
|
881
891
|
this.child = void 0;
|
|
882
|
-
if (!child) return;
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
892
|
+
if (!child) return Promise.resolve();
|
|
893
|
+
return new Promise((resolve) => {
|
|
894
|
+
const done = () => {
|
|
895
|
+
clearTimeout(force);
|
|
896
|
+
child.removeListener("exit", done);
|
|
897
|
+
resolve();
|
|
898
|
+
};
|
|
899
|
+
const force = setTimeout(() => {
|
|
900
|
+
logger.warn(`Codex adapter did not exit within ${this.stopGraceMs}ms of stop; killing it`);
|
|
901
|
+
child.kill();
|
|
902
|
+
done();
|
|
903
|
+
}, this.stopGraceMs);
|
|
904
|
+
force.unref();
|
|
905
|
+
child.once("exit", done);
|
|
906
|
+
child.stdin?.end();
|
|
907
|
+
});
|
|
887
908
|
}
|
|
888
909
|
launch() {
|
|
889
910
|
const startedAt = Date.now();
|
|
@@ -1193,9 +1214,9 @@ var HubServer = class {
|
|
|
1193
1214
|
});
|
|
1194
1215
|
}
|
|
1195
1216
|
stop() {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1217
|
+
const adapterStopped = this.codexSupervisor?.stop();
|
|
1218
|
+
this.codexSupervisor = void 0;
|
|
1219
|
+
const serverStopped = new Promise((resolve) => {
|
|
1199
1220
|
if (this.heartbeatInterval) clearInterval(this.heartbeatInterval);
|
|
1200
1221
|
if (this.telegramBot) this.telegramBot.stopPolling();
|
|
1201
1222
|
for (const ws of this.channelSockets.values()) ws.terminate();
|
|
@@ -1215,6 +1236,8 @@ var HubServer = class {
|
|
|
1215
1236
|
resolve();
|
|
1216
1237
|
}, 3e3);
|
|
1217
1238
|
});
|
|
1239
|
+
return Promise.all([serverStopped, adapterStopped]).then(() => {
|
|
1240
|
+
});
|
|
1218
1241
|
}
|
|
1219
1242
|
// --- HTTP Handler ---
|
|
1220
1243
|
handleHttp(req, res) {
|
|
@@ -1471,7 +1494,16 @@ var HubServer = class {
|
|
|
1471
1494
|
this.sessions.updateActivity(msg.sessionId);
|
|
1472
1495
|
const notifySession = this.sessions.get(msg.sessionId);
|
|
1473
1496
|
const notifyLabel = this.getSessionLabel(notifySession);
|
|
1474
|
-
|
|
1497
|
+
const notifyTitle = `[${notifyLabel}] ${msg.title}`;
|
|
1498
|
+
if (msg.to === "api") {
|
|
1499
|
+
logger.info(`${notifyTitle}: ${msg.message}`);
|
|
1500
|
+
break;
|
|
1501
|
+
}
|
|
1502
|
+
if (msg.to === "telegram") {
|
|
1503
|
+
void this.telegramBot?.sendNotification(msg.sessionId, notifyLabel, notifyTitle, msg.message);
|
|
1504
|
+
break;
|
|
1505
|
+
}
|
|
1506
|
+
if (msg.to !== "dashboard") this.notifier.notifyWithSession(msg.sessionId, notifyLabel, notifyTitle, msg.message, msg.level ?? "info");
|
|
1475
1507
|
this.broadcastToDashboards({
|
|
1476
1508
|
type: "notification",
|
|
1477
1509
|
sessionId: msg.sessionId,
|
|
@@ -1673,6 +1705,11 @@ var HubServer = class {
|
|
|
1673
1705
|
const channelWs = this.channelSockets.get(msg.sessionId);
|
|
1674
1706
|
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
1675
1707
|
channelWs.send(JSON.stringify({ ...msg, source: "dashboard" }));
|
|
1708
|
+
} else {
|
|
1709
|
+
const reason = "the session is not connected";
|
|
1710
|
+
logger.warn(`Message rejected for ${msg.sessionId}: ${reason}`);
|
|
1711
|
+
const rejected = { type: "message_rejected", sessionId: msg.sessionId, reason };
|
|
1712
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(rejected));
|
|
1676
1713
|
}
|
|
1677
1714
|
} else if (msg.type === "image_upload") {
|
|
1678
1715
|
this.handleImageUpload(ws, msg);
|
|
@@ -1798,19 +1835,25 @@ var HubServer = class {
|
|
|
1798
1835
|
this.telegramBot.getSessions = () => this.sessions.getAll();
|
|
1799
1836
|
this.telegramBot.onMessageToSession = (sessionId, content) => {
|
|
1800
1837
|
const channelWs = this.channelSockets.get(sessionId);
|
|
1801
|
-
if (channelWs?.readyState
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
1838
|
+
if (channelWs?.readyState !== WebSocket.OPEN) {
|
|
1839
|
+
logger.warn(`Telegram message not delivered to ${sessionId}: the session is not connected`);
|
|
1840
|
+
return false;
|
|
1805
1841
|
}
|
|
1842
|
+
const msg = { type: "message_to_session", sessionId, content, source: "telegram" };
|
|
1843
|
+
channelWs.send(JSON.stringify(msg));
|
|
1844
|
+
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
1845
|
+
return true;
|
|
1806
1846
|
};
|
|
1807
1847
|
this.telegramBot.onImageToSession = (sessionId, imagePath, mimeType, caption) => {
|
|
1808
1848
|
const channelWs = this.channelSockets.get(sessionId);
|
|
1809
|
-
if (channelWs?.readyState
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
1849
|
+
if (channelWs?.readyState !== WebSocket.OPEN) {
|
|
1850
|
+
logger.warn(`Telegram photo not delivered to ${sessionId}: the session is not connected`);
|
|
1851
|
+
return false;
|
|
1813
1852
|
}
|
|
1853
|
+
const msg = { type: "image_to_session", sessionId, imagePath, mimeType, content: caption, source: "telegram" };
|
|
1854
|
+
channelWs.send(JSON.stringify(msg));
|
|
1855
|
+
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
1856
|
+
return true;
|
|
1814
1857
|
};
|
|
1815
1858
|
this.telegramBot.onPermissionVerdict = (sessionId, requestId, behavior) => {
|
|
1816
1859
|
if (this.forwardPermissionResponse({ sessionId, requestId, behavior })) {
|