nothumanallowed 13.5.184 → 13.5.187
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
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "13.5.
|
|
3
|
+
"version": "13.5.187",
|
|
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/commands/ops.mjs
CHANGED
|
@@ -48,6 +48,27 @@ export async function cmdOps(args) {
|
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
+
case 'restart': {
|
|
52
|
+
const stopResult = stopDaemon();
|
|
53
|
+
if (stopResult.ok) ok(`Daemon stopped (PID ${stopResult.pid})`);
|
|
54
|
+
// Brief pause to let the process fully exit
|
|
55
|
+
await new Promise(r => setTimeout(r, 1200));
|
|
56
|
+
const startResult = startDaemon();
|
|
57
|
+
if (startResult.ok) {
|
|
58
|
+
ok(`Daemon restarted (PID ${startResult.pid})`);
|
|
59
|
+
const config = loadConfig();
|
|
60
|
+
const hasTelegram = !!config.responder?.telegram?.token;
|
|
61
|
+
const hasDiscord = !!config.responder?.discord?.token;
|
|
62
|
+
if (hasTelegram || hasDiscord) {
|
|
63
|
+
const platforms = [hasTelegram && 'Telegram', hasDiscord && 'Discord'].filter(Boolean).join(' + ');
|
|
64
|
+
info(`Message responder active: ${platforms}`);
|
|
65
|
+
}
|
|
66
|
+
} else {
|
|
67
|
+
warn(startResult.message);
|
|
68
|
+
}
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
51
72
|
case 'status': {
|
|
52
73
|
const status = getDaemonStatus();
|
|
53
74
|
const config = loadConfig();
|
|
@@ -109,6 +130,6 @@ export async function cmdOps(args) {
|
|
|
109
130
|
|
|
110
131
|
default:
|
|
111
132
|
fail(`Unknown: nha ops ${sub}`);
|
|
112
|
-
info('Commands: start, stop, status, logs, run');
|
|
133
|
+
info('Commands: start, stop, restart, status, logs, run');
|
|
113
134
|
}
|
|
114
135
|
}
|
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.187';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -134,6 +134,44 @@ function routeMessage(text, useAutoRoute = true) {
|
|
|
134
134
|
return bestAgent;
|
|
135
135
|
}
|
|
136
136
|
|
|
137
|
+
// ── Language detection from message text ─────────────────────────────────────
|
|
138
|
+
|
|
139
|
+
const IT_WORDS = new Set(['il','lo','la','le','gli','un','una','che','di','da','in','con','su','per','tra','fra','non','ma','se','come','dove','quando','chi','cosa','ho','hai','ha','sono','sei','siamo','avere','essere','fare','dire','andare','mi','ti','ci','si','vi','li','le','gli','mio','tuo','suo','nostro','vostro','loro','questo','quello','questi','quelli','anche','già','ancora','sempre','mai','oggi','domani','ieri','adesso','ora','poi','dopo','prima','qui','qua','lì','là','più','meno','molto','poco','bene','male','sì','no','grazie','prego','ciao','buongiorno','buonasera','appuntamenti','calendario','riunione','meteo','temperatura','email','posta','notizie']);
|
|
140
|
+
const ES_WORDS = new Set(['el','la','los','las','un','una','que','de','en','con','por','para','pero','como','donde','cuando','quien','qué','tengo','tienes','tiene','somos','soy','eres','hacer','decir','ir','me','te','se','nos','este','ese','estos','esos','también','ya','todavía','siempre','nunca','hoy','mañana','ayer','aquí','allí','más','menos','muy','bien','mal','sí','no','gracias','hola','buenos']);
|
|
141
|
+
const FR_WORDS = new Set(['le','la','les','un','une','des','que','de','en','avec','pour','par','mais','comme','où','quand','qui','je','tu','il','elle','nous','vous','ils','elles','avoir','être','faire','dire','aller','me','te','se','ce','cet','cette','ces','aussi','déjà','toujours','jamais','aujourd','demain','hier','ici','là','plus','moins','très','bien','mal','oui','non','merci','bonjour','bonsoir']);
|
|
142
|
+
const DE_WORDS = new Set(['der','die','das','ein','eine','und','oder','aber','nicht','mit','für','von','zu','an','auf','ist','sind','hat','haben','sein','werden','ich','du','er','sie','es','wir','ihr','mich','dich','sich','uns','euch','diesem','diesen','dieser','dieses','auch','schon','noch','immer','nie','heute','morgen','gestern','hier','dort','mehr','weniger','sehr','gut','schlecht','ja','nein','danke','hallo']);
|
|
143
|
+
const PT_WORDS = new Set(['o','a','os','as','um','uma','que','de','em','com','por','para','mas','como','onde','quando','quem','eu','tu','ele','ela','nós','vós','eles','elas','ter','ser','fazer','dizer','ir','me','te','se','nos','este','esse','isso','aquele','também','já','ainda','sempre','nunca','hoje','amanhã','ontem','aqui','lá','mais','menos','muito','bem','mal','sim','não','obrigado','olá']);
|
|
144
|
+
|
|
145
|
+
function detectLanguage(text) {
|
|
146
|
+
if (!text || text.length < 6) return null;
|
|
147
|
+
const words = text.toLowerCase().replace(/[^a-zàáâãäèéêëìíîïòóôõöùúûüýñçàèìòù\s]/g, ' ').split(/\s+/).filter(w => w.length > 1);
|
|
148
|
+
if (words.length < 2) return null;
|
|
149
|
+
|
|
150
|
+
let it = 0, es = 0, fr = 0, de = 0, pt = 0, en = 0;
|
|
151
|
+
for (const w of words) {
|
|
152
|
+
if (IT_WORDS.has(w)) it++;
|
|
153
|
+
if (ES_WORDS.has(w)) es++;
|
|
154
|
+
if (FR_WORDS.has(w)) fr++;
|
|
155
|
+
if (DE_WORDS.has(w)) de++;
|
|
156
|
+
if (PT_WORDS.has(w)) pt++;
|
|
157
|
+
// Basic English common words
|
|
158
|
+
if (['the','a','an','is','are','was','were','have','has','do','does','i','you','he','she','we','they','and','or','but','not','with','for','from','to','in','on','at','this','that','these','those','can','will','would','could','should','what','where','when','who','how'].includes(w)) en++;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
const max = Math.max(it, es, fr, de, pt, en);
|
|
162
|
+
if (max === 0) return null;
|
|
163
|
+
const threshold = Math.max(2, words.length * 0.15); // at least 15% of words or 2
|
|
164
|
+
if (max < threshold) return null;
|
|
165
|
+
|
|
166
|
+
if (it === max) return 'Italian';
|
|
167
|
+
if (es === max) return 'Spanish';
|
|
168
|
+
if (fr === max) return 'French';
|
|
169
|
+
if (de === max) return 'German';
|
|
170
|
+
if (pt === max) return 'Portuguese';
|
|
171
|
+
if (en === max) return 'English';
|
|
172
|
+
return null;
|
|
173
|
+
}
|
|
174
|
+
|
|
137
175
|
// ── Tool-aware agent call (LLM + tool execution loop) ────────────────────────
|
|
138
176
|
|
|
139
177
|
/**
|
|
@@ -141,12 +179,13 @@ function routeMessage(text, useAutoRoute = true) {
|
|
|
141
179
|
* Like chat.mjs but headless — no confirmation prompts, all tools auto-executed.
|
|
142
180
|
* Returns a human-readable summary of what was done.
|
|
143
181
|
*/
|
|
144
|
-
async function callAgentWithTools(config, agentName, userMessage) {
|
|
182
|
+
async function callAgentWithTools(config, agentName, userMessage, languageOverride) {
|
|
145
183
|
const today = new Date().toISOString().split('T')[0];
|
|
146
184
|
const tz = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
147
185
|
const locale = Intl.DateTimeFormat().resolvedOptions().locale || 'en';
|
|
148
186
|
const LANG_MAP = { en: 'English', it: 'Italian', es: 'Spanish', fr: 'French', de: 'German', pt: 'Portuguese', nl: 'Dutch', pl: 'Polish', ru: 'Russian', ja: 'Japanese', ko: 'Korean', zh: 'Chinese', ar: 'Arabic', hi: 'Hindi', tr: 'Turkish' };
|
|
149
|
-
|
|
187
|
+
// Priority: explicit override (from message text detection) → config setting → system locale
|
|
188
|
+
const language = languageOverride || config?.profile?.language || config?.language || LANG_MAP[locale.split('-')[0]] || 'English';
|
|
150
189
|
|
|
151
190
|
const systemPrompt = TOOL_DEFINITIONS
|
|
152
191
|
.replace('{{TODAY}}', today)
|
|
@@ -228,7 +267,7 @@ function touchTelegramUser(chatId, username, firstName) {
|
|
|
228
267
|
saveTelegramUsers(users);
|
|
229
268
|
}
|
|
230
269
|
|
|
231
|
-
function getAllTelegramChatIds() {
|
|
270
|
+
export function getAllTelegramChatIds() {
|
|
232
271
|
const users = loadTelegramUsers();
|
|
233
272
|
return Object.keys(users);
|
|
234
273
|
}
|
|
@@ -547,11 +586,20 @@ class TelegramResponder {
|
|
|
547
586
|
// Send typing indicator
|
|
548
587
|
await this._telegramCall('sendChatAction', { chat_id: chatId, action: 'typing' });
|
|
549
588
|
|
|
589
|
+
// Detect language from the message text — overrides system locale
|
|
590
|
+
const detectedLang = detectLanguage(cleanText);
|
|
591
|
+
|
|
550
592
|
// Tool-capable agents use the full tool execution loop
|
|
551
593
|
// Pure reasoning/analysis agents use the simple callAgent (no tools)
|
|
552
594
|
const TOOL_AGENTS = new Set(['herald', 'hermes', 'edi', 'jarvis', 'flux', 'echo', 'mercury', 'pipe', 'navi', 'link', 'prometheus', 'tempest']);
|
|
553
|
-
|
|
554
|
-
|
|
595
|
+
let response;
|
|
596
|
+
if (TOOL_AGENTS.has(agent)) {
|
|
597
|
+
response = await callAgentWithTools(this.config, agent, cleanText, detectedLang);
|
|
598
|
+
} else {
|
|
599
|
+
// For non-tool agents: inject language instruction into the message
|
|
600
|
+
const langInstruction = detectedLang ? `[Respond in ${detectedLang}] ` : '';
|
|
601
|
+
response = await callAgent(this.config, agent, langInstruction + cleanText);
|
|
602
|
+
}
|
|
555
603
|
|
|
556
604
|
// Truncate if too long for Telegram (4096 char limit)
|
|
557
605
|
const truncated = response.length > 4000
|
|
@@ -28,7 +28,8 @@ import { notify } from './notification.mjs';
|
|
|
28
28
|
import { callAgent } from './llm.mjs';
|
|
29
29
|
import { runPlanningPipeline } from './ops-pipeline.mjs';
|
|
30
30
|
import { getTasks, getWeekTasks } from './task-store.mjs';
|
|
31
|
-
import { startResponder, stopResponder, getResponderStatus } from './message-responder.mjs';
|
|
31
|
+
import { startResponder, stopResponder, getResponderStatus, getAllTelegramChatIds } from './message-responder.mjs';
|
|
32
|
+
import { VERSION } from '../constants.mjs';
|
|
32
33
|
|
|
33
34
|
const DAEMON_DIR = path.join(NHA_DIR, 'ops', 'daemon');
|
|
34
35
|
const PID_FILE = path.join(DAEMON_DIR, 'daemon.pid');
|
|
@@ -862,6 +863,71 @@ async function daemonLoop() {
|
|
|
862
863
|
if (responderResult.telegram) log('Message responder: Telegram active');
|
|
863
864
|
if (responderResult.discord) log('Message responder: Discord active');
|
|
864
865
|
|
|
866
|
+
// ── Auto-update check: every 24h, notify + restart if new npm version ──────
|
|
867
|
+
// First check after 5 minutes (let things settle), then every 24h.
|
|
868
|
+
// When a new version is found: notifies via Telegram, then does
|
|
869
|
+
// `npm install -g nothumanallowed@latest` and restarts itself.
|
|
870
|
+
let _lastNotifiedUpdateVersion = null;
|
|
871
|
+
async function _checkAndAutoUpdate() {
|
|
872
|
+
try {
|
|
873
|
+
const res = await fetch('https://registry.npmjs.org/nothumanallowed/latest', {
|
|
874
|
+
signal: AbortSignal.timeout(8000),
|
|
875
|
+
headers: { 'Accept': 'application/json' },
|
|
876
|
+
});
|
|
877
|
+
const data = await res.json();
|
|
878
|
+
const latest = data.version;
|
|
879
|
+
const current = VERSION;
|
|
880
|
+
|
|
881
|
+
// Compare semver
|
|
882
|
+
const pa = current.split('.').map(Number);
|
|
883
|
+
const pb = latest.split('.').map(Number);
|
|
884
|
+
let updateAvailable = false;
|
|
885
|
+
for (let i = 0; i < 3; i++) {
|
|
886
|
+
if ((pb[i] || 0) > (pa[i] || 0)) { updateAvailable = true; break; }
|
|
887
|
+
if ((pb[i] || 0) < (pa[i] || 0)) break;
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
if (!updateAvailable) return;
|
|
891
|
+
if (_lastNotifiedUpdateVersion === latest) return;
|
|
892
|
+
_lastNotifiedUpdateVersion = latest;
|
|
893
|
+
|
|
894
|
+
log(`[AutoUpdate] New version ${latest} available (current: ${current}). Updating...`);
|
|
895
|
+
|
|
896
|
+
// Notify via Telegram before restarting
|
|
897
|
+
const tgToken = config.responder?.telegram?.token;
|
|
898
|
+
if (tgToken) {
|
|
899
|
+
const chatIds = getAllTelegramChatIds();
|
|
900
|
+
const msg = `NHA v${latest} disponibile — aggiornamento automatico in corso...\nIl bot si riavvierà tra pochi secondi.`;
|
|
901
|
+
for (const chatId of chatIds) {
|
|
902
|
+
await fetch(`https://api.telegram.org/bot${tgToken}/sendMessage`, {
|
|
903
|
+
method: 'POST',
|
|
904
|
+
headers: { 'Content-Type': 'application/json' },
|
|
905
|
+
body: JSON.stringify({ chat_id: parseInt(chatId, 10), text: msg }),
|
|
906
|
+
}).catch(() => {});
|
|
907
|
+
await new Promise(r => setTimeout(r, 300));
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
// npm install -g then restart
|
|
912
|
+
await new Promise((resolve) => {
|
|
913
|
+
const proc = spawn('npm', ['install', '-g', `nothumanallowed@${latest}`], { stdio: 'inherit' });
|
|
914
|
+
proc.on('close', resolve);
|
|
915
|
+
});
|
|
916
|
+
|
|
917
|
+
log('[AutoUpdate] npm install complete. Restarting daemon...');
|
|
918
|
+
// Exit — the parent process (startDaemon) will restart us, or the user will
|
|
919
|
+
// The daemon is spawned detached so we just exit and the next `nha ops start` picks it up
|
|
920
|
+
process.exit(0);
|
|
921
|
+
} catch (err) {
|
|
922
|
+
log(`[AutoUpdate] Check failed: ${err.message}`);
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
setTimeout(() => {
|
|
927
|
+
_checkAndAutoUpdate();
|
|
928
|
+
setInterval(_checkAndAutoUpdate, 24 * 60 * 60 * 1000);
|
|
929
|
+
}, 5 * 60 * 1000);
|
|
930
|
+
|
|
865
931
|
const state = {
|
|
866
932
|
startedAt: new Date().toISOString(),
|
|
867
933
|
lastMailCheck: null,
|