claude-notification-plugin 1.1.55 → 1.1.57
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/.claude-plugin/plugin.json +1 -1
- package/README.md +4 -2
- package/commit-sha +1 -1
- package/listener/LISTENER-DETAILED.md +2 -1
- package/listener/listener.js +8 -8
- package/listener/pty-runner.js +26 -8
- package/listener/telegram-poller.js +21 -4
- package/package.json +1 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-notification-plugin",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.57",
|
|
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
|
@@ -98,7 +98,8 @@ Config file: `~/.claude/claude-notify.config.json`
|
|
|
98
98
|
"logDir": "abs-path-to-listener-logs",
|
|
99
99
|
"taskLogDir": "abs-path-to-task-logs",
|
|
100
100
|
"liveConsole": true,
|
|
101
|
-
"
|
|
101
|
+
"liveConsoleIntervalMillis": 1,
|
|
102
|
+
"liveConsoleMaxOutputChars": 300
|
|
102
103
|
}
|
|
103
104
|
}
|
|
104
105
|
```
|
|
@@ -271,7 +272,8 @@ Projects are referenced with the `&` prefix (e.g. `&api`, `&api/branch`).
|
|
|
271
272
|
| `logDir` | `~/.claude` | Listener log directory |
|
|
272
273
|
| `taskLogDir` | same as `logDir` | Task Q&A log directory |
|
|
273
274
|
| `liveConsole` | `true` | Stream PTY output + tool activity to the "Running..." Telegram message in real-time |
|
|
274
|
-
| `
|
|
275
|
+
| `liveConsoleIntervalMillis`| `1` | Live console update interval in seconds |
|
|
276
|
+
| `liveConsoleMaxOutputChars`| `300` | Max characters of PTY output to show in live console |
|
|
275
277
|
|
|
276
278
|
|
|
277
279
|
### Projects and worktrees
|
package/commit-sha
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
f3f8f93367ed5e932cc772b3bb8568dcc7f0da0f
|
|
@@ -768,7 +768,8 @@ The output is cleaned from ANSI escape codes and Claude Code UI chrome (logo, st
|
|
|
768
768
|
|
|
769
769
|
Configuration:
|
|
770
770
|
- `liveConsole` — enable/disable (default: `true`)
|
|
771
|
-
- `
|
|
771
|
+
- `liveConsoleIntervalMillis` — update interval in seconds (default: `1`)
|
|
772
|
+
- `liveConsoleMaxOutputChars` — max characters of PTY output to show (default: `300`)
|
|
772
773
|
|
|
773
774
|
### PTY logs
|
|
774
775
|
|
package/listener/listener.js
CHANGED
|
@@ -127,8 +127,8 @@ const runner = new PtyRunner(logger, taskTimeout, taskLogger, taskLogDir);
|
|
|
127
127
|
const worktreeManager = new WorktreeManager(config, logger);
|
|
128
128
|
|
|
129
129
|
const liveConsoleEnabled = listenerConfig.liveConsole !== false; // default: true
|
|
130
|
-
const
|
|
131
|
-
const
|
|
130
|
+
const liveConsoleIntervalMillis = (listenerConfig.liveConsoleIntervalMillis || 1) * 1000;
|
|
131
|
+
const liveConsoleMaxOutputChars = listenerConfig.liveConsoleMaxOutputChars || 300;
|
|
132
132
|
|
|
133
133
|
const startTime = Date.now();
|
|
134
134
|
|
|
@@ -142,7 +142,7 @@ const liveConsoleTimers = new Map();
|
|
|
142
142
|
logger.info('Listener started');
|
|
143
143
|
logger.info(`Projects: ${JSON.stringify(Object.keys(listenerConfig.projects))}`);
|
|
144
144
|
logger.info(`Session continuity: ${continueSessionEnabled ? 'enabled' : 'disabled'}`);
|
|
145
|
-
logger.info(`Live console: ${liveConsoleEnabled ? `enabled (${
|
|
145
|
+
logger.info(`Live console: ${liveConsoleEnabled ? `enabled (${liveConsoleIntervalMillis / 1000}s interval, max ${liveConsoleMaxOutputChars} chars)` : 'disabled'}`);
|
|
146
146
|
|
|
147
147
|
// ----------------------
|
|
148
148
|
// DISCOVER WORKTREES ON START
|
|
@@ -277,7 +277,7 @@ runner.on('timeout', async (workDir, task) => {
|
|
|
277
277
|
|
|
278
278
|
await poller.deleteMessage(task.runningMessageId);
|
|
279
279
|
|
|
280
|
-
const headerShort = `⏰ <code>${label}</code>\nTask forcefully stopped —
|
|
280
|
+
const headerShort = `⏰ <code>${label}</code>\nTask forcefully stopped — no activity for ${timeoutMin} min`;
|
|
281
281
|
const headerFull = `${headerShort}: ${escapeHtml(task.text)}`;
|
|
282
282
|
const sentId = await poller.sendMessage(headerShort, task.telegramMessageId);
|
|
283
283
|
if (!sentId && task.telegramMessageId) {
|
|
@@ -339,11 +339,11 @@ function startLiveConsole (workDir, messageId, header) {
|
|
|
339
339
|
return;
|
|
340
340
|
}
|
|
341
341
|
// Take the tail that fits
|
|
342
|
-
const tail = cleaned.length >
|
|
343
|
-
? cleaned.slice(-
|
|
342
|
+
const tail = cleaned.length > liveConsoleMaxOutputChars
|
|
343
|
+
? cleaned.slice(-liveConsoleMaxOutputChars)
|
|
344
344
|
: cleaned;
|
|
345
345
|
// Trim to last complete line if we sliced mid-line
|
|
346
|
-
const output = cleaned.length >
|
|
346
|
+
const output = cleaned.length > liveConsoleMaxOutputChars
|
|
347
347
|
? tail.slice(tail.indexOf('\n') + 1)
|
|
348
348
|
: tail;
|
|
349
349
|
if (!output || output === lastSentText) {
|
|
@@ -360,7 +360,7 @@ function startLiveConsole (workDir, messageId, header) {
|
|
|
360
360
|
} catch (err) {
|
|
361
361
|
logger.warn(`Live console edit error: ${err.message}`);
|
|
362
362
|
}
|
|
363
|
-
},
|
|
363
|
+
}, liveConsoleIntervalMillis);
|
|
364
364
|
liveConsoleTimers.set(workDir, timer);
|
|
365
365
|
}
|
|
366
366
|
|
package/listener/pty-runner.js
CHANGED
|
@@ -106,6 +106,7 @@ export class PtyRunner extends EventEmitter {
|
|
|
106
106
|
for (const [pid, resolve] of this.pendingMarkers) {
|
|
107
107
|
const session = this._findSessionByPendingId(pid);
|
|
108
108
|
if (session && this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
109
|
+
session._lastActivityTime = Date.now();
|
|
109
110
|
this.pendingMarkers.delete(pid);
|
|
110
111
|
this._unlinkSafe(filePath);
|
|
111
112
|
resolve(marker);
|
|
@@ -138,6 +139,7 @@ export class PtyRunner extends EventEmitter {
|
|
|
138
139
|
this._unlinkSafe(filePath);
|
|
139
140
|
for (const [workDir, session] of this.sessions) {
|
|
140
141
|
if (this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
142
|
+
session._lastActivityTime = Date.now();
|
|
141
143
|
session._model = marker.model || '';
|
|
142
144
|
this.emit('ready', workDir, marker);
|
|
143
145
|
break;
|
|
@@ -147,6 +149,7 @@ export class PtyRunner extends EventEmitter {
|
|
|
147
149
|
// PostToolUse — update activity data (don't delete, gets overwritten)
|
|
148
150
|
for (const [, session] of this.sessions) {
|
|
149
151
|
if (this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
152
|
+
session._lastActivityTime = Date.now();
|
|
150
153
|
session._lastActivity = {
|
|
151
154
|
toolName: marker.toolName,
|
|
152
155
|
toolInput: marker.toolInput,
|
|
@@ -160,6 +163,7 @@ export class PtyRunner extends EventEmitter {
|
|
|
160
163
|
this._unlinkSafe(filePath);
|
|
161
164
|
for (const [workDir, session] of this.sessions) {
|
|
162
165
|
if (this._normalizePath(session.workDir) === this._normalizePath(cwd)) {
|
|
166
|
+
session._lastActivityTime = Date.now();
|
|
163
167
|
session._lastCompact = {
|
|
164
168
|
summary: marker.summary,
|
|
165
169
|
trigger: marker.trigger,
|
|
@@ -197,15 +201,28 @@ export class PtyRunner extends EventEmitter {
|
|
|
197
201
|
/**
|
|
198
202
|
* Wait for a marker file for the given pending ID.
|
|
199
203
|
*/
|
|
200
|
-
|
|
204
|
+
/**
|
|
205
|
+
* Wait for a marker file with inactivity-based timeout.
|
|
206
|
+
* Timer resets on any PTY output or hook signal activity.
|
|
207
|
+
*/
|
|
208
|
+
_waitForMarker (pendingId, inactivityMs, session) {
|
|
201
209
|
return new Promise((resolve, reject) => {
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
210
|
+
if (session) {
|
|
211
|
+
session._lastActivityTime = Date.now();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const CHECK_INTERVAL = 5000;
|
|
215
|
+
const checker = setInterval(() => {
|
|
216
|
+
const lastActivity = session?._lastActivityTime || 0;
|
|
217
|
+
if (lastActivity > 0 && Date.now() - lastActivity > inactivityMs) {
|
|
218
|
+
clearInterval(checker);
|
|
219
|
+
this.pendingMarkers.delete(pendingId);
|
|
220
|
+
reject(new Error('Marker timeout'));
|
|
221
|
+
}
|
|
222
|
+
}, CHECK_INTERVAL);
|
|
206
223
|
|
|
207
224
|
this.pendingMarkers.set(pendingId, (marker) => {
|
|
208
|
-
|
|
225
|
+
clearInterval(checker);
|
|
209
226
|
resolve(marker);
|
|
210
227
|
});
|
|
211
228
|
});
|
|
@@ -297,8 +314,8 @@ export class PtyRunner extends EventEmitter {
|
|
|
297
314
|
session._buffer = '';
|
|
298
315
|
this._openPtyLog(session, task);
|
|
299
316
|
|
|
300
|
-
// Set up marker wait + timeout
|
|
301
|
-
const markerPromise = this._waitForMarker(pendingId, this.timeout);
|
|
317
|
+
// Set up marker wait + inactivity timeout
|
|
318
|
+
const markerPromise = this._waitForMarker(pendingId, this.timeout, session);
|
|
302
319
|
|
|
303
320
|
// Send the task text to the PTY.
|
|
304
321
|
// Bracketed paste mode (\x1b[200~...\x1b[201~) causes Claude to hang in ConPTY,
|
|
@@ -435,6 +452,7 @@ export class PtyRunner extends EventEmitter {
|
|
|
435
452
|
|
|
436
453
|
ptyProcess.onData((data) => {
|
|
437
454
|
session._buffer += data;
|
|
455
|
+
session._lastActivityTime = Date.now();
|
|
438
456
|
// Keep buffer reasonable size
|
|
439
457
|
if (session._buffer.length > 50000) {
|
|
440
458
|
session._buffer = session._buffer.slice(-25000);
|
|
@@ -276,17 +276,34 @@ function splitMessage (text) {
|
|
|
276
276
|
|
|
277
277
|
// Strip ANSI escape codes and terminal control sequences from PTY output
|
|
278
278
|
function stripAnsi (text) {
|
|
279
|
-
|
|
279
|
+
let result = text
|
|
280
|
+
// Cursor-right (\x1b[<N>C) → replace with N spaces (preserves word spacing)
|
|
281
|
+
.replace(/\x1b\[(\d+)C/g, (_, n) => ' '.repeat(parseInt(n, 10)))
|
|
282
|
+
// Cursor-position (\x1b[<row>;<col>H) → replace with newline (absolute move = new line)
|
|
283
|
+
.replace(/\x1b\[\d+;\d+H/g, '\n')
|
|
280
284
|
// CSI sequences: \x1b[ followed by optional ?/>/! prefix, params, and terminator
|
|
281
285
|
.replace(/\x1b\[[?>=!]?[0-9;]*[a-zA-Z~]/g, '')
|
|
282
286
|
// OSC sequences: \x1b] ... (terminated by BEL or ST)
|
|
283
287
|
.replace(/\x1b][^\x07\x1b]*(?:\x07|\x1b\\)/g, '')
|
|
284
288
|
// Other two-char escape sequences (\x1b followed by any single char)
|
|
285
289
|
.replace(/\x1b[^[\]]/g, '')
|
|
286
|
-
//
|
|
287
|
-
.replace(/\r/g, '')
|
|
288
|
-
// Remaining control chars except newline and tab
|
|
290
|
+
// Remaining control chars except newline, tab, and CR
|
|
289
291
|
.replace(/[\x00-\x08\x0b\x0c\x0e-\x1f]/g, '');
|
|
292
|
+
|
|
293
|
+
// Normalize \r\n to \n first, then simulate standalone \r (line overwrite)
|
|
294
|
+
result = result.replace(/\r\n/g, '\n');
|
|
295
|
+
const lines = result.split('\n');
|
|
296
|
+
const resolved = [];
|
|
297
|
+
for (const line of lines) {
|
|
298
|
+
if (line.includes('\r')) {
|
|
299
|
+
// Standalone \r means overwrite — keep only the last segment
|
|
300
|
+
const parts = line.split('\r');
|
|
301
|
+
resolved.push(parts[parts.length - 1]);
|
|
302
|
+
} else {
|
|
303
|
+
resolved.push(line);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return resolved.join('\n');
|
|
290
307
|
}
|
|
291
308
|
|
|
292
309
|
// Clean PTY output for display: strip ANSI + remove Claude Code UI chrome
|
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.
|
|
4
|
+
"version": "1.1.57",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|