polygram 0.5.5 → 0.5.7
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/lib/abort-detector.js +24 -10
- package/package.json +1 -1
- package/polygram.js +26 -6
package/lib/abort-detector.js
CHANGED
|
@@ -6,14 +6,17 @@
|
|
|
6
6
|
* interrupt the in-flight turn instead of queueing the message behind it.
|
|
7
7
|
*
|
|
8
8
|
* Conservative on purpose. False positives hijack user intent — "stop using
|
|
9
|
-
* emoji" should NOT abort. So we require:
|
|
10
|
-
* 1. The message (after stripping leading @-mention + trailing
|
|
11
|
-
*
|
|
12
|
-
* 2. It
|
|
9
|
+
* emoji" should NOT abort. So we require ONE of:
|
|
10
|
+
* 1. The whole message (after stripping leading @-mention + trailing
|
|
11
|
+
* punctuation) is an exact match against a known abort phrase, OR
|
|
12
|
+
* 2. It starts with an explicit slash command: /stop, /abort, /cancel, OR
|
|
13
|
+
* 3. The FIRST SENTENCE (split on . ! ?) is an exact abort phrase. This
|
|
14
|
+
* catches "Stop. I'll ask in another session." — clear abort intent
|
|
15
|
+
* with continuation explaining what comes next. Comma is not a split
|
|
16
|
+
* character ("Stop, look here" is ambiguous and stays non-abort).
|
|
13
17
|
*
|
|
14
18
|
* Not detected (on purpose):
|
|
15
|
-
* - "
|
|
16
|
-
* - "stop using markdown" → has trailing content
|
|
19
|
+
* - "stop using markdown" → first sentence is the whole thing, not exact
|
|
17
20
|
* - "I said stop" → not at start / not exact match
|
|
18
21
|
*/
|
|
19
22
|
|
|
@@ -54,10 +57,21 @@ function isAbortRequest(text) {
|
|
|
54
57
|
|
|
55
58
|
const n = normalize(text);
|
|
56
59
|
if (!n) return false;
|
|
57
|
-
//
|
|
58
|
-
// content, not an abort.
|
|
59
|
-
if (n.length
|
|
60
|
-
|
|
60
|
+
// Whole-message exact match (capped — a long message that happens to
|
|
61
|
+
// start with "stop" is real content, not an abort).
|
|
62
|
+
if (n.length <= 40 && ABORT_PHRASES.has(n)) return true;
|
|
63
|
+
|
|
64
|
+
// First-sentence exact match. Splits on . ! ? (NOT comma — "Stop, look
|
|
65
|
+
// here" is ambiguous and stays non-abort). The leading @-mention has
|
|
66
|
+
// already been stripped by normalize but only on the whole string, so
|
|
67
|
+
// we strip it again on the raw text before splitting.
|
|
68
|
+
const head = text.trim().replace(LEADING_MENTION_RE, '');
|
|
69
|
+
const firstSentence = head.split(/[.!?]/, 1)[0]?.trim().toLowerCase();
|
|
70
|
+
if (firstSentence && firstSentence.length <= 40 && ABORT_PHRASES.has(firstSentence)) {
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return false;
|
|
61
75
|
}
|
|
62
76
|
|
|
63
77
|
module.exports = { isAbortRequest, ABORT_PHRASES };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "polygram",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.7",
|
|
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
|
@@ -576,12 +576,32 @@ const inFlightHandlers = new Map(); // sessionKey → count
|
|
|
576
576
|
// polygram is going down, in-flight handlers reject with "Process
|
|
577
577
|
// killed" / "Process exited" but those failures aren't "real" — the
|
|
578
578
|
// next boot's replay will re-dispatch them. Suppressing the user-facing
|
|
579
|
-
//
|
|
580
|
-
//
|
|
581
|
-
//
|
|
582
|
-
// suppressing the apology if the replay itself fails.)
|
|
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.)
|
|
583
582
|
let isShuttingDown = false;
|
|
584
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
|
+
|
|
585
605
|
// Sessions the operator just /stop'd (or natural-language "стоп"). Keyed
|
|
586
606
|
// by sessionKey → timestamp of abort. ANY pending that rejects within
|
|
587
607
|
// ABORT_GRACE_MS of the mark is considered abort-caused — its generic
|
|
@@ -638,7 +658,7 @@ function dispatchHandleMessage(sessionKey, chatId, msg, bot) {
|
|
|
638
658
|
aborted: wasAborted || undefined,
|
|
639
659
|
replay: isReplay || undefined,
|
|
640
660
|
}), 'log handler-error');
|
|
641
|
-
// Suppress the
|
|
661
|
+
// Suppress the user-facing error reply when:
|
|
642
662
|
// - boot replay (user typed this minutes ago and moved on)
|
|
643
663
|
// - polygram is shutting down (the failure is "Process killed" /
|
|
644
664
|
// "Process exited" which isn't a real error — boot replay will
|
|
@@ -647,7 +667,7 @@ function dispatchHandleMessage(sessionKey, chatId, msg, bot) {
|
|
|
647
667
|
if (!wasAborted && !isReplay && !isShuttingDown) {
|
|
648
668
|
tg(bot, 'sendMessage', {
|
|
649
669
|
chat_id: chatId,
|
|
650
|
-
text:
|
|
670
|
+
text: errorReplyText(err),
|
|
651
671
|
reply_parameters: { message_id: msg.message_id },
|
|
652
672
|
}, { source: 'error-reply', botName: BOT_NAME }).catch((replyErr) => {
|
|
653
673
|
console.error(`[${sessionKey}] failed to send error reply: ${replyErr.message}`);
|