polygram 0.5.4 → 0.5.6

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 (2) hide show
  1. package/package.json +1 -1
  2. package/polygram.js +41 -9
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.5.4",
3
+ "version": "0.5.6",
4
4
  "description": "Telegram daemon for Claude Code that preserves the OpenClaw per-chat session model. Migration path for OpenClaw users moving to Claude Code.",
5
5
  "main": "lib/ipc-client.js",
6
6
  "bin": {
package/polygram.js CHANGED
@@ -571,6 +571,37 @@ async function sendToProcess(sessionKey, prompt, context = {}) {
571
571
  const CONCURRENT_WARN_THRESHOLD = 20;
572
572
  const inFlightHandlers = new Map(); // sessionKey → count
573
573
 
574
+ // Set true by the SIGTERM/SIGINT handler. Module-scoped so the
575
+ // fire-and-forget catch in dispatchHandleMessage can check it: when
576
+ // polygram is going down, in-flight handlers reject with "Process
577
+ // killed" / "Process exited" but those failures aren't "real" — the
578
+ // next boot's replay will re-dispatch them. Suppressing the user-facing
579
+ // error reply during shutdown removes a misleading post-mortem apology
580
+ // the user shouldn't see. (The boot replay's own _isReplay flag handles
581
+ // the OTHER half: suppressing if the replay itself fails.)
582
+ let isShuttingDown = false;
583
+
584
+ // Map a handler-error to a user-facing reply that says what happened
585
+ // and what to do next. The technical strings come from process-manager
586
+ // (idle / wall-clock timeouts) and node child_process (Process exited /
587
+ // killed). Anything we don't recognise falls back to a generic line
588
+ // with a single-line snippet of the error so the user can at least
589
+ // distinguish unique failures from the obvious "try again" cases.
590
+ function errorReplyText(err) {
591
+ const msg = err?.message || '';
592
+ if (/idle with no Claude activity/i.test(msg)) {
593
+ return '⏳ I went quiet too long without finishing. Try resending or simplifying the task.';
594
+ }
595
+ if (/wall-clock ceiling/i.test(msg)) {
596
+ return '⏱ This was taking too long, so I stopped. Try resending or simplifying the task.';
597
+ }
598
+ if (/Process (exited|killed)/i.test(msg)) {
599
+ return '💥 Something crashed on my end. Try again.';
600
+ }
601
+ const reason = msg.split('\n')[0].slice(0, 120);
602
+ return `Hit a snag: ${reason || 'unknown error'}. Try resending.`;
603
+ }
604
+
574
605
  // Sessions the operator just /stop'd (or natural-language "стоп"). Keyed
575
606
  // by sessionKey → timestamp of abort. ANY pending that rejects within
576
607
  // ABORT_GRACE_MS of the mark is considered abort-caused — its generic
@@ -627,14 +658,16 @@ function dispatchHandleMessage(sessionKey, chatId, msg, bot) {
627
658
  aborted: wasAborted || undefined,
628
659
  replay: isReplay || undefined,
629
660
  }), 'log handler-error');
630
- // Suppress the "Sorry, I couldn't process" reply when this turn is a
631
- // boot replay — the user typed this message minutes ago and has long
632
- // moved on. A late apology just adds more noise to the post-restart
633
- // chat (alongside the "Это снова реплей" cascade we're fixing).
634
- if (!wasAborted && !isReplay) {
661
+ // Suppress the user-facing error reply when:
662
+ // - boot replay (user typed this minutes ago and moved on)
663
+ // - polygram is shutting down (the failure is "Process killed" /
664
+ // "Process exited" which isn't a real error boot replay will
665
+ // re-dispatch it on next start)
666
+ // - user just /stop'd (already saw their abort acknowledgement)
667
+ if (!wasAborted && !isReplay && !isShuttingDown) {
635
668
  tg(bot, 'sendMessage', {
636
669
  chat_id: chatId,
637
- text: `Sorry, I couldn't process that message. The operator has been notified.`,
670
+ text: errorReplyText(err),
638
671
  reply_parameters: { message_id: msg.message_id },
639
672
  }, { source: 'error-reply', botName: BOT_NAME }).catch((replyErr) => {
640
673
  console.error(`[${sessionKey}] failed to send error reply: ${replyErr.message}`);
@@ -2157,10 +2190,9 @@ async function main() {
2157
2190
  // replay picks it up. Prevents "Sorry, I couldn't process that message"
2158
2191
  // from showing on every restart.
2159
2192
  const SHUTDOWN_DRAIN_MS = 30_000;
2160
- let shuttingDown = false;
2161
2193
  const shutdown = async () => {
2162
- if (shuttingDown) return;
2163
- shuttingDown = true;
2194
+ if (isShuttingDown) return;
2195
+ isShuttingDown = true;
2164
2196
  console.log('\nShutting down...');
2165
2197
  // 1. Stop accepting new inbound first so nothing new queues behind the drain.
2166
2198
  if (bot && bot._stop) bot._stop();