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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "16.0.58",
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.58';
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
 
@@ -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
- hint = '\n → 401 from OpenRouter usually means: (1) your sk-or-v1-... key is invalid/expired (regenerate at openrouter.ai/keys), or (2) the model name "' + model + '" needs a vendor prefix like "anthropic/' + model.replace(/^[a-z-]+\//, '') + '". This call auto-prefixed to "' + model + '".';
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 usually means the model "' + model + '" does not exist. See openrouter.ai/models for valid IDs.';
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 means insufficient credits. Top up at openrouter.ai/credits.';
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
- // Fall through if no cached list
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 ────────────────────────────────