nothumanallowed 16.0.58 → 16.0.60
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/llm.mjs +25 -3
- package/src/services/message-responder.mjs +63 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "16.0.
|
|
3
|
+
"version": "16.0.60",
|
|
4
4
|
"description": "Local AI assistant: 80 tools (Gmail, Calendar, Drive, GitHub, Slack, browser, code, files), 38 agents, visual workflows (Studio, AWF, WebCraft). Install with `npm i -g nothumanallowed`, run with `nha ui`. Free tier built-in (Liara), no API key required. Your data stays on your PC — OAuth tokens local, no cloud. Open-source MIT.",
|
|
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 = '16.0.
|
|
8
|
+
export const VERSION = '16.0.60';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/llm.mjs
CHANGED
|
@@ -605,7 +605,11 @@ export function _autoPrefixOpenRouterModel(model) {
|
|
|
605
605
|
* Model names: "anthropic/claude-sonnet-4.5", "openai/gpt-4o", "google/gemini-2.5-pro"...
|
|
606
606
|
*/
|
|
607
607
|
export async function callOpenRouter(apiKey, model, systemPrompt, userMessage, stream = false, opts = {}) {
|
|
608
|
+
const originalModel = model;
|
|
608
609
|
model = _autoPrefixOpenRouterModel(model);
|
|
610
|
+
if (process.env.NHA_LOG_OPENROUTER === '1') {
|
|
611
|
+
console.error(`[openrouter] model resolved: "${originalModel}" → "${model}"`);
|
|
612
|
+
}
|
|
609
613
|
const body = {
|
|
610
614
|
model: model || 'anthropic/claude-sonnet-4.5',
|
|
611
615
|
max_tokens: opts.max_tokens || 8192,
|
|
@@ -630,16 +634,34 @@ export async function callOpenRouter(apiKey, model, systemPrompt, userMessage, s
|
|
|
630
634
|
});
|
|
631
635
|
if (!res.ok) {
|
|
632
636
|
const err = await res.text();
|
|
637
|
+
// Auto-recovery: if OpenRouter routed to OpenAI (model is openai/*) and
|
|
638
|
+
// returned 401 mentioning platform.openai.com, the OpenRouter account has
|
|
639
|
+
// BYOK enabled with an invalid/missing OpenAI key. Retry once with safe
|
|
640
|
+
// default model anthropic/claude-sonnet-4.5 (works with OpenRouter credit).
|
|
641
|
+
const isBYOKFailure = res.status === 401 &&
|
|
642
|
+
/platform\.openai\.com|Incorrect API key/i.test(err) &&
|
|
643
|
+
model.startsWith('openai/') &&
|
|
644
|
+
!opts._retriedFromByokFailure;
|
|
645
|
+
if (isBYOKFailure) {
|
|
646
|
+
if (process.env.NHA_LOG_OPENROUTER === '1') {
|
|
647
|
+
console.error(`[openrouter] BYOK failure on ${model}, retrying with anthropic/claude-sonnet-4.5`);
|
|
648
|
+
}
|
|
649
|
+
return callOpenRouter(apiKey, 'anthropic/claude-sonnet-4.5', systemPrompt, userMessage, stream, { ...opts, _retriedFromByokFailure: true });
|
|
650
|
+
}
|
|
633
651
|
// OpenRouter often passes through misleading "platform.openai.com" key errors
|
|
634
652
|
// when the real cause is (a) invalid OpenRouter key, (b) model name without
|
|
635
653
|
// vendor prefix, (c) model requires BYOK. Provide an actionable hint.
|
|
636
654
|
let hint = '';
|
|
637
655
|
if (res.status === 401) {
|
|
638
|
-
|
|
656
|
+
if (/platform\.openai\.com/i.test(err)) {
|
|
657
|
+
hint = `\n → 401 with "platform.openai.com" hint: model "${model}" routes via OpenAI but your OpenRouter account either lacks credit or has BYOK enabled with an invalid OpenAI key.\n Fix: (1) use an Anthropic/Gemini model instead (e.g. "anthropic/claude-sonnet-4.5"), OR (2) add credit at openrouter.ai/credits, OR (3) disable BYOK in OpenRouter Settings → Integrations.`;
|
|
658
|
+
} else {
|
|
659
|
+
hint = '\n → 401 from OpenRouter: your sk-or-v1-... key is invalid/expired. Regenerate at openrouter.ai/keys.';
|
|
660
|
+
}
|
|
639
661
|
} else if (res.status === 404) {
|
|
640
|
-
hint = '\n → 404 from OpenRouter
|
|
662
|
+
hint = '\n → 404 from OpenRouter: model "' + model + '" does not exist. See openrouter.ai/models for valid IDs.';
|
|
641
663
|
} else if (res.status === 402) {
|
|
642
|
-
hint = '\n → 402 from OpenRouter
|
|
664
|
+
hint = '\n → 402 from OpenRouter: insufficient credits. Top up at openrouter.ai/credits.';
|
|
643
665
|
}
|
|
644
666
|
throw new Error(`OpenRouter ${res.status}: ${err}${hint}`);
|
|
645
667
|
}
|
|
@@ -2672,7 +2672,69 @@ class TelegramResponder {
|
|
|
2672
2672
|
}
|
|
2673
2673
|
}
|
|
2674
2674
|
} catch (e) { this.log(`[direct] pagination failed: ${e.message}`); }
|
|
2675
|
-
|
|
2675
|
+
|
|
2676
|
+
// NO CACHE FALLBACK (Giovanni's recurring bug): if user asks "mostra i
|
|
2677
|
+
// prossimi" but no calendar list was cached in this session, the LLM
|
|
2678
|
+
// hallucinates plausible-but-fake events. Instead, run calendar_upcoming
|
|
2679
|
+
// for the next 7 days directly here. Deterministic, zero LLM call.
|
|
2680
|
+
try {
|
|
2681
|
+
const { listEvents } = await import('./google-calendar.mjs');
|
|
2682
|
+
const now = new Date();
|
|
2683
|
+
const weekAhead = new Date(now.getTime() + 7 * 86400000);
|
|
2684
|
+
const evs = await listEvents(config, 'primary', now, weekAhead);
|
|
2685
|
+
if (Array.isArray(evs) && evs.length > 0) {
|
|
2686
|
+
const items = evs.map(e => ({
|
|
2687
|
+
eventId: e.id,
|
|
2688
|
+
id: e.id,
|
|
2689
|
+
summary: e.summary || '(senza titolo)',
|
|
2690
|
+
time: (e.start || '').slice(11, 16),
|
|
2691
|
+
date: (e.start || '').slice(0, 10),
|
|
2692
|
+
start: e.start,
|
|
2693
|
+
}));
|
|
2694
|
+
// Persist into list-cache so subsequent "mostra i prossimi" paginates
|
|
2695
|
+
try {
|
|
2696
|
+
const { rememberList } = await import('./list-cache.mjs');
|
|
2697
|
+
rememberList(chatId || '__last_list__', 'calendar', items);
|
|
2698
|
+
} catch {}
|
|
2699
|
+
const pageSize = 8;
|
|
2700
|
+
const firstSlice = items.slice(0, pageSize);
|
|
2701
|
+
const lines = [`📅 Prossimi eventi (7 giorni, ${items.length} totali):`];
|
|
2702
|
+
const byDay = new Map();
|
|
2703
|
+
for (const e of firstSlice) {
|
|
2704
|
+
const day = e.date || 'misc';
|
|
2705
|
+
if (!byDay.has(day)) byDay.set(day, []);
|
|
2706
|
+
byDay.get(day).push(e);
|
|
2707
|
+
}
|
|
2708
|
+
for (const [day, evs] of [...byDay.entries()].sort()) {
|
|
2709
|
+
const d = day !== 'misc' ? new Date(day + 'T12:00:00').toLocaleDateString('it-IT', { weekday: 'short', day: 'numeric', month: 'short' }) : '';
|
|
2710
|
+
if (d) lines.push(`\n${d}:`);
|
|
2711
|
+
for (const e of evs) lines.push(` ${e.time || '—'} — ${e.summary}`);
|
|
2712
|
+
}
|
|
2713
|
+
if (items.length > pageSize) {
|
|
2714
|
+
lines.push(`\n... ${items.length - pageSize} eventi rimanenti. Scrivi "mostra i prossimi" per continuare.`);
|
|
2715
|
+
} else {
|
|
2716
|
+
lines.push(`\n✓ Fine elenco.`);
|
|
2717
|
+
}
|
|
2718
|
+
// Track shownCount so next "mostra i prossimi" works
|
|
2719
|
+
try {
|
|
2720
|
+
const cacheFile = path.join(os.homedir(), '.nha', 'list-cache.json');
|
|
2721
|
+
const cacheNow = fs.existsSync(cacheFile) ? JSON.parse(fs.readFileSync(cacheFile, 'utf-8')) : {};
|
|
2722
|
+
const key = chatId || '__last_list__';
|
|
2723
|
+
if (!cacheNow[key]) cacheNow[key] = {};
|
|
2724
|
+
cacheNow[key].lastList_calendar_shownCount = pageSize;
|
|
2725
|
+
fs.writeFileSync(cacheFile, JSON.stringify(cacheNow, null, 2));
|
|
2726
|
+
} catch {}
|
|
2727
|
+
this.log(`[direct] PAGINATION fallback: fetched ${items.length} events from calendar_upcoming(7d)`);
|
|
2728
|
+
return { action: 'calendar_page', success: true, message: lines.join('\n') };
|
|
2729
|
+
}
|
|
2730
|
+
// No events in next 7 days — say so explicitly, do NOT fall to LLM
|
|
2731
|
+
return { action: 'calendar_page', success: true, message: '📅 Nessun evento programmato nei prossimi 7 giorni. Scrivi "appuntamenti di [data/mese]" per cercare in un altro periodo.' };
|
|
2732
|
+
} catch (e) {
|
|
2733
|
+
this.log(`[direct] pagination fallback failed: ${e.message}`);
|
|
2734
|
+
// CRITICAL: do NOT fall through to LLM — return error instead of
|
|
2735
|
+
// letting HERALD hallucinate fake events (Giovanni's bug).
|
|
2736
|
+
return { action: 'calendar_page', success: false, message: `Non riesco a leggere il calendario in questo momento (${e.message.slice(0, 100)}). Verifica la connessione Google in Settings.` };
|
|
2737
|
+
}
|
|
2676
2738
|
}
|
|
2677
2739
|
|
|
2678
2740
|
// ─── ANAPHORIC delete + CONFIRMATION yes ────────────────────────────────
|