nothumanallowed 15.1.27 → 15.1.30
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.30",
|
|
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.30';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -108,6 +108,16 @@ class SandboxManager {
|
|
|
108
108
|
const MAX_RETRIES = 3;
|
|
109
109
|
this._stoppedByUser = false;
|
|
110
110
|
|
|
111
|
+
// Server version banner — lets the user verify their `nha ui` process is
|
|
112
|
+
// actually running the latest code (npm install only updates files on disk;
|
|
113
|
+
// the running process must be killed and restarted to pick them up).
|
|
114
|
+
if (_attempt === 1) {
|
|
115
|
+
try {
|
|
116
|
+
const { VERSION } = await import('../../constants.mjs');
|
|
117
|
+
emit({ type: 'status', msg: `nha sandbox manager v${VERSION} — if this is older than what you installed, kill nha ui and restart it (the running process won't pick up new code by itself)` });
|
|
118
|
+
} catch { /* non-fatal */ }
|
|
119
|
+
}
|
|
120
|
+
|
|
111
121
|
// Kill any existing sandbox
|
|
112
122
|
if (this.isRunning()) {
|
|
113
123
|
emit({ type: 'phase', phase: 'cleanup', msg: 'Stopping previous sandbox...' });
|
|
@@ -808,6 +808,33 @@ class TelegramResponder {
|
|
|
808
808
|
if (lastCtx.history && lastCtx.history.length > 0) {
|
|
809
809
|
preHistory = lastCtx.history;
|
|
810
810
|
}
|
|
811
|
+
|
|
812
|
+
// ── Pending-action force execution ──────────────────────────────
|
|
813
|
+
// The classic failure: assistant proposes "Cercherò X, poi eliminerò,
|
|
814
|
+
// poi mostrerò la lista — Procedo?" → user says "Si" → model replies
|
|
815
|
+
// "Procedo. Cosa vuoi?" (amnesia). Liara/Qwen3 don't always pick up
|
|
816
|
+
// that a short confirmation = execute the proposed plan.
|
|
817
|
+
// Fix: if the previous assistant reply asks for confirmation AND the
|
|
818
|
+
// user message is a short confirmation, REWRITE the user message into
|
|
819
|
+
// an explicit "execute now" instruction with the original plan inline.
|
|
820
|
+
const lowerReply = (lastCtx.agentReply || '').toLowerCase();
|
|
821
|
+
const proposedAction = /procedo\??|confermi\??|conferma\??|posso (?:procedere|farlo|cancellarlo|eliminarlo|crearlo|inserirlo|aggiungere|inviarlo|spostarlo)|vuoi che (?:lo )?(?:cancelli|elimini|crei|inserisca|invii|sposti|modifichi|aggiunga)|shall i|should i|do you want me/.test(lowerReply);
|
|
822
|
+
const shortConfirm = /^(s[ìi]\.?|ok\.?|okay\.?|yes\.?|yep\.?|sure\.?|certo\.?|procedi\.?|fallo\.?|vai\.?|go\.?|do it\.?|conferma\.?|certo che s[ìi]\.?|fallo pure\.?)$/i.test(cleanText.trim());
|
|
823
|
+
|
|
824
|
+
if (proposedAction && shortConfirm) {
|
|
825
|
+
enrichedMessage =
|
|
826
|
+
`[CONFERMA UTENTE — Esegui SUBITO l'azione che hai proposto nel turno precedente]\n\n` +
|
|
827
|
+
`Tuo turno precedente:\n"${(lastCtx.agentReply || '').slice(0, 800)}"\n\n` +
|
|
828
|
+
`Risposta utente: "${cleanText.trim()}"\n\n` +
|
|
829
|
+
`ISTRUZIONI OBBLIGATORIE:\n` +
|
|
830
|
+
`1. L'utente sta confermando il piano che TU hai proposto. NON chiedere altri chiarimenti.\n` +
|
|
831
|
+
`2. NON dire "procedo" senza eseguire — emetti SUBITO i tool block JSON.\n` +
|
|
832
|
+
`3. Se hai promesso N step (cercare, eliminare, mostrare la lista, etc.) — emetti TUTTI gli N tool block in sequenza nel TUO prossimo messaggio.\n` +
|
|
833
|
+
`4. Esempio: se hai detto "cercherò l'evento, lo eliminerò, mostrerò la lista" → emetti calendar_find → quando ricevi il risultato emetti calendar_delete con l'eventId trovato → infine calendar_month/calendar_week.\n` +
|
|
834
|
+
`5. NON ripetere "Procedo. Cosa vuoi?" — sai già cosa vuole, lo hai proposto tu. Esegui.`;
|
|
835
|
+
this.log(`[Telegram] ${fromUser}: pending-action force (proposal detected in prev reply, user confirmed short)`);
|
|
836
|
+
}
|
|
837
|
+
|
|
811
838
|
this.log(`[Telegram] ${fromUser}: continuation → ${agent.toUpperCase()} (ctx ${Math.round(stickyAge/1000)}s ago, history=${preHistory ? preHistory.length : 0})`);
|
|
812
839
|
} else {
|
|
813
840
|
// Fresh request — route normally
|
|
@@ -714,7 +714,12 @@ calendar_update(eventId, summary?, location?, description?, start?, end?)
|
|
|
714
714
|
calendar_delete(eventId) — Delete an event you have a REAL eventId for. Same rules as update: never invent, never use placeholders.
|
|
715
715
|
calendar_move(eventId, newStart, newEnd) — Reschedule.
|
|
716
716
|
calendar_find(query, daysAhead?) — Search events by name. Returns eventIds. Use ONLY when you don't already have the id.
|
|
717
|
-
|
|
717
|
+
- daysAhead default is 7 — pass 30 or 60 for broader search if not found in 7.
|
|
718
|
+
- Query is matched case-insensitively against summary AND description.
|
|
719
|
+
- If a user names an event ambiguously (e.g. "Italiano della BMW" via voice-to-text typo for "Tagliando della BMW"), try BOTH the literal query AND alternative spellings.
|
|
720
|
+
calendar_today() / calendar_tomorrow() / calendar_date(date) / calendar_upcoming(hours?) / calendar_week(startDate?) / calendar_month(month?) — Read-only listings.
|
|
721
|
+
- **calendar_week with NO arguments** returns the CURRENT calendar week (Mon..Sun). Use this when the user says "questa settimana" / "this week". Do NOT pass startDate — let the tool compute the right Monday. Only pass startDate when the user explicitly names a different week.
|
|
722
|
+
- **"elimina gli appuntamenti di [date]"** → call calendar_date(date) FIRST to get real eventIds, then calendar_delete with each real id.
|
|
718
723
|
schedule_meeting(clientName, subject, location, durationMinutes, dateFrom, dateTo) — Find optimal slots considering travel time.
|
|
719
724
|
|
|
720
725
|
### Gmail / IMAP
|
|
@@ -1494,7 +1499,18 @@ export async function executeTool(action, params, config) {
|
|
|
1494
1499
|
}
|
|
1495
1500
|
|
|
1496
1501
|
case 'calendar_week': {
|
|
1497
|
-
|
|
1502
|
+
// Default to MONDAY of the current ISO week, not "today". When a user
|
|
1503
|
+
// asks "questa settimana"/"this week", they mean Mon..Sun of the calendar
|
|
1504
|
+
// week — not "next 7 days from today". If they want the latter they can
|
|
1505
|
+
// pass startDate explicitly.
|
|
1506
|
+
const isoMondayOfCurrentWeek = () => {
|
|
1507
|
+
const d = new Date();
|
|
1508
|
+
const day = d.getDay(); // 0=Sun, 1=Mon, ..., 6=Sat
|
|
1509
|
+
const offset = day === 0 ? -6 : 1 - day; // shift back to Monday
|
|
1510
|
+
const monday = new Date(d.getTime() + offset * 86400000);
|
|
1511
|
+
return `${monday.getFullYear()}-${String(monday.getMonth() + 1).padStart(2, '0')}-${String(monday.getDate()).padStart(2, '0')}`;
|
|
1512
|
+
};
|
|
1513
|
+
const startDate = params.startDate || isoMondayOfCurrentWeek();
|
|
1498
1514
|
const from = new Date(startDate + 'T00:00:00');
|
|
1499
1515
|
const to = new Date(from.getTime() + 7 * 86400000);
|
|
1500
1516
|
const events = await listEvents(config, 'primary', from, to);
|
|
@@ -1561,25 +1577,57 @@ export async function executeTool(action, params, config) {
|
|
|
1561
1577
|
}
|
|
1562
1578
|
|
|
1563
1579
|
case 'calendar_find': {
|
|
1564
|
-
const
|
|
1565
|
-
const
|
|
1566
|
-
|
|
1567
|
-
|
|
1568
|
-
|
|
1580
|
+
const queryRaw = (params.query || '').trim();
|
|
1581
|
+
const query = queryRaw.toLowerCase();
|
|
1582
|
+
// Token-level fuzzy match: handles voice-to-text typos like
|
|
1583
|
+
// "Italiano della BMW" matching event "Tagliando BMW" via the shared
|
|
1584
|
+
// significant token "BMW". Stop-words and articles are ignored.
|
|
1585
|
+
const STOP_WORDS = new Set(['il','la','i','le','gli','un','una','di','del','dello','della','dei','degli','delle','a','al','allo','alla','ai','agli','alle','con','per','su','da','in','e','o','che','the','a','an','of','for','to','in','on','with','and','or','my','your']);
|
|
1586
|
+
const tokens = query.split(/\s+/).filter(t => t.length >= 3 && !STOP_WORDS.has(t));
|
|
1587
|
+
|
|
1588
|
+
const requestedDays = params.daysAhead || 30;
|
|
1589
|
+
// Try the requested range first, then auto-broaden to 90 days if empty.
|
|
1590
|
+
// Voice users frequently underestimate how far ahead an event is.
|
|
1591
|
+
const tryRange = async (days) => {
|
|
1592
|
+
const from = new Date();
|
|
1593
|
+
const to = new Date(from.getTime() + days * 86400000);
|
|
1594
|
+
const events = await listEvents(config, 'primary', from, to);
|
|
1569
1595
|
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1596
|
+
// Strict substring match first
|
|
1597
|
+
let matches = events.filter(e =>
|
|
1598
|
+
(e.summary || '').toLowerCase().includes(query) ||
|
|
1599
|
+
(e.description || '').toLowerCase().includes(query)
|
|
1600
|
+
);
|
|
1601
|
+
|
|
1602
|
+
// Fuzzy fallback: any significant token (3+ chars, no stop-word) matches
|
|
1603
|
+
if (matches.length === 0 && tokens.length > 0) {
|
|
1604
|
+
matches = events.filter(e => {
|
|
1605
|
+
const hay = ((e.summary || '') + ' ' + (e.description || '')).toLowerCase();
|
|
1606
|
+
return tokens.some(t => hay.includes(t));
|
|
1607
|
+
});
|
|
1608
|
+
}
|
|
1609
|
+
return { events, matches };
|
|
1610
|
+
};
|
|
1574
1611
|
|
|
1575
|
-
|
|
1612
|
+
let { matches } = await tryRange(requestedDays);
|
|
1613
|
+
let effectiveDays = requestedDays;
|
|
1614
|
+
if (matches.length === 0 && requestedDays < 90) {
|
|
1615
|
+
const broad = await tryRange(90);
|
|
1616
|
+
matches = broad.matches;
|
|
1617
|
+
effectiveDays = 90;
|
|
1618
|
+
}
|
|
1576
1619
|
|
|
1620
|
+
if (matches.length === 0) {
|
|
1621
|
+
return `No events found matching "${queryRaw}" in the next ${effectiveDays} days (tried fuzzy match on tokens: ${tokens.join(', ') || '(none)'}). Try calendar_week or calendar_month for a broader view.`;
|
|
1622
|
+
}
|
|
1623
|
+
|
|
1624
|
+
const widened = effectiveDays > requestedDays ? ` (auto-broadened to ${effectiveDays} days)` : '';
|
|
1577
1625
|
return matches.map((e, i) => {
|
|
1578
1626
|
const time = e.isAllDay ? 'All day' : `${formatTime(e.start)} - ${formatTime(e.end)}`;
|
|
1579
1627
|
const date = e.start.split('T')[0];
|
|
1580
1628
|
const loc = e.location ? ` | Location: ${e.location}` : '';
|
|
1581
1629
|
return `${i + 1}. [eventId: ${e.id}] ${date} ${time} — ${e.summary}${loc}`;
|
|
1582
|
-
}).join('\n');
|
|
1630
|
+
}).join('\n') + (widened ? `\n${widened}` : '');
|
|
1583
1631
|
}
|
|
1584
1632
|
|
|
1585
1633
|
case 'calendar_update': {
|