claude-notification-plugin 1.1.96 โ†’ 1.1.98

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,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-notification-plugin",
3
- "version": "1.1.96",
3
+ "version": "1.1.98",
4
4
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
5
5
  "author": {
6
6
  "name": "Viacheslav Makarov",
package/README.md CHANGED
@@ -251,6 +251,7 @@ Projects are referenced with the `&` prefix (e.g. `&api`, `&api/branch`).
251
251
  | `/cancel &project[/branch]` | Cancel the active task |
252
252
  | `/drop &project N` | Remove task N from queue |
253
253
  | `/clear &project[/branch]` | Clear queue + reset session |
254
+ | `/clearchat` | Delete the bot's messages in this chat (private chats: bot's own; groups: all if admin) |
254
255
  | `/newsession [&project[/branch]]` | Reset session only (keep queue) |
255
256
  | `/projects` | List projects and paths |
256
257
  | `/addproject <alias> <path>` | Register a project alias |
package/commit-sha CHANGED
@@ -1 +1 @@
1
- 32972e747e117771d32217ae045b1e64ef008505
1
+ d616d4330e2f4b80ffc5c66df77e2dc6ab72f6bc
@@ -608,7 +608,7 @@ function formatDuration (ms) {
608
608
  // COMMAND HANDLERS
609
609
  // ----------------------
610
610
 
611
- async function handleCommand (cmd, args) {
611
+ async function handleCommand (cmd, args, messageId) {
612
612
  switch (cmd) {
613
613
  case '/status':
614
614
  return handleStatus(args);
@@ -620,6 +620,8 @@ async function handleCommand (cmd, args) {
620
620
  return handleDrop(args);
621
621
  case '/clear':
622
622
  return handleClear(args);
623
+ case '/clearchat':
624
+ return handleClearChat(messageId);
623
625
  case '/newsession':
624
626
  return handleNewSession(args);
625
627
  case '/projects':
@@ -842,6 +844,43 @@ function handleClear (args) {
842
844
  return `๐Ÿงน [${escapeHtml(label)}] Queue cleared (${count} tasks), session reset`;
843
845
  }
844
846
 
847
+ // Sweep deletes the bot's outgoing messages in the private chat. We can't
848
+ // know the bot's message-id range, so we walk backwards from the user's
849
+ // /clearchat command id and ask Telegram to delete each id. The bot has no
850
+ // permission to delete the user's own messages in private chats โ€” those
851
+ // requests fail silently and don't count. We stop early when an entire
852
+ // parallel batch comes back as failures (we've passed the bot's recent
853
+ // outputs into a stretch of user-only messages, or hit the 48h delete window).
854
+ async function handleClearChat (messageId) {
855
+ if (!messageId || messageId < 2) {
856
+ return 'โŒ /clearchat needs a message context';
857
+ }
858
+ const BATCH = 25; // โ‰ค Telegram's 30 req/sec ceiling for a single chat
859
+ const MAX_LOOKBACK = 5000;
860
+
861
+ let deleted = 0;
862
+ let attempted = 0;
863
+ let cursor = messageId - 1;
864
+ while (cursor > 0 && attempted < MAX_LOOKBACK) {
865
+ const ids = [];
866
+ for (let i = 0; i < BATCH && cursor - i > 0; i++) {
867
+ ids.push(cursor - i);
868
+ }
869
+ const results = await Promise.all(ids.map((id) => poller.deleteMessage(id)));
870
+ attempted += results.length;
871
+ const ok = results.filter(Boolean).length;
872
+ deleted += ok;
873
+ cursor -= BATCH;
874
+ if (ok === 0) {
875
+ break;
876
+ }
877
+ }
878
+ // Also drop the user's /clearchat command itself โ€” works only if Telegram
879
+ // accepts it (e.g. bot is admin in a group); silently skipped in private chats.
880
+ await poller.deleteMessage(messageId);
881
+ return `๐Ÿงน Deleted ${deleted} of ${attempted} bot messages.`;
882
+ }
883
+
845
884
  function handleNewSession (args) {
846
885
  const target = parseTarget(args);
847
886
  const projectAlias = target?.project || getDefaultProject(listenerConfig.projects);
@@ -1444,6 +1483,7 @@ function handleHelp () {
1444
1483
  /cancel [&project[/branch]] โ€” cancel task
1445
1484
  /drop &project N โ€” remove task from queue
1446
1485
  /clear &project[/branch] โ€” clear queue + reset session
1486
+ /clearchat โ€” delete the bot's messages in this chat (private chats: bot's own; groups: all if admin)
1447
1487
  /newsession [&project[/branch]] โ€” reset session (keep queue)
1448
1488
  /projects โ€” list projects
1449
1489
  /addproject &lt;alias&gt; &lt;path-or-/basename&gt; โ€” register a project
@@ -1583,7 +1623,7 @@ async function mainLoop () {
1583
1623
 
1584
1624
  if (parsed.type === 'command') {
1585
1625
  logger.info(`Command: ${parsed.cmd} ${parsed.args}`);
1586
- const response = await handleCommand(parsed.cmd, parsed.args);
1626
+ const response = await handleCommand(parsed.cmd, parsed.args, msg.messageId);
1587
1627
  if (response) {
1588
1628
  if (typeof response === 'object' && response.text) {
1589
1629
  await poller.sendMessage(response.text, msg.callbackQueryId ? null : msg.messageId, response.replyMarkup);
@@ -1616,6 +1656,7 @@ async function mainLoop () {
1616
1656
  { command: 'history', description: 'Recent task history' },
1617
1657
  { command: 'pty', description: 'PTY session diagnostics' },
1618
1658
  { command: 'sessions', description: 'List recent CC sessions' },
1659
+ { command: 'clearchat', description: 'Delete the bot\'s messages in this chat' },
1619
1660
  { command: 'help', description: 'Show all commands' },
1620
1661
  { command: 'stop', description: 'Stop listener' },
1621
1662
  ]);
@@ -328,28 +328,25 @@ export class PtyRunner extends EventEmitter {
328
328
  const inactivityMs = task.raw ? RAW_INACTIVITY_MS : this.timeout;
329
329
  const markerPromise = this._waitForMarker(pendingId, inactivityMs, session);
330
330
 
331
- // Send the task text to the PTY.
332
- // Bracketed paste mode (\x1b[200~...\x1b[201~) causes Claude to hang in ConPTY,
333
- // so we send raw text. For multiline messages, use backslash + Enter as line
334
- // continuation (Claude Code interprets \ + Enter as a newline within the prompt),
335
- // with delays between lines so Claude can process each one.
331
+ // Send the task text to the PTY. Bracketed paste mode (\x1b[200~...\x1b[201~)
332
+ // hangs claude under ConPTY, so we send raw text and submit with a CR.
333
+ // CRITICAL: write the text first, *then* the CR with a small delay โ€” claude's
334
+ // Ink-based input handler intermittently swallows submit when the CR arrives
335
+ // in the same PTY write as the text. Separating them is reliable.
336
+ // Multiline: each non-final line ends with `\\\r` (claude treats `\` + Enter
337
+ // as a soft newline within the prompt), with inter-line delays so claude
338
+ // processes each one.
336
339
  const lines = task.text.split(/\r?\n/);
337
340
  const writeLines = async () => {
338
- if (lines.length === 1) {
339
- session.pty.write(`${lines[0]}\r`);
340
- } else {
341
- for (let i = 0; i < lines.length; i++) {
342
- if (i > 0) {
343
- await new Promise(r => setTimeout(r, 300));
344
- }
345
- if (i < lines.length - 1) {
346
- session.pty.write(`${lines[i]}\\\r`);
347
- } else {
348
- session.pty.write(`${lines[i]}\r`);
349
- }
341
+ for (let i = 0; i < lines.length; i++) {
342
+ if (i > 0) {
343
+ await new Promise(r => setTimeout(r, 300));
350
344
  }
351
- // Extra Enter to submit the multiline prompt
352
- await new Promise(r => setTimeout(r, 300));
345
+ const isLast = i === lines.length - 1;
346
+ session.pty.write(isLast ? lines[i] : `${lines[i]}\\`);
347
+ // Submit the line. For non-final lines a soft-newline CR; for the final
348
+ // (or only) line, a delay then the submit CR.
349
+ await new Promise(r => setTimeout(r, isLast ? 300 : 0));
353
350
  session.pty.write('\r');
354
351
  }
355
352
  };
@@ -456,10 +453,12 @@ export class PtyRunner extends EventEmitter {
456
453
  this.logger.info(`Creating PTY session in ${workDir} with args: ${JSON.stringify(args)}`);
457
454
 
458
455
  const shell = process.platform === 'win32' ? 'cmd.exe' : '/bin/bash';
459
- // Switch CMD to UTF-8 codepage (65001) so non-ASCII prompts piped via
460
- // pty.write() reach claude as valid UTF-8. Without this, cmd.exe applies
461
- // the system OEM codepage to the input stream and Cyrillic/CJK bytes
462
- // arrive at claude mangled (`๏ฟฝ`), so the prompt is never submitted.
456
+ // Switch CMD to UTF-8 codepage (65001 = UTF-8, covers all Unicode:
457
+ // Cyrillic, CJK, Arabic, Hebrew, accented Latin, emoji, etc.) so non-ASCII
458
+ // prompts piped via pty.write() reach claude as valid UTF-8. Without this,
459
+ // cmd.exe applies the system OEM codepage to the input stream and any
460
+ // multi-byte UTF-8 sequence arrives at claude mangled (`๏ฟฝ`), so the
461
+ // prompt is never submitted.
463
462
  // `chcp` and `&` MUST be separate argv tokens โ€” bundling them into one
464
463
  // string ("chcp 65001 & claude") trips cmd.exe's argument parser.
465
464
  const shellArgs = process.platform === 'win32'
@@ -194,10 +194,10 @@ export class TelegramPoller {
194
194
 
195
195
  async deleteMessage (messageId) {
196
196
  if (!messageId) {
197
- return;
197
+ return false;
198
198
  }
199
199
  try {
200
- await fetch(`${this.baseUrl}/deleteMessage`, {
200
+ const res = await fetch(`${this.baseUrl}/deleteMessage`, {
201
201
  method: 'POST',
202
202
  headers: { 'Content-Type': 'application/json' },
203
203
  body: JSON.stringify({
@@ -205,8 +205,11 @@ export class TelegramPoller {
205
205
  message_id: messageId,
206
206
  }),
207
207
  });
208
+ const data = await res.json();
209
+ return !!data.ok;
208
210
  } catch (err) {
209
211
  this.logger.error(`deleteMessage error: ${err.message}`);
212
+ return false;
210
213
  }
211
214
  }
212
215
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-notification-plugin",
3
3
  "productName": "claude-notification-plugin",
4
- "version": "1.1.96",
4
+ "version": "1.1.98",
5
5
  "description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
6
6
  "type": "module",
7
7
  "engines": {