nothumanallowed 15.1.13 → 15.1.16
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/cli.mjs +3 -1
- package/src/constants.mjs +1 -1
- package/src/server/routes/studio.mjs +39 -7
- package/src/services/message-responder.mjs +42 -3
- package/src/services/tool-executor.mjs +14 -8
- package/src/ui-dist/assets/{index-D1_FWACq.js → index-e_9WzUAL.js} +51 -51
- package/src/ui-dist/index.html +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "15.1.
|
|
3
|
+
"version": "15.1.16",
|
|
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/cli.mjs
CHANGED
|
@@ -55,12 +55,14 @@ export async function main(argv) {
|
|
|
55
55
|
// npm version check (non-blocking). The one-liner uses --prefer-online to
|
|
56
56
|
// bypass npm's metadata cache, which is the #1 reason `npm install -g`
|
|
57
57
|
// appears to "do nothing" — it had stale "latest" in the local cache.
|
|
58
|
+
// Every command separated by `&&` so users can copy-paste the whole line.
|
|
58
59
|
checkNpmVersion().then(result => {
|
|
59
60
|
if (result?.updateAvailable) {
|
|
60
61
|
console.log('');
|
|
61
62
|
warn(`New NHA version available: ${result.current} → ${result.latest}`);
|
|
62
63
|
info(`Run "nha update" (recommended — auto-installs npm + agents)`);
|
|
63
|
-
info(`Or
|
|
64
|
+
info(`Or copy-paste this ENTIRE line (note: --prefer-online, not --pref-online):`);
|
|
65
|
+
info(` npm cache clean --force && npm install -g nothumanallowed@${result.latest} --prefer-online && hash -r && nha version`);
|
|
64
66
|
}
|
|
65
67
|
}).catch(() => {});
|
|
66
68
|
}
|
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 = '15.1.
|
|
8
|
+
export const VERSION = '15.1.16';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -759,16 +759,35 @@ ${okBlock}`;
|
|
|
759
759
|
|
|
760
760
|
tok('[Parlamento — Round 2: Cross-Reading & Refinamento] ');
|
|
761
761
|
const r2Results = [];
|
|
762
|
+
// Per-agent timeout — if any single agent stalls (network, LLM hung)
|
|
763
|
+
// the loop must not block the whole deliberation. After this budget,
|
|
764
|
+
// fall back to the agent's Round 1 output and continue.
|
|
765
|
+
const PER_AGENT_TIMEOUT_MS = 90_000;
|
|
762
766
|
for (const proposal of eligible) {
|
|
763
767
|
tok(`[Round 2: ${proposal.label || proposal.agent}] `);
|
|
764
768
|
const r2Sys = `You are ${proposal.agent}, a specialist AI agent in NHA Studio Parliament. Today is ${today}. Respond entirely in ${language}.\n\n## WORKFLOW GOAL: ${task}\n\n## YOUR ROUND 1 RESPONSE:\n${proposal.output.slice(0,3000)}\n\n## OTHER AGENTS' ROUND 1 PROPOSALS:\n${crossCtx(proposal.agent)}\n\nDELIBERATION ROUND 2 — REFINEMENT:\n1. Review the other agents' proposals carefully\n2. Incorporate valid points where you AGREE — mark with [AGREE]\n3. Flag genuine disagreements with [CONTRADICTION] and explain your reasoning with evidence\n4. Produce your COMPLETE REFINED response — thorough and exhaustive\n5. Keep analysis focused on: ${task}\n\nBe THOROUGH. Minimum 600 words of substantive refined analysis.`;
|
|
765
769
|
let r2Out = '';
|
|
770
|
+
let fellBack = false;
|
|
766
771
|
try {
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
+
// Race the LLM stream against a hard timeout.
|
|
773
|
+
await Promise.race([
|
|
774
|
+
callLLMStream(config, r2Sys, 'Produce your refined Round 2 response. Write complete content under every heading — never leave a section title without body text.',
|
|
775
|
+
(t) => { r2Out += t; }, { max_tokens: 16384 }),
|
|
776
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('R2_AGENT_TIMEOUT')), PER_AGENT_TIMEOUT_MS)),
|
|
777
|
+
]);
|
|
778
|
+
} catch (err) {
|
|
779
|
+
// Fallback: use Round 1 output so the rest of deliberation can proceed.
|
|
780
|
+
fellBack = true;
|
|
781
|
+
r2Out = proposal.output;
|
|
782
|
+
tok(`[Round 2 ${proposal.label || proposal.agent}: timeout — uso Round 1 come fallback] `);
|
|
783
|
+
}
|
|
784
|
+
// If the stream returned but produced no text (LLM hiccup), also fall back.
|
|
785
|
+
if (!r2Out || r2Out.trim().length < 50) {
|
|
786
|
+
fellBack = true;
|
|
787
|
+
r2Out = proposal.output;
|
|
788
|
+
}
|
|
789
|
+
r2Results.push({ agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out, fellBack });
|
|
790
|
+
sse({ deliberation_r2: { agent: proposal.agent, label: proposal.label, icon: proposal.icon, output: r2Out, fellBack } });
|
|
772
791
|
}
|
|
773
792
|
|
|
774
793
|
const r2Conv = measureConvergence(r2Results.map(r => r.output));
|
|
@@ -792,9 +811,22 @@ ${okBlock}`;
|
|
|
792
811
|
const medSys = `You are HERALD, the Parliament Mediator in NHA Studio. Today is ${today}. Respond entirely in ${language}.\n\n## WORKFLOW GOAL: ${task}\n\n## ALL AGENTS' REFINED POSITIONS (Round 2):\n${allR2Ctx}${contBlock}\n\n${medTask}\n\nCRITICAL: NEVER write a heading without immediately writing full content below it. Every section MUST have at least 5-8 concrete bullet points or detailed paragraphs. Be EXHAUSTIVE.`;
|
|
793
812
|
|
|
794
813
|
let mediationOutput = '';
|
|
814
|
+
const HERALD_TIMEOUT_MS = 120_000;
|
|
795
815
|
try {
|
|
796
|
-
await
|
|
797
|
-
|
|
816
|
+
await Promise.race([
|
|
817
|
+
callLLMStream(config, medSys, 'Produce the Parliament final synthesis. Be thorough and complete.', (t) => { mediationOutput += t; }, { max_tokens: 16384 }),
|
|
818
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error('HERALD_TIMEOUT')), HERALD_TIMEOUT_MS)),
|
|
819
|
+
]);
|
|
820
|
+
} catch (err) {
|
|
821
|
+
// HERALD timed out — emit whatever we got so the client isn't stuck.
|
|
822
|
+
tok(`[HERALD mediation: timeout dopo ${HERALD_TIMEOUT_MS/1000}s — restituisco contenuto parziale] `);
|
|
823
|
+
}
|
|
824
|
+
// If absolutely nothing came back, fall back to a concatenation of the
|
|
825
|
+
// r2 outputs so the user has SOMETHING to read instead of a blank panel.
|
|
826
|
+
if (!mediationOutput || mediationOutput.trim().length < 50) {
|
|
827
|
+
mediationOutput = `# Sintesi automatica (HERALD non disponibile)\n\n` +
|
|
828
|
+
r2Results.map(r => `## ${r.label || r.agent}\n${r.output.slice(0, 2000)}`).join('\n\n---\n\n');
|
|
829
|
+
}
|
|
798
830
|
sse({ deliberation_r3: { output: mediationOutput, converged } });
|
|
799
831
|
|
|
800
832
|
clearInterval(keepalive);
|
|
@@ -320,12 +320,50 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
320
320
|
}
|
|
321
321
|
|
|
322
322
|
history.push({ role: 'assistant', content: response });
|
|
323
|
-
|
|
323
|
+
// LANGUAGE enforcement up FRONT — putting it last allows Liara to ignore it.
|
|
324
|
+
// Repeat the instruction at the top so the model commits to it before reading
|
|
325
|
+
// the tool results, then again at the bottom for reinforcement.
|
|
326
|
+
userMessage =
|
|
327
|
+
`RISPOSTA OBBLIGATORIA IN ${language.toUpperCase()}. Tutta la frase deve essere in ${language}, niente inglese, niente lingue miste.\n\n` +
|
|
328
|
+
`Tool results:\n${toolResults.join('\n')}\n\n` +
|
|
329
|
+
`Now give the user a short, clear confirmation. Be direct — no preamble, no HERALD format. ` +
|
|
330
|
+
`If an action was completed, say so clearly. REMEMBER: reply ONLY in ${language}.`;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
// Defensive language post-check: small models sometimes drop back to English
|
|
334
|
+
// even when instructed otherwise (especially after tool execution, where the
|
|
335
|
+
// English tool-result text biases the continuation). If the final reply is
|
|
336
|
+
// in a clearly different language than expected, translate it.
|
|
337
|
+
if (finalText && language && !looksLikeLanguage(finalText, language)) {
|
|
338
|
+
try {
|
|
339
|
+
const translatePrompt =
|
|
340
|
+
`Traduci il seguente messaggio in ${language}. Mantieni lo stesso significato, lo stesso tono, la stessa lunghezza. Restituisci SOLO la traduzione, senza preamboli o note.\n\nMessaggio:\n${finalText}`;
|
|
341
|
+
const translated = await callLLM(config,
|
|
342
|
+
`You are a precise translator. Translate the given text into ${language}. Output ONLY the translation, no commentary.`,
|
|
343
|
+
translatePrompt,
|
|
344
|
+
{ max_tokens: 800 },
|
|
345
|
+
);
|
|
346
|
+
const cleaned = (translated || '').trim();
|
|
347
|
+
if (cleaned && cleaned.length > 0) finalText = cleaned;
|
|
348
|
+
} catch { /* keep the original if translation fails */ }
|
|
324
349
|
}
|
|
325
350
|
|
|
326
351
|
return { text: finalText || 'Fatto.', history };
|
|
327
352
|
}
|
|
328
353
|
|
|
354
|
+
/**
|
|
355
|
+
* Detect if a text is in the expected language. Used as a defensive check
|
|
356
|
+
* after the LLM produces the final tool-confirmation reply — small models
|
|
357
|
+
* sometimes drop back to English even when instructed otherwise.
|
|
358
|
+
* Returns true if the language seems right, false if a strong mismatch.
|
|
359
|
+
*/
|
|
360
|
+
function looksLikeLanguage(text, expectedLanguage) {
|
|
361
|
+
if (!text || !expectedLanguage) return true;
|
|
362
|
+
const detected = detectLanguage(text);
|
|
363
|
+
if (!detected) return true; // not enough signal, give benefit of the doubt
|
|
364
|
+
return detected === expectedLanguage;
|
|
365
|
+
}
|
|
366
|
+
|
|
329
367
|
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
330
368
|
|
|
331
369
|
// ── User store for Telegram chat IDs (for broadcast notifications) ──────────
|
|
@@ -509,8 +547,9 @@ class TelegramResponder {
|
|
|
509
547
|
const msg =
|
|
510
548
|
`🆕 NHA v${latest} disponibile!\n\n` +
|
|
511
549
|
`Una nuova versione di NotHumanAllowed è stata pubblicata.\n\n` +
|
|
512
|
-
`Aggiorna con
|
|
513
|
-
`
|
|
550
|
+
`Aggiorna con UN SOLO comando (copia e incolla tutto):\n` +
|
|
551
|
+
`npm cache clean --force && npm install -g nothumanallowed@latest --prefer-online && nha ops stop && nha ops start\n\n` +
|
|
552
|
+
`Importante: usa esattamente "--prefer-online" (NON "--pref-online") e tieni tutti i "&&" tra i comandi.`;
|
|
514
553
|
|
|
515
554
|
this.log(`[Telegram] Broadcasting update notification v${latest} to ${chatIds.length} users`);
|
|
516
555
|
|
|
@@ -165,12 +165,11 @@ TOOLS:
|
|
|
165
165
|
Use this when the user says: "inserisci", "aggiungi", "crea", "metti", "fissa", "prenota", "add", "create", "schedule", "book".
|
|
166
166
|
|
|
167
167
|
PARAMETER MEANING (CRITICAL — most common mistake):
|
|
168
|
-
- summary = the TITLE of the event (what you'd write on a calendar grid). Short,
|
|
168
|
+
- summary = the TITLE of the event (what you'd write on a calendar grid). Short, derived from what the user said.
|
|
169
169
|
- description = optional NOTES/details. Leave empty unless the user gave extra context that doesn't fit in the title.
|
|
170
170
|
- NEVER put the title in description. NEVER leave summary empty.
|
|
171
171
|
|
|
172
|
-
|
|
173
|
-
{ "action": "calendar_create", "params": { "summary": "Tagliando BMW", "start": "2026-05-15T17:00:00", "end": "2026-05-15T18:00:00" } }
|
|
172
|
+
Always derive summary, date and time from the actual user message — never use literal values from this documentation.
|
|
174
173
|
|
|
175
174
|
IMPORTANT: When user says "inserisci appuntamento" or "crea evento" → use calendar_create, NOT calendar_find.
|
|
176
175
|
Extract the summary, date, and time from the user message. If end time is not specified, default to 1 hour after start.
|
|
@@ -667,7 +666,15 @@ RULES:
|
|
|
667
666
|
// Used ONLY when provider === 'nha'. Liara already knows tool signatures from
|
|
668
667
|
// LoRA training — no verbose descriptions needed. Dynamic values (today, tz,
|
|
669
668
|
// language, profile, imap accounts) are still injected at runtime below.
|
|
670
|
-
export const LIARA_TOOL_DEFINITIONS =
|
|
669
|
+
export const LIARA_TOOL_DEFINITIONS = `## LANGUAGE — HIGHEST PRIORITY
|
|
670
|
+
|
|
671
|
+
You MUST write your ENTIRE response in {{LANGUAGE}}. Every sentence, every confirmation, every error message. This overrides everything else. Even when this prompt itself is written in English, your reply to the user must be in {{LANGUAGE}} only. Do NOT mix languages. Do NOT default to English.
|
|
672
|
+
|
|
673
|
+
If {{LANGUAGE}} is Italian, write only in Italian: "L'appuntamento è stato cancellato." NOT "The event has been deleted." or "Both events have been deleted."
|
|
674
|
+
|
|
675
|
+
---
|
|
676
|
+
|
|
677
|
+
You are Liara, the NHA personal AI assistant.
|
|
671
678
|
Today: {{TODAY}} | Timezone: {{TIMEZONE}} | Language: {{LANGUAGE}}
|
|
672
679
|
|
|
673
680
|
When the user's request requires an action, output one or more fenced JSON blocks:
|
|
@@ -688,12 +695,11 @@ Never output a JSON block as a suggestion — every block executes immediately.
|
|
|
688
695
|
|
|
689
696
|
### Calendar
|
|
690
697
|
calendar_create(summary, start, end, description?, attendees?, location?)
|
|
691
|
-
- summary = SHORT title
|
|
698
|
+
- summary = SHORT title derived from what the user actually said. REQUIRED. Do NOT leave empty.
|
|
692
699
|
- description = optional notes only. Do NOT put the title here.
|
|
693
700
|
- start/end = ISO 8601 datetimes. Default end = start + 1 hour.
|
|
694
|
-
- The tool response includes the REAL eventId
|
|
695
|
-
|
|
696
|
-
{"action":"calendar_create","params":{"summary":"Tagliando BMW","start":"2026-05-15T17:00:00","end":"2026-05-15T18:00:00"}}
|
|
701
|
+
- The tool response includes the REAL eventId. Use that exact value verbatim for any subsequent operation on the same event. The eventId is a long alphanumeric string returned by Google — NEVER fabricate one.
|
|
702
|
+
- Always derive every parameter value from the user's message, never from this documentation.
|
|
697
703
|
|
|
698
704
|
calendar_update(eventId, summary?, location?, description?, start?, end?)
|
|
699
705
|
- Use this for "correggi", "modifica", "rinomina", "cambia titolo", "sposta", "aggiorna" referring to an event you JUST created or found.
|