nothumanallowed 15.1.47 → 15.1.48
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.48",
|
|
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.48';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
} from '../../services/conversations.mjs';
|
|
17
17
|
import { callLLMStream, callLLM, callLLMVision, parseAgentFile } from '../../services/llm.mjs';
|
|
18
18
|
import { buildMemoryContext } from '../../services/memory.mjs';
|
|
19
|
-
import { parseActions, executeTool, buildSystemPrompt } from '../../services/tool-executor.mjs';
|
|
19
|
+
import { parseActions, executeTool, buildSystemPrompt, stripOrphanFences } from '../../services/tool-executor.mjs';
|
|
20
20
|
|
|
21
21
|
// Migrate on import (once)
|
|
22
22
|
migrateOldHistory();
|
|
@@ -275,17 +275,59 @@ export function register(router) {
|
|
|
275
275
|
a.params = { url: 'https://' + a.params.query.trim() };
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
|
-
// Auto-detect email reading intent — force
|
|
278
|
+
// Auto-detect email reading intent — force the right IMAP tool if the
|
|
279
|
+
// LLM didn't emit one. Semantic keywords (offerta/RFQ/preventivo/...)
|
|
280
|
+
// trigger `imap_search` across all synced messages with each variant,
|
|
281
|
+
// because users almost never want "just the 5 latest" — they want
|
|
282
|
+
// "everything matching X". Generic "leggi le email" still falls back
|
|
283
|
+
// to imap_list. Multi-language: it/en/de/fr/es.
|
|
284
|
+
const lower = msg.toLowerCase();
|
|
279
285
|
const wantsReadEmail = /\b(leggi|read|mostra|lista|ultime?|recenti?|email|mail|inbox|posta)\b.*\b(email|mail|messag|inbox|posta)\b|\b(email|mail)\b.*\b(leggi|read|mostra|lista|ultime?|recenti?)\b/i.test(msg);
|
|
280
|
-
|
|
286
|
+
// Semantic intent → quote / offer / proposal / order requests
|
|
287
|
+
const QUOTE_KEYWORDS = {
|
|
288
|
+
offerta: ['offerta', 'richiesta offerta', 'richiesta di offerta', 'preventivo', 'quotazione', 'rdo', 'rda'],
|
|
289
|
+
quote: ['quote', 'quotation', 'rfq', 'rfp', 'request for quote', 'request for proposal', 'price request', 'pricing'],
|
|
290
|
+
order: ['ordine', 'order', 'po ', 'purchase order', 'commessa', 'bestellung', 'commande'],
|
|
291
|
+
invoice: ['fattura', 'invoice', 'rechnung', 'facture'],
|
|
292
|
+
proposal: ['proposta', 'proposal', 'angebot', 'devis'],
|
|
293
|
+
};
|
|
294
|
+
let semanticBag = null;
|
|
295
|
+
if (/\b(offert|preventiv|quotaz|rdo\b|rda\b|quotation|rfq|rfp|request\s+for\s+(quote|proposal|pricing)|price\s+request|pricing|angebot|devis|proposta|proposal)/i.test(lower)) {
|
|
296
|
+
semanticBag = 'offerta';
|
|
297
|
+
} else if (/\b(ordin|order|purchase\s+order|po\s+\d|commessa|bestellung|commande)/i.test(lower)) {
|
|
298
|
+
semanticBag = 'order';
|
|
299
|
+
} else if (/\b(fattur|invoice|rechnung|facture)/i.test(lower)) {
|
|
300
|
+
semanticBag = 'invoice';
|
|
301
|
+
} else if (/\b(propost|proposal|angebot|devis)/i.test(lower)) {
|
|
302
|
+
semanticBag = 'proposal';
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
if ((wantsReadEmail || semanticBag) && !actions.some(a => a.action?.startsWith('imap_') || a.action === 'list_emails')) {
|
|
281
306
|
try {
|
|
282
307
|
const { listAccounts: _la } = await import('../../services/email-db.mjs');
|
|
283
308
|
const imapAccs = _la();
|
|
284
309
|
if (imapAccs.length > 0) {
|
|
285
310
|
const firstAcc = imapAccs[0];
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
311
|
+
if (semanticBag) {
|
|
312
|
+
// Push one imap_search per keyword variant in the bag. The
|
|
313
|
+
// synthesis step will dedupe overlapping hits. We default to a
|
|
314
|
+
// 60-day window (limit=80 per query) — much wider than the
|
|
315
|
+
// 5-email peek the old branch did.
|
|
316
|
+
const bag = QUOTE_KEYWORDS[semanticBag] || [];
|
|
317
|
+
const variants = [
|
|
318
|
+
...(QUOTE_KEYWORDS.offerta || []).slice(0, 3),
|
|
319
|
+
...(QUOTE_KEYWORDS.quote || []).slice(0, 3),
|
|
320
|
+
...bag,
|
|
321
|
+
];
|
|
322
|
+
const unique = [...new Set(variants)].slice(0, 6);
|
|
323
|
+
for (const q of unique) {
|
|
324
|
+
actions.push({ action: 'imap_search', params: { accountId: firstAcc.id, query: q, limit: 50 } });
|
|
325
|
+
}
|
|
326
|
+
} else {
|
|
327
|
+
const limitMatch = msg.match(/\b(\d+)\b/);
|
|
328
|
+
const limit = limitMatch ? Math.min(parseInt(limitMatch[1]), 50) : 20;
|
|
329
|
+
actions.push({ action: 'imap_list', params: { accountId: firstAcc.id, limit } });
|
|
330
|
+
}
|
|
289
331
|
}
|
|
290
332
|
} catch { /* fallback to LLM response */ }
|
|
291
333
|
}
|
|
@@ -389,18 +431,23 @@ export function register(router) {
|
|
|
389
431
|
});
|
|
390
432
|
}
|
|
391
433
|
|
|
434
|
+
// Strip orphan tool-fence blocks that the LLM may have emitted as
|
|
435
|
+
// a "no-more-tools" marker (e.g. empty ```json ``` or '''json ''').
|
|
436
|
+
// They leaked into the chat panel as visible noise.
|
|
437
|
+
const cleanFullResponse = stripOrphanFences(fullResponse);
|
|
438
|
+
|
|
392
439
|
// Persist to conversation
|
|
393
440
|
if (body.conversationId) {
|
|
394
441
|
try {
|
|
395
442
|
const conv = loadConversation(body.conversationId);
|
|
396
443
|
if (conv) {
|
|
397
|
-
addMessages(conv, msg,
|
|
444
|
+
addMessages(conv, msg, cleanFullResponse);
|
|
398
445
|
}
|
|
399
446
|
} catch {}
|
|
400
447
|
}
|
|
401
448
|
|
|
402
449
|
if (heartbeatInterval) { clearInterval(heartbeatInterval); heartbeatInterval = null; }
|
|
403
|
-
sse('done', { content:
|
|
450
|
+
sse('done', { content: cleanFullResponse });
|
|
404
451
|
res.write('data: [DONE]\n\n');
|
|
405
452
|
res.end();
|
|
406
453
|
} catch (e) {
|
|
@@ -820,9 +820,12 @@ export function parseActions(text) {
|
|
|
820
820
|
const actions = [];
|
|
821
821
|
const textParts = [];
|
|
822
822
|
|
|
823
|
-
// Normalize: some LLMs output "json ... "
|
|
824
|
-
//
|
|
823
|
+
// Normalize: some LLMs output "json ... ", 'json ... ' or '''json ... '''
|
|
824
|
+
// (Python-style triple-quote) instead of ```json ... ```. We rewrite all
|
|
825
|
+
// these to proper triple-backtick fences before the main regex runs.
|
|
825
826
|
const normalized = text
|
|
827
|
+
.replace(/'''json\s*\n?([\s\S]*?)\n?\s*'''/g, (_, body) => '```json\n' + body.trim() + '\n```')
|
|
828
|
+
.replace(/"""json\s*\n?([\s\S]*?)\n?\s*"""/g, (_, body) => '```json\n' + body.trim() + '\n```')
|
|
826
829
|
.replace(/"json\s*\n([\s\S]*?)\n\s*"/g, (_, body) => '```json\n' + body.trim() + '\n```')
|
|
827
830
|
.replace(/'json\s*\n([\s\S]*?)\n\s*'/g, (_, body) => '```json\n' + body.trim() + '\n```');
|
|
828
831
|
|
|
@@ -914,6 +917,30 @@ export function parseActions(text) {
|
|
|
914
917
|
return { textParts, actions };
|
|
915
918
|
}
|
|
916
919
|
|
|
920
|
+
/**
|
|
921
|
+
* Strip orphan tool-fence blocks from a finished LLM response. These appear
|
|
922
|
+
* when the model emits an empty `'''json '''` (or backtick-fenced) block
|
|
923
|
+
* as a no-op marker — the parser doesn't pick them up as actions but they
|
|
924
|
+
* leak into the UI as visible noise. Run this on the final assistant text
|
|
925
|
+
* before showing it to the user.
|
|
926
|
+
*/
|
|
927
|
+
export function stripOrphanFences(text) {
|
|
928
|
+
if (!text || typeof text !== 'string') return text;
|
|
929
|
+
return text
|
|
930
|
+
// Triple-backtick, triple-single, triple-double quote fences with `json`
|
|
931
|
+
// marker — empty or with a body. We strip the whole block.
|
|
932
|
+
.replace(/```json\s*\n?[\s\S]*?```/g, '')
|
|
933
|
+
.replace(/'''json\s*\n?[\s\S]*?'''/g, '')
|
|
934
|
+
.replace(/"""json\s*\n?[\s\S]*?"""/g, '')
|
|
935
|
+
// Bare action JSON that the parser already consumed but the synthesis
|
|
936
|
+
// step regurgitated (rare but happens with Liara). Only strip if the
|
|
937
|
+
// JSON shape matches "action":"...".
|
|
938
|
+
.replace(/\{\s*"action"\s*:\s*"[^"]+"\s*,?\s*("params"\s*:\s*\{[\s\S]*?\}\s*)?\}/g, '')
|
|
939
|
+
// Collapse blank-line clusters produced by the stripping.
|
|
940
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
941
|
+
.trim();
|
|
942
|
+
}
|
|
943
|
+
|
|
917
944
|
// ── Formatting Helpers ───────────────────────────────────────────────────────
|
|
918
945
|
|
|
919
946
|
/**
|