@delt/claude-alarm 1.2.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/README.md +2 -1
- package/dist/cli.js +574 -206
- package/dist/cli.js.map +1 -1
- package/dist/codex/main.js +459 -93
- package/dist/codex/main.js.map +1 -1
- package/dist/dashboard/index.html +86 -41
- package/dist/hub/server.js +189 -69
- package/dist/hub/server.js.map +1 -1
- package/dist/index.d.ts +19 -3
- package/dist/index.js +189 -69
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/dashboard/index.html +86 -41
package/dist/index.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ type ChannelMessage = {
|
|
|
49
49
|
title: string;
|
|
50
50
|
message: string;
|
|
51
51
|
level?: NotifyLevel;
|
|
52
|
+
to?: MessageSource;
|
|
52
53
|
} | {
|
|
53
54
|
type: 'reply';
|
|
54
55
|
sessionId: string;
|
|
@@ -73,6 +74,15 @@ type ChannelMessage = {
|
|
|
73
74
|
originalName?: string;
|
|
74
75
|
content?: string;
|
|
75
76
|
source?: MessageSource;
|
|
77
|
+
} | {
|
|
78
|
+
type: 'upload_rejected';
|
|
79
|
+
sessionId: string;
|
|
80
|
+
reason: string;
|
|
81
|
+
withText: boolean;
|
|
82
|
+
} | {
|
|
83
|
+
type: 'message_rejected';
|
|
84
|
+
sessionId: string;
|
|
85
|
+
reason: string;
|
|
76
86
|
} | {
|
|
77
87
|
type: 'sessions_list';
|
|
78
88
|
sessions: SessionInfo[];
|
|
@@ -316,18 +326,20 @@ declare class TelegramBot {
|
|
|
316
326
|
private polling;
|
|
317
327
|
private pollTimer;
|
|
318
328
|
private messageSessionMap;
|
|
319
|
-
onMessageToSession?: (sessionId: string, content: string) =>
|
|
320
|
-
onImageToSession?: (sessionId: string, imagePath: string, mimeType: string, caption?: string) =>
|
|
329
|
+
onMessageToSession?: (sessionId: string, content: string) => boolean;
|
|
330
|
+
onImageToSession?: (sessionId: string, imagePath: string, mimeType: string, caption?: string) => boolean;
|
|
321
331
|
onPermissionVerdict?: (sessionId: string, requestId: string, behavior: 'allow' | 'deny') => void;
|
|
322
332
|
onChoiceVerdict?: (sessionId: string, requestId: string, choiceId: string) => void;
|
|
323
333
|
private choiceTokens;
|
|
324
334
|
private choiceMessages;
|
|
325
335
|
getSessions?: () => SessionInfo[];
|
|
326
|
-
private
|
|
336
|
+
private latestSelection;
|
|
337
|
+
private selections;
|
|
327
338
|
constructor(config: TelegramConfig);
|
|
328
339
|
private get apiUrl();
|
|
329
340
|
/** Send a notification message to Telegram */
|
|
330
341
|
sendNotification(sessionId: string, _sessionLabel: string, title: string, message: string): Promise<void>;
|
|
342
|
+
private fitNotification;
|
|
331
343
|
/** Send a text message to the configured chat */
|
|
332
344
|
private sendMessage;
|
|
333
345
|
/** Start long polling for incoming messages */
|
|
@@ -338,8 +350,10 @@ declare class TelegramBot {
|
|
|
338
350
|
private scheduleNextPoll;
|
|
339
351
|
private handleIncomingMessage;
|
|
340
352
|
private deliverToSession;
|
|
353
|
+
private deliverPending;
|
|
341
354
|
private deliverPhotoToSession;
|
|
342
355
|
private deliverPhotoToSessionByFileId;
|
|
356
|
+
private photoNotDelivered;
|
|
343
357
|
private pendingSession;
|
|
344
358
|
private getLabel;
|
|
345
359
|
/** Send a permission request with inline buttons */
|
|
@@ -352,6 +366,8 @@ declare class TelegramBot {
|
|
|
352
366
|
private handleCallbackQuery;
|
|
353
367
|
private handleSessionSelectCallback;
|
|
354
368
|
private answerCallbackQuery;
|
|
369
|
+
private expire;
|
|
370
|
+
private removeButtons;
|
|
355
371
|
private editMessageText;
|
|
356
372
|
private escHtml;
|
|
357
373
|
private static readonly LANG_BY_EXT;
|
package/dist/index.js
CHANGED
|
@@ -245,6 +245,14 @@ ${requestId}`;
|
|
|
245
245
|
// src/hub/telegram.ts
|
|
246
246
|
var TELEGRAM_API = "https://api.telegram.org/bot";
|
|
247
247
|
var MAX_CHOICE_MESSAGES = 200;
|
|
248
|
+
var MAX_SELECTIONS = 20;
|
|
249
|
+
var MAX_VISIBLE_CHARS = 4e3;
|
|
250
|
+
var TRUNCATED = "\u2026(truncated)";
|
|
251
|
+
var MAX_PHOTO_BYTES = 10 * 1024 * 1024;
|
|
252
|
+
var NO_LONGER_CONNECTED = "the session is no longer connected";
|
|
253
|
+
function visibleLength(html) {
|
|
254
|
+
return html.replace(/<[^>]*>/g, "").replace(/&(?:amp|lt|gt);/g, "&").length;
|
|
255
|
+
}
|
|
248
256
|
var TelegramBot = class _TelegramBot {
|
|
249
257
|
config;
|
|
250
258
|
offset = 0;
|
|
@@ -265,9 +273,9 @@ var TelegramBot = class _TelegramBot {
|
|
|
265
273
|
choiceMessages = /* @__PURE__ */ new Map();
|
|
266
274
|
// Callback: get current sessions list
|
|
267
275
|
getSessions;
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
276
|
+
latestSelection;
|
|
277
|
+
// Keyed by prompt, not by chat: buttons from an older prompt must still send that prompt's message.
|
|
278
|
+
selections = /* @__PURE__ */ new Map();
|
|
271
279
|
constructor(config) {
|
|
272
280
|
this.config = config;
|
|
273
281
|
}
|
|
@@ -276,8 +284,7 @@ var TelegramBot = class _TelegramBot {
|
|
|
276
284
|
}
|
|
277
285
|
/** Send a notification message to Telegram */
|
|
278
286
|
async sendNotification(sessionId, _sessionLabel, title, message) {
|
|
279
|
-
const text =
|
|
280
|
-
${this.mdToHtml(message)}`;
|
|
287
|
+
const text = this.fitNotification(title, message);
|
|
281
288
|
const result = await this.sendMessage(text);
|
|
282
289
|
if (result?.message_id) {
|
|
283
290
|
this.messageSessionMap.set(result.message_id, sessionId);
|
|
@@ -289,6 +296,25 @@ ${this.mdToHtml(message)}`;
|
|
|
289
296
|
}
|
|
290
297
|
}
|
|
291
298
|
}
|
|
299
|
+
fitNotification(title, message) {
|
|
300
|
+
const render = (body) => `<b>${this.escHtml(title)}</b>
|
|
301
|
+
${this.mdToHtml(body)}`;
|
|
302
|
+
const full = render(message);
|
|
303
|
+
if (visibleLength(full) <= MAX_VISIBLE_CHARS) return full;
|
|
304
|
+
const cut = (n) => {
|
|
305
|
+
const end = n > 0 && /[\uD800-\uDBFF]/.test(message[n - 1]) ? n - 1 : n;
|
|
306
|
+
return render(`${message.slice(0, end)}
|
|
307
|
+
${TRUNCATED}`);
|
|
308
|
+
};
|
|
309
|
+
let lo = 0;
|
|
310
|
+
let hi = message.length;
|
|
311
|
+
while (lo < hi) {
|
|
312
|
+
const mid = Math.ceil((lo + hi) / 2);
|
|
313
|
+
if (visibleLength(cut(mid)) <= MAX_VISIBLE_CHARS) lo = mid;
|
|
314
|
+
else hi = mid - 1;
|
|
315
|
+
}
|
|
316
|
+
return cut(lo);
|
|
317
|
+
}
|
|
292
318
|
/** Send a text message to the configured chat */
|
|
293
319
|
async sendMessage(text, replyToMessageId, replyMarkup) {
|
|
294
320
|
try {
|
|
@@ -370,6 +396,13 @@ ${this.mdToHtml(message)}`;
|
|
|
370
396
|
const hasPhoto = msg.photo && msg.photo.length > 0;
|
|
371
397
|
const text = (msg.text || msg.caption || "").trim();
|
|
372
398
|
if (!text && !hasPhoto) return;
|
|
399
|
+
if (hasPhoto) {
|
|
400
|
+
const size = msg.photo[msg.photo.length - 1].file_size;
|
|
401
|
+
if (size !== void 0 && size > MAX_PHOTO_BYTES) {
|
|
402
|
+
this.photoNotDelivered("it is larger than 10 MB");
|
|
403
|
+
return;
|
|
404
|
+
}
|
|
405
|
+
}
|
|
373
406
|
if (msg.reply_to_message) {
|
|
374
407
|
const sessionId = this.messageSessionMap.get(msg.reply_to_message.message_id);
|
|
375
408
|
if (sessionId) {
|
|
@@ -383,18 +416,14 @@ ${this.mdToHtml(message)}`;
|
|
|
383
416
|
}
|
|
384
417
|
if (text) {
|
|
385
418
|
const selectMatch = text.match(/^\/s_(\d+)$/);
|
|
386
|
-
|
|
387
|
-
|
|
419
|
+
const latest = this.latestSelection;
|
|
420
|
+
if (selectMatch && latest !== void 0) {
|
|
421
|
+
const pending = this.selections.get(latest);
|
|
388
422
|
if (pending) {
|
|
389
|
-
this.
|
|
423
|
+
this.selections.delete(latest);
|
|
390
424
|
const session = this.pendingSession(pending, parseInt(selectMatch[1], 10) - 1);
|
|
391
425
|
if (session) {
|
|
392
|
-
if (pending
|
|
393
|
-
await this.deliverPhotoToSessionByFileId(session.id, pending.photoFileId, pending.caption);
|
|
394
|
-
} else if (pending.text) {
|
|
395
|
-
this.deliverToSession(session.id, pending.text);
|
|
396
|
-
}
|
|
397
|
-
this.sendMessage(`Sent to [${this.getLabel(session)}]`);
|
|
426
|
+
if (await this.deliverPending(session.id, pending)) this.sendMessage(`Sent to [${this.getLabel(session)}]`);
|
|
398
427
|
} else {
|
|
399
428
|
this.sendMessage("Invalid session number.");
|
|
400
429
|
}
|
|
@@ -415,16 +444,19 @@ ${this.mdToHtml(message)}`;
|
|
|
415
444
|
}
|
|
416
445
|
return;
|
|
417
446
|
}
|
|
447
|
+
const selectionId = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
448
|
+
this.latestSelection = selectionId;
|
|
418
449
|
const sessionIds = sessions.map((s) => s.id);
|
|
419
450
|
if (hasPhoto) {
|
|
420
451
|
const largest = msg.photo[msg.photo.length - 1];
|
|
421
|
-
this.
|
|
452
|
+
this.selections.set(selectionId, { photoFileId: largest.file_id, caption: text, sessionIds });
|
|
422
453
|
} else {
|
|
423
|
-
this.
|
|
454
|
+
this.selections.set(selectionId, { text, sessionIds });
|
|
424
455
|
}
|
|
456
|
+
while (this.selections.size > MAX_SELECTIONS) this.selections.delete(this.selections.keys().next().value);
|
|
425
457
|
const buttons = sessions.map((s, i) => ({
|
|
426
458
|
text: this.getLabel(s),
|
|
427
|
-
callback_data: `
|
|
459
|
+
callback_data: `sel:${selectionId}:${i}`
|
|
428
460
|
}));
|
|
429
461
|
const rows = [];
|
|
430
462
|
for (let i = 0; i < buttons.length; i += 2) {
|
|
@@ -433,30 +465,37 @@ ${this.mdToHtml(message)}`;
|
|
|
433
465
|
this.sendMessage("Multiple sessions active. Select one:", void 0, { inline_keyboard: rows });
|
|
434
466
|
}
|
|
435
467
|
deliverToSession(sessionId, content) {
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
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;
|
|
439
475
|
}
|
|
440
476
|
async deliverPhotoToSession(sessionId, photos, caption) {
|
|
441
477
|
const largest = photos[photos.length - 1];
|
|
442
|
-
|
|
478
|
+
return this.deliverPhotoToSessionByFileId(sessionId, largest.file_id, caption);
|
|
443
479
|
}
|
|
444
480
|
async deliverPhotoToSessionByFileId(sessionId, fileId, caption) {
|
|
445
481
|
try {
|
|
446
482
|
const fileRes = await fetch(`${this.apiUrl}/getFile?file_id=${fileId}`);
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
return;
|
|
483
|
+
const fileData = fileRes.ok ? await fileRes.json() : void 0;
|
|
484
|
+
if (!fileData?.ok || !fileData.result) {
|
|
485
|
+
this.photoNotDelivered("Telegram did not return the file");
|
|
486
|
+
return false;
|
|
450
487
|
}
|
|
451
|
-
const fileData = await fileRes.json();
|
|
452
|
-
if (!fileData.ok) return;
|
|
453
488
|
const downloadUrl = `https://api.telegram.org/file/bot${this.config.botToken}/${fileData.result.file_path}`;
|
|
454
489
|
const imgRes = await fetch(downloadUrl);
|
|
455
490
|
if (!imgRes.ok) {
|
|
456
|
-
|
|
457
|
-
return;
|
|
491
|
+
this.photoNotDelivered("the download failed");
|
|
492
|
+
return false;
|
|
458
493
|
}
|
|
459
494
|
const buffer = Buffer.from(await imgRes.arrayBuffer());
|
|
495
|
+
if (buffer.length > MAX_PHOTO_BYTES) {
|
|
496
|
+
this.photoNotDelivered("it is larger than 10 MB");
|
|
497
|
+
return false;
|
|
498
|
+
}
|
|
460
499
|
const ext = fileData.result.file_path.split(".").pop() || "jpg";
|
|
461
500
|
const mimeType = ext === "png" ? "image/png" : ext === "gif" ? "image/gif" : ext === "webp" ? "image/webp" : "image/jpeg";
|
|
462
501
|
fs.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
@@ -464,19 +503,37 @@ ${this.mdToHtml(message)}`;
|
|
|
464
503
|
const filePath = path2.join(UPLOADS_DIR, filename);
|
|
465
504
|
fs.writeFileSync(filePath, buffer);
|
|
466
505
|
logger.info(`Telegram photo saved: ${filename} (${buffer.length} bytes)`);
|
|
467
|
-
|
|
468
|
-
|
|
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;
|
|
469
519
|
}
|
|
470
520
|
setTimeout(() => {
|
|
471
521
|
try {
|
|
472
522
|
fs.unlinkSync(filePath);
|
|
473
523
|
} catch {
|
|
474
524
|
}
|
|
475
|
-
}, 5 * 60 * 1e3);
|
|
525
|
+
}, 5 * 60 * 1e3).unref();
|
|
526
|
+
return true;
|
|
476
527
|
} catch (err) {
|
|
477
528
|
logger.warn(`Telegram photo download failed: ${err.message}`);
|
|
529
|
+
this.photoNotDelivered("the download failed");
|
|
530
|
+
return false;
|
|
478
531
|
}
|
|
479
532
|
}
|
|
533
|
+
photoNotDelivered(reason) {
|
|
534
|
+
logger.warn(`Telegram photo not delivered: ${reason}`);
|
|
535
|
+
void this.sendMessage(`Photo not delivered: ${reason}`);
|
|
536
|
+
}
|
|
480
537
|
// Buttons are numbered against the list shown when they were sent; Codex sessions come and go, so the live list may have shifted.
|
|
481
538
|
pendingSession(pending, idx) {
|
|
482
539
|
const id = pending.sessionIds[idx];
|
|
@@ -590,7 +647,7 @@ ${this.escHtml(description)}` : "") + (slice ? `
|
|
|
590
647
|
async handleChoiceCallback(query) {
|
|
591
648
|
const choice = this.choiceTokens.get(query.data.slice("pc:".length));
|
|
592
649
|
if (!choice) {
|
|
593
|
-
await this.
|
|
650
|
+
await this.expire(query);
|
|
594
651
|
return;
|
|
595
652
|
}
|
|
596
653
|
this.dropChoiceTokens(choice.sessionId, choice.requestId);
|
|
@@ -619,10 +676,14 @@ ${this.escHtml(description)}` : "") + (slice ? `
|
|
|
619
676
|
async handleCallbackQuery(query) {
|
|
620
677
|
if (!query.data) return;
|
|
621
678
|
if (String(query.message?.chat.id) !== String(this.config.chatId)) return;
|
|
622
|
-
if (query.data.startsWith("
|
|
679
|
+
if (query.data.startsWith("sel:")) {
|
|
623
680
|
await this.handleSessionSelectCallback(query);
|
|
624
681
|
return;
|
|
625
682
|
}
|
|
683
|
+
if (query.data.startsWith("sess:")) {
|
|
684
|
+
await this.expire(query);
|
|
685
|
+
return;
|
|
686
|
+
}
|
|
626
687
|
if (query.data.startsWith("pc:")) {
|
|
627
688
|
await this.handleChoiceCallback(query);
|
|
628
689
|
return;
|
|
@@ -645,21 +706,22 @@ ${label}`);
|
|
|
645
706
|
}
|
|
646
707
|
}
|
|
647
708
|
async handleSessionSelectCallback(query) {
|
|
648
|
-
const
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
709
|
+
const [, selectionId, idxStr] = query.data.split(":");
|
|
710
|
+
const pending = this.selections.get(selectionId);
|
|
711
|
+
if (!pending) {
|
|
712
|
+
await this.expire(query);
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
const session = this.pendingSession(pending, parseInt(idxStr, 10));
|
|
716
|
+
if (!session) {
|
|
655
717
|
await this.answerCallbackQuery(query.id, "Session not found");
|
|
656
718
|
return;
|
|
657
719
|
}
|
|
658
|
-
this.
|
|
659
|
-
if (pending
|
|
660
|
-
await this.
|
|
661
|
-
|
|
662
|
-
|
|
720
|
+
this.selections.delete(selectionId);
|
|
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;
|
|
663
725
|
}
|
|
664
726
|
await this.answerCallbackQuery(query.id, `Sent to ${this.getLabel(session)}`);
|
|
665
727
|
if (query.message) {
|
|
@@ -677,6 +739,21 @@ ${label}`);
|
|
|
677
739
|
logger.warn(`Telegram answerCallbackQuery error: ${err.message}`);
|
|
678
740
|
}
|
|
679
741
|
}
|
|
742
|
+
async expire(query) {
|
|
743
|
+
await this.answerCallbackQuery(query.id, "Expired");
|
|
744
|
+
if (query.message) await this.removeButtons(query.message.chat.id, query.message.message_id);
|
|
745
|
+
}
|
|
746
|
+
async removeButtons(chatId, messageId) {
|
|
747
|
+
try {
|
|
748
|
+
await fetch(`${this.apiUrl}/editMessageReplyMarkup`, {
|
|
749
|
+
method: "POST",
|
|
750
|
+
headers: { "Content-Type": "application/json" },
|
|
751
|
+
body: JSON.stringify({ chat_id: chatId, message_id: messageId, reply_markup: { inline_keyboard: [] } })
|
|
752
|
+
});
|
|
753
|
+
} catch (err) {
|
|
754
|
+
logger.warn(`Telegram editMessageReplyMarkup error: ${err.message}`);
|
|
755
|
+
}
|
|
756
|
+
}
|
|
680
757
|
async editMessageText(chatId, messageId, text) {
|
|
681
758
|
try {
|
|
682
759
|
await fetch(`${this.apiUrl}/editMessageText`, {
|
|
@@ -773,19 +850,30 @@ var HEALTHY_RUN_MS = 6e4;
|
|
|
773
850
|
function resolveAdapterScript(baseDir) {
|
|
774
851
|
return [path3.join(baseDir, "..", "codex", "main.js"), path3.join(baseDir, "codex", "main.js")].find((p) => fs2.existsSync(p));
|
|
775
852
|
}
|
|
853
|
+
function adapterEnv(base, hub) {
|
|
854
|
+
const env = { ...base, CLAUDE_ALARM_HUB_HOST: hub.host, CLAUDE_ALARM_HUB_PORT: String(hub.port) };
|
|
855
|
+
delete env.CLAUDE_ALARM_HUB_TOKEN;
|
|
856
|
+
return env;
|
|
857
|
+
}
|
|
776
858
|
var CodexSupervisor = class {
|
|
777
|
-
constructor(script,
|
|
859
|
+
constructor(script, opts = {}) {
|
|
778
860
|
this.script = script;
|
|
779
|
-
this.spawnFn = spawnFn;
|
|
780
|
-
this.minDelayMs = minDelayMs;
|
|
781
|
-
this.maxDelayMs = maxDelayMs;
|
|
782
|
-
this.stopGraceMs = stopGraceMs;
|
|
783
|
-
this.
|
|
861
|
+
this.spawnFn = opts.spawnFn ?? spawn;
|
|
862
|
+
this.minDelayMs = opts.minDelayMs ?? 2e3;
|
|
863
|
+
this.maxDelayMs = opts.maxDelayMs ?? 6e4;
|
|
864
|
+
this.stopGraceMs = opts.stopGraceMs ?? 3e3;
|
|
865
|
+
this.env = opts.env;
|
|
866
|
+
this.delay = this.minDelayMs;
|
|
784
867
|
}
|
|
785
868
|
child;
|
|
786
869
|
timer;
|
|
787
870
|
stopped = true;
|
|
788
871
|
delay;
|
|
872
|
+
spawnFn;
|
|
873
|
+
minDelayMs;
|
|
874
|
+
maxDelayMs;
|
|
875
|
+
stopGraceMs;
|
|
876
|
+
env;
|
|
789
877
|
start() {
|
|
790
878
|
this.stopped = false;
|
|
791
879
|
this.launch();
|
|
@@ -807,7 +895,8 @@ var CodexSupervisor = class {
|
|
|
807
895
|
const child = this.spawnFn(process.execPath, [this.script, "--watch-stdin"], {
|
|
808
896
|
detached: true,
|
|
809
897
|
stdio: ["pipe", "pipe", "pipe"],
|
|
810
|
-
windowsHide: true
|
|
898
|
+
windowsHide: true,
|
|
899
|
+
...this.env ? { env: this.env } : {}
|
|
811
900
|
});
|
|
812
901
|
this.child = child;
|
|
813
902
|
child.stdout?.on("data", (d) => process.stdout.write(d));
|
|
@@ -1411,7 +1500,16 @@ var HubServer = class {
|
|
|
1411
1500
|
this.sessions.updateActivity(msg.sessionId);
|
|
1412
1501
|
const notifySession = this.sessions.get(msg.sessionId);
|
|
1413
1502
|
const notifyLabel = this.getSessionLabel(notifySession);
|
|
1414
|
-
|
|
1503
|
+
const notifyTitle = `[${notifyLabel}] ${msg.title}`;
|
|
1504
|
+
if (msg.to === "api") {
|
|
1505
|
+
logger.info(`${notifyTitle}: ${msg.message}`);
|
|
1506
|
+
break;
|
|
1507
|
+
}
|
|
1508
|
+
if (msg.to === "telegram") {
|
|
1509
|
+
void this.telegramBot?.sendNotification(msg.sessionId, notifyLabel, notifyTitle, msg.message);
|
|
1510
|
+
break;
|
|
1511
|
+
}
|
|
1512
|
+
if (msg.to !== "dashboard") this.notifier.notifyWithSession(msg.sessionId, notifyLabel, notifyTitle, msg.message, msg.level ?? "info");
|
|
1415
1513
|
this.broadcastToDashboards({
|
|
1416
1514
|
type: "notification",
|
|
1417
1515
|
sessionId: msg.sessionId,
|
|
@@ -1613,9 +1711,14 @@ var HubServer = class {
|
|
|
1613
1711
|
const channelWs = this.channelSockets.get(msg.sessionId);
|
|
1614
1712
|
if (channelWs?.readyState === WebSocket.OPEN) {
|
|
1615
1713
|
channelWs.send(JSON.stringify({ ...msg, source: "dashboard" }));
|
|
1714
|
+
} else {
|
|
1715
|
+
const reason = "the session is not connected";
|
|
1716
|
+
logger.warn(`Message rejected for ${msg.sessionId}: ${reason}`);
|
|
1717
|
+
const rejected = { type: "message_rejected", sessionId: msg.sessionId, reason };
|
|
1718
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(rejected));
|
|
1616
1719
|
}
|
|
1617
1720
|
} else if (msg.type === "image_upload") {
|
|
1618
|
-
this.handleImageUpload(msg);
|
|
1721
|
+
this.handleImageUpload(ws, msg);
|
|
1619
1722
|
} else if (msg.type === "permission_response") {
|
|
1620
1723
|
if (this.forwardPermissionResponse(msg)) {
|
|
1621
1724
|
const verdict = msg.choiceId !== void 0 ? `choice ${msg.choiceId}` : msg.behavior;
|
|
@@ -1666,20 +1769,31 @@ var HubServer = class {
|
|
|
1666
1769
|
}
|
|
1667
1770
|
}
|
|
1668
1771
|
}
|
|
1669
|
-
handleImageUpload(msg) {
|
|
1772
|
+
handleImageUpload(ws, msg) {
|
|
1670
1773
|
const { sessionId, imageData, mimeType, originalName, content } = msg;
|
|
1774
|
+
const reject = (reason) => {
|
|
1775
|
+
logger.warn(`Image upload rejected for ${sessionId}: ${reason}`);
|
|
1776
|
+
const rejected = { type: "upload_rejected", sessionId, reason, withText: typeof content === "string" && content.trim().length > 0 };
|
|
1777
|
+
if (ws.readyState === WebSocket.OPEN) ws.send(JSON.stringify(rejected));
|
|
1778
|
+
};
|
|
1779
|
+
const channelWs = this.channelSockets.get(sessionId);
|
|
1780
|
+
if (!channelWs || channelWs.readyState !== WebSocket.OPEN) {
|
|
1781
|
+
reject("the session is not connected");
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1671
1784
|
if (!this.localChannels.has(sessionId)) {
|
|
1672
|
-
|
|
1785
|
+
reject("this session is on another PC; images can only go to sessions on the hub's PC");
|
|
1673
1786
|
return;
|
|
1674
1787
|
}
|
|
1675
|
-
const channelWs = this.channelSockets.get(sessionId);
|
|
1676
|
-
if (!channelWs || channelWs.readyState !== WebSocket.OPEN) return;
|
|
1677
1788
|
const allowedTypes = ["image/png", "image/jpeg", "image/gif", "image/webp"];
|
|
1678
|
-
if (!allowedTypes.includes(mimeType))
|
|
1789
|
+
if (!allowedTypes.includes(mimeType)) {
|
|
1790
|
+
reject("only PNG, JPEG, GIF and WebP images are supported");
|
|
1791
|
+
return;
|
|
1792
|
+
}
|
|
1679
1793
|
const base64Data = imageData.replace(/^data:image\/\w+;base64,/, "");
|
|
1680
1794
|
const buffer = Buffer.from(base64Data, "base64");
|
|
1681
1795
|
if (buffer.length > 10 * 1024 * 1024) {
|
|
1682
|
-
|
|
1796
|
+
reject("the image is larger than 10 MB");
|
|
1683
1797
|
return;
|
|
1684
1798
|
}
|
|
1685
1799
|
fs5.mkdirSync(UPLOADS_DIR, { recursive: true });
|
|
@@ -1727,19 +1841,25 @@ var HubServer = class {
|
|
|
1727
1841
|
this.telegramBot.getSessions = () => this.sessions.getAll();
|
|
1728
1842
|
this.telegramBot.onMessageToSession = (sessionId, content) => {
|
|
1729
1843
|
const channelWs = this.channelSockets.get(sessionId);
|
|
1730
|
-
if (channelWs?.readyState
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
1844
|
+
if (channelWs?.readyState !== WebSocket.OPEN) {
|
|
1845
|
+
logger.warn(`Telegram message not delivered to ${sessionId}: the session is not connected`);
|
|
1846
|
+
return false;
|
|
1734
1847
|
}
|
|
1848
|
+
const msg = { type: "message_to_session", sessionId, content, source: "telegram" };
|
|
1849
|
+
channelWs.send(JSON.stringify(msg));
|
|
1850
|
+
logger.info(`Telegram message forwarded to session: ${sessionId}`);
|
|
1851
|
+
return true;
|
|
1735
1852
|
};
|
|
1736
1853
|
this.telegramBot.onImageToSession = (sessionId, imagePath, mimeType, caption) => {
|
|
1737
1854
|
const channelWs = this.channelSockets.get(sessionId);
|
|
1738
|
-
if (channelWs?.readyState
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
1855
|
+
if (channelWs?.readyState !== WebSocket.OPEN) {
|
|
1856
|
+
logger.warn(`Telegram photo not delivered to ${sessionId}: the session is not connected`);
|
|
1857
|
+
return false;
|
|
1742
1858
|
}
|
|
1859
|
+
const msg = { type: "image_to_session", sessionId, imagePath, mimeType, content: caption, source: "telegram" };
|
|
1860
|
+
channelWs.send(JSON.stringify(msg));
|
|
1861
|
+
logger.info(`Telegram photo forwarded to session: ${sessionId}`);
|
|
1862
|
+
return true;
|
|
1743
1863
|
};
|
|
1744
1864
|
this.telegramBot.onPermissionVerdict = (sessionId, requestId, behavior) => {
|
|
1745
1865
|
if (this.forwardPermissionResponse({ sessionId, requestId, behavior })) {
|
|
@@ -1864,7 +1984,7 @@ var HubServer = class {
|
|
|
1864
1984
|
logger.warn("Codex adapter is enabled but codex/main.js was not found next to the hub");
|
|
1865
1985
|
return;
|
|
1866
1986
|
}
|
|
1867
|
-
this.codexSupervisor = new CodexSupervisor(script);
|
|
1987
|
+
this.codexSupervisor = new CodexSupervisor(script, { env: adapterEnv(process.env, { host: this.host, port: this.port }) });
|
|
1868
1988
|
this.codexSupervisor.start();
|
|
1869
1989
|
logger.info("Codex adapter started");
|
|
1870
1990
|
}
|