nothumanallowed 15.1.12 → 15.1.14
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": "15.1.
|
|
3
|
+
"version": "15.1.14",
|
|
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 = '15.1.
|
|
8
|
+
export const VERSION = '15.1.14';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -36,6 +36,16 @@ const ROUTING_TABLE = [
|
|
|
36
36
|
'promemoria', 'ricordami', 'prenotazione', 'evento', 'eventi',
|
|
37
37
|
'disponibilità', 'domani', 'settimana', 'oggi', 'questa settimana',
|
|
38
38
|
'prossima settimana', 'orario', 'orari', 'stamattina', 'stasera',
|
|
39
|
+
// Calendar action verbs IT/EN — without these, "cancella appuntamento"
|
|
40
|
+
// falls through to CONDUCTOR which has no calendar context.
|
|
41
|
+
'cancella', 'cancellare', 'cancellalo', 'cancellali',
|
|
42
|
+
'elimina', 'eliminare', 'eliminalo', 'eliminali',
|
|
43
|
+
'rimuovi', 'rimuovere',
|
|
44
|
+
'sposta', 'spostare', 'sposto',
|
|
45
|
+
'modifica', 'modificare', 'modificalo',
|
|
46
|
+
'correggi', 'correggere',
|
|
47
|
+
'rinomina', 'rinominare',
|
|
48
|
+
'delete', 'remove', 'move', 'update', 'rename', 'reschedule',
|
|
39
49
|
// Email EN+IT
|
|
40
50
|
'email', 'emails', 'mail', 'inbox', 'unread', 'posta',
|
|
41
51
|
'non lette', 'da leggere', 'controlla', 'controllare',
|
|
@@ -201,10 +211,19 @@ function isContinuationMessage(text, lastCtx) {
|
|
|
201
211
|
// Short messages (≤ 6 words) without clear new-request keywords are likely continuations
|
|
202
212
|
const words = lower.split(/\s+/);
|
|
203
213
|
if (words.length <= 6) {
|
|
214
|
+
// ONLY truly fresh-topic verbs go here. Action verbs that operate on a
|
|
215
|
+
// referenced item ("cancella X", "elimina Y", "sposta Z") are NOT fresh
|
|
216
|
+
// requests — they continue the previous turn. Previously `delete` and
|
|
217
|
+
// `cancel` were here and broke sticky-mode for Italian users (because
|
|
218
|
+
// `cancel` is a substring of `cancella`).
|
|
204
219
|
const NEW_REQUEST_KEYWORDS = ['calendario','appuntamento','email','posta','meteo','tempo',
|
|
205
220
|
'crea','aggiungi','cerca','trova','mostra','mandami','dimmi','quanto','quando','dove',
|
|
206
|
-
'create','add','find','search','show','send','
|
|
207
|
-
|
|
221
|
+
'create','add','find','search','show','send','weather','mail','event'];
|
|
222
|
+
// Use word-boundary matching to avoid false positives (e.g. "cancel" inside "cancella")
|
|
223
|
+
const hasNewKeyword = NEW_REQUEST_KEYWORDS.some(k => {
|
|
224
|
+
const re = new RegExp(`\\b${k}\\b`, 'i');
|
|
225
|
+
return re.test(lower);
|
|
226
|
+
});
|
|
208
227
|
if (!hasNewKeyword) return true;
|
|
209
228
|
}
|
|
210
229
|
|
|
@@ -301,12 +320,50 @@ async function callAgentWithTools(config, agentName, userMessage, languageOverri
|
|
|
301
320
|
}
|
|
302
321
|
|
|
303
322
|
history.push({ role: 'assistant', content: response });
|
|
304
|
-
|
|
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 */ }
|
|
305
349
|
}
|
|
306
350
|
|
|
307
351
|
return { text: finalText || 'Fatto.', history };
|
|
308
352
|
}
|
|
309
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
|
+
|
|
310
367
|
// ── Telegram Bot (Long Polling via native fetch) ─────────────────────────────
|
|
311
368
|
|
|
312
369
|
// ── User store for Telegram chat IDs (for broadcast notifications) ──────────
|
|
@@ -165,16 +165,15 @@ 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.
|
|
177
|
-
The tool RESPONSE will include
|
|
176
|
+
The tool RESPONSE will include the real eventId in parentheses (a long lowercase alphanumeric Google ID — NEVER invent one). REMEMBER this exact value. If the user later says "correggi", "modifica", "cambia", "sposta", "elimina" referring to this same event, use calendar_update / calendar_move / calendar_delete with that exact eventId — do NOT create a second event. Never use placeholder IDs like "ABC123" or "event_123" — those are illustrative only.
|
|
178
177
|
|
|
179
178
|
15. calendar_move(eventId: string, newStart: string, newEnd: string)
|
|
180
179
|
Reschedule an event. ALWAYS confirm before moving.
|
|
@@ -191,13 +190,13 @@ TOOLS:
|
|
|
191
190
|
Only include fields that need to change.
|
|
192
191
|
|
|
193
192
|
HOW TO GET eventId — IN ORDER OF PREFERENCE:
|
|
194
|
-
1. From your OWN previous tool response in this conversation: when you ran calendar_create earlier, the response
|
|
195
|
-
2. If step 1 doesn't apply
|
|
193
|
+
1. From your OWN previous tool response in this conversation: when you ran calendar_create / calendar_find / calendar_date earlier, the response contained the REAL eventId (a long lowercase alphanumeric Google-issued string). Use that exact value verbatim.
|
|
194
|
+
2. If step 1 doesn't apply, call calendar_find or calendar_date FIRST to look it up.
|
|
195
|
+
3. NEVER fabricate an eventId. NEVER copy placeholder strings from this documentation. Real eventIds come from Google Calendar tool responses only.
|
|
196
196
|
|
|
197
197
|
CRITICAL — "CORREGGI" / "MODIFICA" / "CAMBIA TITOLO" mappings:
|
|
198
|
-
When the user says "correggi", "modifica", "cambia", "rinomina", "sposta", "aggiorna" referring to the most recent event you just created,
|
|
198
|
+
When the user says "correggi", "modifica", "cambia", "rinomina", "sposta", "aggiorna" referring to the most recent event you just created, use calendar_update with the real eventId returned by your previous calendar_create call.
|
|
199
199
|
Do NOT call calendar_create a second time — that would create a DUPLICATE event.
|
|
200
|
-
Example: you just created event ABC123 ("Visita BMW"). User says "correggi, il titolo è Tagliando BMW" → call calendar_update(eventId: "ABC123", summary: "Tagliando BMW"). Don't create a new event, don't delete-then-create.
|
|
201
200
|
|
|
202
201
|
19. calendar_delete(eventId: string)
|
|
203
202
|
Delete (permanently remove) a calendar event by its eventId.
|
|
@@ -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,23 +695,22 @@ 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
|
|
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.
|
|
700
|
-
- eventId
|
|
701
|
-
-
|
|
702
|
-
|
|
706
|
+
- eventId MUST come from a real tool response (calendar_create / calendar_find / calendar_date). NEVER invent it.
|
|
707
|
+
- DO NOT use placeholder strings like "ABC123", "DEF456", "abc123", "event_123" — those are not real eventIds.
|
|
708
|
+
- If you don't have a real eventId, call calendar_find or calendar_date FIRST. Do not guess.
|
|
703
709
|
|
|
704
|
-
calendar_delete(eventId) — Delete an event you have
|
|
710
|
+
calendar_delete(eventId) — Delete an event you have a REAL eventId for. Same rules as update: never invent, never use placeholders.
|
|
705
711
|
calendar_move(eventId, newStart, newEnd) — Reschedule.
|
|
706
712
|
calendar_find(query, daysAhead?) — Search events by name. Returns eventIds. Use ONLY when you don't already have the id.
|
|
707
|
-
calendar_today() / calendar_tomorrow() / calendar_date(date) / calendar_upcoming(hours?) / calendar_week(startDate?) / calendar_month(month?) — Read-only listings.
|
|
713
|
+
calendar_today() / calendar_tomorrow() / calendar_date(date) / calendar_upcoming(hours?) / calendar_week(startDate?) / calendar_month(month?) — Read-only listings. **When the user says "elimina gli appuntamenti di [date]", call calendar_date(date) FIRST to get real eventIds, then calendar_delete with each real id.**
|
|
708
714
|
schedule_meeting(clientName, subject, location, durationMinutes, dateFrom, dateTo) — Find optimal slots considering travel time.
|
|
709
715
|
|
|
710
716
|
### Gmail / IMAP
|
|
@@ -1532,9 +1538,21 @@ export async function executeTool(action, params, config) {
|
|
|
1532
1538
|
}
|
|
1533
1539
|
|
|
1534
1540
|
case 'calendar_update': {
|
|
1541
|
+
let eventId = String(params.eventId || '').trim();
|
|
1542
|
+
if (!eventId) return 'eventId required. Call calendar_find or calendar_date first to get the REAL eventId.';
|
|
1543
|
+
|
|
1544
|
+
// Reject obvious placeholder IDs (model copied from prompt examples)
|
|
1545
|
+
const PLACEHOLDERS = new Set([
|
|
1546
|
+
'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
|
|
1547
|
+
'123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
|
|
1548
|
+
'eventid', 'event_id', 'id', 'null', 'undefined',
|
|
1549
|
+
]);
|
|
1550
|
+
if (PLACEHOLDERS.has(eventId.toLowerCase())) {
|
|
1551
|
+
return `"${eventId}" is a placeholder, not a real eventId. Call calendar_find or calendar_date FIRST to get real eventIds.`;
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1535
1554
|
// Smart eventId resolution: if it looks like a name instead of a Google Calendar ID, search for it
|
|
1536
|
-
|
|
1537
|
-
if (eventId && (eventId.includes(' ') || eventId.length < 10 || /[A-Z]/.test(eventId))) {
|
|
1555
|
+
if (eventId.includes(' ') || eventId.length < 10 || /[A-Z]/.test(eventId)) {
|
|
1538
1556
|
const from = new Date();
|
|
1539
1557
|
const to = new Date(from.getTime() + 14 * 86400000);
|
|
1540
1558
|
const events = await listEvents(config, 'primary', from, to);
|
|
@@ -1581,7 +1599,20 @@ export async function executeTool(action, params, config) {
|
|
|
1581
1599
|
|
|
1582
1600
|
case 'calendar_delete': {
|
|
1583
1601
|
if (!params.eventId) return 'eventId required. Call calendar_find or calendar_date first to get the REAL eventId.';
|
|
1584
|
-
let delEventId = params.eventId;
|
|
1602
|
+
let delEventId = String(params.eventId).trim();
|
|
1603
|
+
|
|
1604
|
+
// Reject placeholder IDs the model may have copied verbatim from prompt
|
|
1605
|
+
// examples or hallucinated wholesale. These are obvious giveaways — real
|
|
1606
|
+
// Google Calendar eventIds are long lowercase alphanumeric strings, not
|
|
1607
|
+
// dictionary words.
|
|
1608
|
+
const PLACEHOLDER_IDS = new Set([
|
|
1609
|
+
'abc123', 'def456', 'xyz789', 'event_123', 'event_123456789',
|
|
1610
|
+
'123', '456', 'foo', 'bar', 'baz', 'example', 'placeholder',
|
|
1611
|
+
'eventid', 'event_id', 'id', 'null', 'undefined',
|
|
1612
|
+
]);
|
|
1613
|
+
if (PLACEHOLDER_IDS.has(delEventId.toLowerCase())) {
|
|
1614
|
+
return `"${delEventId}" is a placeholder, not a real eventId. Call calendar_date(YYYY-MM-DD) or calendar_find(query) FIRST to get the real eventIds, then call calendar_delete with one of those values.`;
|
|
1615
|
+
}
|
|
1585
1616
|
|
|
1586
1617
|
// Branch A — looks like a free-text search query (spaces, caps, very short).
|
|
1587
1618
|
// The model passed a NAME instead of an ID — resolve to ID via listEvents.
|