claude-notification-plugin 1.1.56 → 1.1.58
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/commit-sha +1 -1
- package/listener/listener.js +1 -1
- package/listener/pty-runner.js +26 -8
- package/notifier/notifier.js +15 -15
- 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.58",
|
|
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/commit-sha
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
62dff0d5bb6a43cead78d21f435af9110212e139
|
package/listener/listener.js
CHANGED
|
@@ -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) {
|
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);
|
package/notifier/notifier.js
CHANGED
|
@@ -624,28 +624,28 @@ function numberToWords (n, lang) {
|
|
|
624
624
|
}
|
|
625
625
|
|
|
626
626
|
const voicePhrases = {
|
|
627
|
-
en: (d) => `Claude finished
|
|
628
|
-
ru: (d) => `Клод завершил работу за ${numberToWords(d, 'ru')} ${pluralize(d, ['секунду', 'секунды', 'секунд'])}`,
|
|
629
|
-
de: (d) => `Claude hat die Arbeit in ${d} ${pluralize(d, ['Sekunde', 'Sekunden'])} abgeschlossen`,
|
|
630
|
-
fr: (d) => `Claude a termine en ${d} ${pluralize(d, ['seconde', 'secondes'])}`,
|
|
631
|
-
es: (d) => `Claude termino en ${d} ${pluralize(d, ['segundo', 'segundos'])}`,
|
|
632
|
-
pt: (d) => `Claude terminou em ${d} ${pluralize(d, ['segundo', 'segundos'])}`,
|
|
633
|
-
ja: (d) => `Claudeは${d}
|
|
634
|
-
ko: (d) => `Claude가 ${d}초 만에
|
|
627
|
+
en: (d, p) => `Claude finished working on ${p} in ${numberToWords(d, 'en')} ${pluralize(d, ['second', 'seconds'])}`,
|
|
628
|
+
ru: (d, p) => `Клод завершил работу над проектом ${p} за ${numberToWords(d, 'ru')} ${pluralize(d, ['секунду', 'секунды', 'секунд'])}`,
|
|
629
|
+
de: (d, p) => `Claude hat die Arbeit an ${p} in ${d} ${pluralize(d, ['Sekunde', 'Sekunden'])} abgeschlossen`,
|
|
630
|
+
fr: (d, p) => `Claude a termine ${p} en ${d} ${pluralize(d, ['seconde', 'secondes'])}`,
|
|
631
|
+
es: (d, p) => `Claude termino ${p} en ${d} ${pluralize(d, ['segundo', 'segundos'])}`,
|
|
632
|
+
pt: (d, p) => `Claude terminou ${p} em ${d} ${pluralize(d, ['segundo', 'segundos'])}`,
|
|
633
|
+
ja: (d, p) => `Claudeは${p}の作業を${d}秒で完了しました`,
|
|
634
|
+
ko: (d, p) => `Claude가 ${p} 작업을 ${d}초 만에 완료했습니다`,
|
|
635
635
|
};
|
|
636
636
|
|
|
637
|
-
function getVoicePhrase (duration) {
|
|
637
|
+
function getVoicePhrase (duration, project) {
|
|
638
638
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en';
|
|
639
639
|
const lang = locale.split('-')[0].toLowerCase();
|
|
640
640
|
const fn = voicePhrases[lang] || voicePhrases.en;
|
|
641
|
-
return fn(duration);
|
|
641
|
+
return fn(duration, project || 'unknown');
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
-
function speakResult (config, duration) {
|
|
644
|
+
function speakResult (config, duration, project) {
|
|
645
645
|
if (!config.voice.enabled) {
|
|
646
646
|
return;
|
|
647
647
|
}
|
|
648
|
-
const text = getVoicePhrase(duration);
|
|
648
|
+
const text = getVoicePhrase(duration, project);
|
|
649
649
|
try {
|
|
650
650
|
switch (PLATFORM) {
|
|
651
651
|
case 'win32': {
|
|
@@ -808,7 +808,7 @@ process.stdin.on('end', async () => {
|
|
|
808
808
|
|
|
809
809
|
if (config.debug) {
|
|
810
810
|
const debugBlockHtml = '\n\n<b>Debug:</b>\n'
|
|
811
|
-
+ (config.voice.enabled ? `\nVoice: ${escapeHtml(getVoicePhrase(duration))}` : '')
|
|
811
|
+
+ (config.voice.enabled ? `\nVoice: ${escapeHtml(getVoicePhrase(duration, project))}` : '')
|
|
812
812
|
+ `\n\nHook input:\n<pre>${escapeHtml(JSON.stringify(event, null, 2))}</pre>`;
|
|
813
813
|
telegramMessage += debugBlockHtml;
|
|
814
814
|
}
|
|
@@ -819,7 +819,7 @@ process.stdin.on('end', async () => {
|
|
|
819
819
|
branch: branch || undefined,
|
|
820
820
|
duration,
|
|
821
821
|
trigger: eventType,
|
|
822
|
-
voicePhrase: config.voice.enabled ? getVoicePhrase(duration) : null,
|
|
822
|
+
voicePhrase: config.voice.enabled ? getVoicePhrase(duration, project) : null,
|
|
823
823
|
hookEvent: event,
|
|
824
824
|
});
|
|
825
825
|
|
|
@@ -833,5 +833,5 @@ process.stdin.on('end', async () => {
|
|
|
833
833
|
|
|
834
834
|
await sendDesktopNotification(config, desktopTitle, desktopMessage);
|
|
835
835
|
playSound(config);
|
|
836
|
-
speakResult(config, duration);
|
|
836
|
+
speakResult(config, duration, project);
|
|
837
837
|
});
|
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.58",
|
|
5
5
|
"description": "Claude Code task-completion notifications: Telegram, desktop notifications (Windows/macOS/Linux), sound, and voice",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|