polygram 0.8.0-rc.40 → 0.8.0-rc.41

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.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://anthropic.com/claude-code/plugin.schema.json",
3
3
  "name": "polygram",
4
- "version": "0.8.0-rc.40",
4
+ "version": "0.8.0-rc.41",
5
5
  "description": "Telegram integration for Claude Code that preserves the OpenClaw per-chat session model. Migration target for OpenClaw users. Multi-bot, multi-chat, per-topic isolation; SQLite transcripts; inline-keyboard approvals. Bundles /polygram:status|logs|pair-code|approvals admin commands and a history skill.",
6
6
  "keywords": [
7
7
  "telegram",
@@ -2,35 +2,72 @@
2
2
  * Detect "stop working on the current turn" signals in natural language.
3
3
  *
4
4
  * Mirrors OpenClaw's isAbortRequestText semantics: users should be able to
5
- * say "stop" / "подожди" / "cancel" / or just `/stop` and have polygram
5
+ * say "stop" / "стоп" / "cancel" / or just `/stop` and have polygram
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
9
  * emoji" should NOT abort. So we require ONE of:
10
10
  * 1. The whole message (after stripping leading @-mention + trailing
11
- * punctuation) is an exact match against a known abort phrase, OR
11
+ * punctuation) is an exact match against a known abort phrase
12
+ * (HARD or SOFT phrases — see below), OR
12
13
  * 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).
14
+ * 3. The FIRST SENTENCE (split on . ! ?) is an exact match against the
15
+ * HARD phrases ONLY. Catches "Stop. I'll ask in another session." —
16
+ * clear abort intent with continuation. Does NOT trigger on
17
+ * "Wait? Something is off..." (rc.41 false-positive fix soft words
18
+ * like "wait" / "hold on" are too conversational to abort on
19
+ * first-sentence alone).
20
+ *
21
+ * Hard phrases (whole-message OR first-sentence trigger):
22
+ * English: stop, cancel, abort, halt
23
+ * Russian: стоп, остановись, остановить, отмена, прекрати, прекращай,
24
+ * хватит, отставить
25
+ *
26
+ * Soft phrases (whole-message ONLY):
27
+ * English: wait, hold on, hold up, nevermind, never mind, nvm,
28
+ * forget it, forget that
29
+ * Russian: подожди, подожди-ка, забей, не надо, отмени
30
+ *
31
+ * The split exists because "wait", "hold on", "подожди" are commonly used
32
+ * as conversational openers ("Wait? There is something wrong..." — Ivan DM
33
+ * 2026-05-01 19:01) where the user is NOT asking the bot to stop, they're
34
+ * flagging an issue. Hard phrases ("stop", "cancel", "abort") are
35
+ * unambiguously about ending the current task.
17
36
  *
18
37
  * Not detected (on purpose):
19
38
  * - "stop using markdown" → first sentence is the whole thing, not exact
20
39
  * - "I said stop" → not at start / not exact match
40
+ * - "Wait? Something is wrong..." (rc.41) — soft word, multi-sentence
41
+ * - "Hold on, let me think" — same shape
21
42
  */
22
43
 
23
- const ABORT_PHRASES = new Set([
44
+ 'use strict';
45
+
46
+ // HARD phrases: unambiguous abort intent. Trigger on whole-message OR
47
+ // first-sentence match.
48
+ const HARD_ABORT_PHRASES = new Set([
24
49
  // English
25
- 'stop', 'wait', 'cancel', 'abort', 'halt',
26
- 'hold on', 'hold up', 'nevermind', 'never mind', 'nvm',
50
+ 'stop', 'cancel', 'abort', 'halt',
51
+ // Russian
52
+ 'стоп', 'остановись', 'остановить',
53
+ 'отмена', 'прекрати', 'прекращай', 'хватит', 'отставить',
54
+ ]);
55
+
56
+ // SOFT phrases: conversational filler that COULD mean abort but commonly
57
+ // doesn't. Whole-message match only.
58
+ const SOFT_ABORT_PHRASES = new Set([
59
+ // English
60
+ 'wait', 'hold on', 'hold up', 'nevermind', 'never mind', 'nvm',
27
61
  'forget it', 'forget that',
28
62
  // Russian
29
- 'стоп', 'подожди', 'подожди-ка', 'остановись', 'остановить',
30
- 'отмена', 'отставить', 'прекрати', 'прекращай', 'хватит',
31
- 'забей', 'не надо', 'отмени',
63
+ 'подожди', 'подожди-ка', 'забей', 'не надо', 'отмени',
32
64
  ]);
33
65
 
66
+ // Combined set for whole-message matching. Kept exported as ABORT_PHRASES
67
+ // for backward compatibility with any callers / tests that import it
68
+ // directly.
69
+ const ABORT_PHRASES = new Set([...HARD_ABORT_PHRASES, ...SOFT_ABORT_PHRASES]);
70
+
34
71
  const ABORT_SLASH_RE = /^\/(stop|abort|cancel)(\s|$|@)/i;
35
72
 
36
73
  // Strip leading @botname mentions ("@shumobot stop" → "stop"). Matches any
@@ -58,20 +95,28 @@ function isAbortRequest(text) {
58
95
  const n = normalize(text);
59
96
  if (!n) return false;
60
97
  // Whole-message exact match (capped — a long message that happens to
61
- // start with "stop" is real content, not an abort).
98
+ // start with "stop" is real content, not an abort). HARD or SOFT
99
+ // phrases both qualify here — the user typed JUST that word, which is
100
+ // unambiguous regardless of category.
62
101
  if (n.length <= 40 && ABORT_PHRASES.has(n)) return true;
63
102
 
64
103
  // 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.
104
+ // here" is ambiguous and stays non-abort). HARD phrases ONLY — soft
105
+ // phrases like "wait" or "hold on" are conversational openers ("Wait?
106
+ // There is something wrong...") and shouldn't hijack a message where
107
+ // the rest contains real content.
68
108
  const head = text.trim().replace(LEADING_MENTION_RE, '');
69
109
  const firstSentence = head.split(/[.!?]/, 1)[0]?.trim().toLowerCase();
70
- if (firstSentence && firstSentence.length <= 40 && ABORT_PHRASES.has(firstSentence)) {
110
+ if (firstSentence && firstSentence.length <= 40 && HARD_ABORT_PHRASES.has(firstSentence)) {
71
111
  return true;
72
112
  }
73
113
 
74
114
  return false;
75
115
  }
76
116
 
77
- module.exports = { isAbortRequest, ABORT_PHRASES };
117
+ module.exports = {
118
+ isAbortRequest,
119
+ ABORT_PHRASES,
120
+ HARD_ABORT_PHRASES,
121
+ SOFT_ABORT_PHRASES,
122
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "polygram",
3
- "version": "0.8.0-rc.40",
3
+ "version": "0.8.0-rc.41",
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": {