nothumanallowed 13.5.182 → 13.5.184
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/package.json +1 -1
- package/src/constants.mjs +1 -1
- package/src/services/message-responder.mjs +146 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.184",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 80 tools, Studio (visual agentic workflows). Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, Alexandria E2E messaging, GitHub, Notion, Slack, voice chat, free AI (Liara), 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/constants.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { fileURLToPath } from 'url';
|
|
|
5
5
|
const __filename = fileURLToPath(import.meta.url);
|
|
6
6
|
const __dirname = path.dirname(__filename);
|
|
7
7
|
|
|
8
|
-
export const VERSION = '13.5.
|
|
8
|
+
export const VERSION = '13.5.184';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -13,6 +13,10 @@ import { buildSystemPrompt, parseActions, executeTool, TOOL_DEFINITIONS } from '
|
|
|
13
13
|
import https from 'https';
|
|
14
14
|
import http from 'http';
|
|
15
15
|
import { URL } from 'url';
|
|
16
|
+
import fs from 'fs';
|
|
17
|
+
import path from 'path';
|
|
18
|
+
import os from 'os';
|
|
19
|
+
import { VERSION } from '../constants.mjs';
|
|
16
20
|
|
|
17
21
|
// ── Agent Routing (keyword-based, zero LLM calls) ───────────────────────────
|
|
18
22
|
|
|
@@ -190,6 +194,71 @@ async function callAgentWithTools(config, agentName, userMessage) {
|
|
|
190
194
|
|
|
191
195
|
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
192
196
|
|
|
197
|
+
// ── User store for Telegram chat IDs (for broadcast notifications) ──────────
|
|
198
|
+
|
|
199
|
+
const TELEGRAM_USERS_FILE = path.join(os.homedir(), '.nha', 'telegram-users.json');
|
|
200
|
+
|
|
201
|
+
function loadTelegramUsers() {
|
|
202
|
+
try {
|
|
203
|
+
const raw = fs.readFileSync(TELEGRAM_USERS_FILE, 'utf8');
|
|
204
|
+
return JSON.parse(raw);
|
|
205
|
+
} catch {
|
|
206
|
+
return {};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function saveTelegramUsers(users) {
|
|
211
|
+
try {
|
|
212
|
+
fs.mkdirSync(path.dirname(TELEGRAM_USERS_FILE), { recursive: true });
|
|
213
|
+
fs.writeFileSync(TELEGRAM_USERS_FILE, JSON.stringify(users, null, 2));
|
|
214
|
+
} catch {}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function touchTelegramUser(chatId, username, firstName) {
|
|
218
|
+
const users = loadTelegramUsers();
|
|
219
|
+
const id = String(chatId);
|
|
220
|
+
const now = new Date().toISOString();
|
|
221
|
+
users[id] = {
|
|
222
|
+
chatId: id,
|
|
223
|
+
username: username || null,
|
|
224
|
+
firstName: firstName || null,
|
|
225
|
+
firstSeen: users[id]?.firstSeen || now,
|
|
226
|
+
lastSeen: now,
|
|
227
|
+
};
|
|
228
|
+
saveTelegramUsers(users);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function getAllTelegramChatIds() {
|
|
232
|
+
const users = loadTelegramUsers();
|
|
233
|
+
return Object.keys(users);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// ── npm update check ─────────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
function compareSemver(a, b) {
|
|
239
|
+
const pa = a.split('.').map(Number);
|
|
240
|
+
const pb = b.split('.').map(Number);
|
|
241
|
+
for (let i = 0; i < 3; i++) {
|
|
242
|
+
if ((pa[i] || 0) > (pb[i] || 0)) return 1;
|
|
243
|
+
if ((pa[i] || 0) < (pb[i] || 0)) return -1;
|
|
244
|
+
}
|
|
245
|
+
return 0;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
async function checkNpmVersion() {
|
|
249
|
+
const res = await fetch('https://registry.npmjs.org/nothumanallowed/latest', {
|
|
250
|
+
signal: AbortSignal.timeout(8000),
|
|
251
|
+
headers: { 'Accept': 'application/json' },
|
|
252
|
+
});
|
|
253
|
+
const data = await res.json();
|
|
254
|
+
const latest = data.version;
|
|
255
|
+
const current = VERSION;
|
|
256
|
+
const updateAvailable = compareSemver(latest, current) > 0;
|
|
257
|
+
return { current, latest, updateAvailable };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
261
|
+
|
|
193
262
|
class TelegramResponder {
|
|
194
263
|
constructor(config, log, wsBroadcast) {
|
|
195
264
|
this.config = config;
|
|
@@ -203,6 +272,8 @@ class TelegramResponder {
|
|
|
203
272
|
this.abortController = null;
|
|
204
273
|
this.pendingRequests = 0;
|
|
205
274
|
this.maxConcurrent = 3;
|
|
275
|
+
this._updateCheckTimer = null;
|
|
276
|
+
this._lastNotifiedVersion = null;
|
|
206
277
|
}
|
|
207
278
|
|
|
208
279
|
get enabled() {
|
|
@@ -214,6 +285,8 @@ class TelegramResponder {
|
|
|
214
285
|
this.running = true;
|
|
215
286
|
this.log('[Telegram] Responder started — polling for messages');
|
|
216
287
|
this._pollLoop();
|
|
288
|
+
// Check for npm updates after 60s, then every 24h
|
|
289
|
+
this._updateCheckTimer = setTimeout(() => this._scheduleUpdateCheck(), 60 * 1000);
|
|
217
290
|
}
|
|
218
291
|
|
|
219
292
|
stop() {
|
|
@@ -222,9 +295,57 @@ class TelegramResponder {
|
|
|
222
295
|
this.abortController.abort();
|
|
223
296
|
this.abortController = null;
|
|
224
297
|
}
|
|
298
|
+
if (this._updateCheckTimer) {
|
|
299
|
+
clearTimeout(this._updateCheckTimer);
|
|
300
|
+
clearInterval(this._updateCheckTimer);
|
|
301
|
+
this._updateCheckTimer = null;
|
|
302
|
+
}
|
|
225
303
|
this.log('[Telegram] Responder stopped');
|
|
226
304
|
}
|
|
227
305
|
|
|
306
|
+
async _scheduleUpdateCheck() {
|
|
307
|
+
await this._checkAndNotifyUpdate();
|
|
308
|
+
// Then every 24h
|
|
309
|
+
this._updateCheckTimer = setInterval(() => this._checkAndNotifyUpdate(), 24 * 60 * 60 * 1000);
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
async _checkAndNotifyUpdate() {
|
|
313
|
+
try {
|
|
314
|
+
const { latest, updateAvailable } = await checkNpmVersion();
|
|
315
|
+
if (!updateAvailable) return;
|
|
316
|
+
if (this._lastNotifiedVersion === latest) return; // Already notified for this version
|
|
317
|
+
|
|
318
|
+
this._lastNotifiedVersion = latest;
|
|
319
|
+
const chatIds = getAllTelegramChatIds();
|
|
320
|
+
if (chatIds.length === 0) return;
|
|
321
|
+
|
|
322
|
+
const msg =
|
|
323
|
+
`🆕 *NHA v${latest} disponibile!*\n\n` +
|
|
324
|
+
`Una nuova versione di NotHumanAllowed è stata pubblicata.\n\n` +
|
|
325
|
+
`Aggiorna con:\n` +
|
|
326
|
+
`\`npm install -g nothumanallowed@latest\`\n\n` +
|
|
327
|
+
`Poi riavvia il bot con: \`nha responder restart\``;
|
|
328
|
+
|
|
329
|
+
this.log(`[Telegram] Broadcasting update notification v${latest} to ${chatIds.length} users`);
|
|
330
|
+
|
|
331
|
+
for (const chatId of chatIds) {
|
|
332
|
+
try {
|
|
333
|
+
await this._telegramCall('sendMessage', {
|
|
334
|
+
chat_id: parseInt(chatId, 10),
|
|
335
|
+
text: msg,
|
|
336
|
+
parse_mode: 'Markdown',
|
|
337
|
+
});
|
|
338
|
+
} catch {
|
|
339
|
+
// User blocked bot or chat no longer exists — ignore
|
|
340
|
+
}
|
|
341
|
+
// Small delay to avoid Telegram rate limits
|
|
342
|
+
await this._sleep(300);
|
|
343
|
+
}
|
|
344
|
+
} catch (err) {
|
|
345
|
+
this.log(`[Telegram] Update check failed: ${err.message}`);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
228
349
|
async _pollLoop() {
|
|
229
350
|
while (this.running) {
|
|
230
351
|
try {
|
|
@@ -284,10 +405,29 @@ class TelegramResponder {
|
|
|
284
405
|
if (!audioRes.ok) throw new Error(`Download failed: ${audioRes.status}`);
|
|
285
406
|
const audioBuffer = Buffer.from(await audioRes.arrayBuffer());
|
|
286
407
|
|
|
287
|
-
// Step 3: transcribe —
|
|
408
|
+
// Step 3: transcribe — priority: NHA proxy (no user key needed) → Groq local key → OpenAI local key
|
|
288
409
|
const groqKey = this.config.llm?.groqKey;
|
|
289
410
|
const openaiKey = this.config.llm?.openaiKey || (this.config.llm?.provider === 'openai' ? this.config.llm?.apiKey : null);
|
|
290
411
|
|
|
412
|
+
// Option A: NHA voice proxy (server-side Groq key, free for all users)
|
|
413
|
+
try {
|
|
414
|
+
const proxyForm = new FormData();
|
|
415
|
+
proxyForm.append('audio', new Blob([audioBuffer], { type: 'audio/ogg' }), 'voice.ogg');
|
|
416
|
+
const proxyRes = await fetch('https://nothumanallowed.com/api/v1/voice/transcribe', {
|
|
417
|
+
method: 'POST',
|
|
418
|
+
body: proxyForm,
|
|
419
|
+
signal: AbortSignal.timeout(30000),
|
|
420
|
+
});
|
|
421
|
+
if (proxyRes.ok) {
|
|
422
|
+
const d = await proxyRes.json();
|
|
423
|
+
if (d.text) return d.text;
|
|
424
|
+
}
|
|
425
|
+
// If proxy returned rate limit or error, fall through to local keys
|
|
426
|
+
} catch {
|
|
427
|
+
// Network error — fall through to local keys
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Option B: local Groq key
|
|
291
431
|
const boundary = '----NHAVoice' + Date.now().toString(36);
|
|
292
432
|
const crlf = '\r\n';
|
|
293
433
|
const filename = 'voice.ogg';
|
|
@@ -314,6 +454,7 @@ class TelegramResponder {
|
|
|
314
454
|
return d.text || '';
|
|
315
455
|
}
|
|
316
456
|
|
|
457
|
+
// Option C: local OpenAI key
|
|
317
458
|
if (openaiKey) {
|
|
318
459
|
const modelPartOAI = Buffer.from(
|
|
319
460
|
`${crlf}--${boundary}${crlf}` +
|
|
@@ -331,7 +472,7 @@ class TelegramResponder {
|
|
|
331
472
|
return d.text || '';
|
|
332
473
|
}
|
|
333
474
|
|
|
334
|
-
throw new Error('
|
|
475
|
+
throw new Error('Voice transcription unavailable. The NHA proxy is temporarily unreachable.');
|
|
335
476
|
}
|
|
336
477
|
|
|
337
478
|
async _handleMessage(message) {
|
|
@@ -343,6 +484,9 @@ class TelegramResponder {
|
|
|
343
484
|
return;
|
|
344
485
|
}
|
|
345
486
|
|
|
487
|
+
// Track this user for broadcast notifications (update alerts, etc.)
|
|
488
|
+
touchTelegramUser(chatId, message.from?.username, message.from?.first_name);
|
|
489
|
+
|
|
346
490
|
let rawText = message.text || '';
|
|
347
491
|
let isVoice = false;
|
|
348
492
|
|