nothumanallowed 12.6.3 → 12.6.5

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/bin/nha.mjs CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import('../src/cli.mjs');
2
+ import('../src/cli.mjs').then(m => m.main(process.argv.slice(2)));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "12.6.3",
3
+ "version": "12.6.5",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 80 tools. 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": {
@@ -337,13 +337,19 @@ export async function cmdUI(args) {
337
337
  if (method === 'POST' && pathname === '/api/google/auth') {
338
338
  try {
339
339
  const { runAuthFlow } = await import('../services/google-oauth.mjs');
340
- // Run auth flow in background — opens browser
341
- runAuthFlow(config).then(success => {
342
- if (success) config._googleConnected = true;
343
- }).catch(() => {});
344
- sendJSON(res, 200, { ok: true, message: 'OAuth flow started. Check the browser window that opened.' });
340
+ // Run auth flow — opens browser, waits for callback
341
+ const success = await runAuthFlow(config);
342
+ if (success) {
343
+ config._googleConnected = true;
344
+ // Reload config to pick up new tokens
345
+ const freshConfig = await loadConfig();
346
+ Object.assign(config, freshConfig);
347
+ sendJSON(res, 200, { ok: true, message: 'Google connected successfully! You can now use email, calendar, contacts, and Drive.' });
348
+ } else {
349
+ sendJSON(res, 200, { ok: false, message: 'Google OAuth failed. Make sure you authorized the app in the browser window.' });
350
+ }
345
351
  } catch (e) {
346
- sendJSON(res, 500, { error: e.message });
352
+ sendJSON(res, 500, { error: `Google OAuth error: ${e.message}. Run "nha google" from terminal for manual setup.` });
347
353
  }
348
354
  logRequest(method, pathname, 200, Date.now() - start);
349
355
  return;
@@ -1257,12 +1263,14 @@ export async function cmdUI(args) {
1257
1263
  if (sLines.length > 0) parts.push(`[CONTEXT — ${sLines.length} earlier exchanges]\n${sLines.join('\n')}\n[END CONTEXT]`);
1258
1264
  }
1259
1265
  for (const turn of requestHistory.slice(-RECENT)) {
1260
- parts.push(`${turn.role === 'user' ? '[User]' : '[Assistant]'} ${turn.content.slice(0, 2000)}`);
1266
+ // Use llmContent (file context) when available, otherwise display content
1267
+ const turnContent = turn.llmContent || turn.content;
1268
+ parts.push(`${turn.role === 'user' ? '[User]' : '[Assistant]'} ${turnContent.slice(0, 4000)}`);
1261
1269
  }
1262
1270
  parts.push(`[User] ${body.message}`);
1263
1271
  let userMessage = parts.join('\n\n');
1264
1272
 
1265
- // Inject episodic memory context into the system prompt
1273
+ // Inject episodic memory + cross-conversation memory into the system prompt
1266
1274
  const basePrompt = effectiveSystemPrompt || chatSystemPrompt;
1267
1275
  let enrichedSystemPrompt = basePrompt;
1268
1276
  try {
@@ -1270,6 +1278,37 @@ export async function cmdUI(args) {
1270
1278
  if (memCtx) enrichedSystemPrompt = basePrompt + memCtx;
1271
1279
  } catch { /* memory unavailable */ }
1272
1280
 
1281
+ // Cross-conversation memory — summaries of recent conversations
1282
+ try {
1283
+ const allConvs = listConversations();
1284
+ if (allConvs.length > 1) {
1285
+ const summaries = [];
1286
+ let totalChars = 0;
1287
+ for (const c of allConvs) {
1288
+ if (c.id === (body.conversationId || activeConvId)) continue;
1289
+ if (summaries.length >= 8 || totalChars > 2000) break;
1290
+ if (!c.messages || c.messages.length === 0) continue;
1291
+ const firstUser = c.messages.find(m => m.role === 'user');
1292
+ const lastAssistant = [...c.messages].reverse().find(m => m.role === 'assistant');
1293
+ if (!firstUser) continue;
1294
+ const date = c.updatedAt?.split('T')[0] || '?';
1295
+ const title = c.title !== 'New Chat' ? c.title : firstUser.content.slice(0, 60);
1296
+ let s = `• [${date}] "${title}" (${c.messages.length} msgs)`;
1297
+ s += `\n User: ${firstUser.content.replace(/\s+/g, ' ').slice(0, 120)}`;
1298
+ if (lastAssistant) {
1299
+ const preview = lastAssistant.content.replace(/<think>[\s\S]*?<\/think>/g, '').replace(/\s+/g, ' ').slice(0, 150);
1300
+ s += `\n Result: ${preview}`;
1301
+ }
1302
+ if (totalChars + s.length > 2000) break;
1303
+ summaries.push(s);
1304
+ totalChars += s.length;
1305
+ }
1306
+ if (summaries.length > 0) {
1307
+ enrichedSystemPrompt += `\n\n--- CONVERSATION MEMORY ---\nYou remember these past conversations:\n\n${summaries.join('\n\n')}\n\nUse this to maintain continuity. Never say "I don't have access to previous conversations".\n--- END MEMORY ---`;
1308
+ }
1309
+ }
1310
+ } catch { /* non-critical */ }
1311
+
1273
1312
  // Handle image attachment — vision API
1274
1313
  if (body.imageBase64 && body.imageMimeType) {
1275
1314
  try {
@@ -1362,13 +1401,33 @@ export async function cmdUI(args) {
1362
1401
  const pdfPrompt = body.message || `Read and analyze this PDF document "${body.pdfName}". Extract all text content, summarize key information.`;
1363
1402
  let pdfResponse = '';
1364
1403
 
1365
- if (provider === 'nha') {
1366
- // NHA Free tier: extract text from PDF, then send to Liara chat
1367
- // Decode PDF base64 and extract text content
1404
+ // Step 1: Extract text — try server (pdftotext) first, then local fallback
1405
+ let pdfText = '';
1406
+ try {
1407
+ const extractRes = await fetch('https://nothumanallowed.com/api/v1/tools/extract-pdf', {
1408
+ method: 'POST',
1409
+ headers: { 'Content-Type': 'application/json', 'X-NHA-Client': 'desktop' },
1410
+ body: JSON.stringify({ base64: body.pdfBase64 }),
1411
+ signal: AbortSignal.timeout(30000),
1412
+ });
1413
+ if (extractRes.ok) {
1414
+ const d = await extractRes.json();
1415
+ pdfText = d.text || '';
1416
+ }
1417
+ } catch { /* server unreachable */ }
1418
+ // Local fallback if server extraction failed
1419
+ if (pdfText.length < 20) {
1368
1420
  const pdfBuffer = Buffer.from(body.pdfBase64, 'base64');
1369
- const pdfText = extractTextFromPdf(pdfBuffer);
1421
+ pdfText = extractTextFromPdf(pdfBuffer);
1422
+ }
1423
+
1424
+ // Save extracted text as llmContent so it persists across turns
1425
+ const pdfLlmContent = pdfText.length > 20
1426
+ ? `[PDF: ${body.pdfName}]\n\n${pdfText.slice(0, 12000)}\n\n---\n\nUser question: ${pdfPrompt}`
1427
+ : '';
1428
+
1429
+ if (provider === 'nha') {
1370
1430
  if (!pdfText || pdfText.length < 10) {
1371
- // Fallback: send first page as image to vision model
1372
1431
  const r = await fetch('https://nothumanallowed.com/api/v1/liara/vision', {
1373
1432
  method: 'POST',
1374
1433
  headers: { 'Content-Type': 'application/json' },
@@ -1381,7 +1440,6 @@ export async function cmdUI(args) {
1381
1440
  pdfResponse = 'Could not read this PDF. Try a text-based PDF or use Claude/Gemini for scanned documents.';
1382
1441
  }
1383
1442
  } else {
1384
- // Send extracted text to Liara chat
1385
1443
  const truncatedText = pdfText.slice(0, 12000);
1386
1444
  const r = await fetch('https://nothumanallowed.com/api/v1/liara/chat', {
1387
1445
  method: 'POST',
@@ -1439,7 +1497,8 @@ export async function cmdUI(args) {
1439
1497
  pdfResponse = `PDF reading requires Anthropic (Claude) or Gemini. Your provider "${provider}" does not support native PDF documents.`;
1440
1498
  }
1441
1499
 
1442
- sendJSON(res, 200, { response: pdfResponse });
1500
+ // Return llmContent so frontend can persist the PDF text across turns
1501
+ sendJSON(res, 200, { response: pdfResponse, llmContent: pdfLlmContent || undefined });
1443
1502
  logRequest(method, pathname, 200, Date.now() - start);
1444
1503
  return;
1445
1504
  } catch (e) {
@@ -1449,12 +1508,14 @@ export async function cmdUI(args) {
1449
1508
  }
1450
1509
  }
1451
1510
 
1452
- // Handle text file attachment
1511
+ // Handle text file attachment — include file content as llmContent for persistence
1512
+ let fileLlmContent = '';
1453
1513
  if (body.fileContent && body.fileName) {
1454
1514
  const filePrompt = body.message
1455
1515
  ? `User asks about file "${body.fileName}": ${body.message}\n\nFile content:\n${body.fileContent.slice(0, 8000)}`
1456
1516
  : `Analyze this file "${body.fileName}":\n\n${body.fileContent.slice(0, 8000)}`;
1457
1517
  userMessage = filePrompt;
1518
+ fileLlmContent = filePrompt;
1458
1519
  }
1459
1520
 
1460
1521
  try {
@@ -1551,7 +1612,7 @@ export async function cmdUI(args) {
1551
1612
  } catch { /* non-critical */ }
1552
1613
  try { extractMemory('chat', body.message, fullResponse); } catch { /* non-critical */ }
1553
1614
 
1554
- sendJSON(res, 200, { response: fullResponse, toolResults, actions });
1615
+ sendJSON(res, 200, { response: fullResponse, toolResults, actions, ...(fileLlmContent ? { llmContent: fileLlmContent } : {}) });
1555
1616
  } catch (e) {
1556
1617
  sendJSON(res, 200, { response: null, error: e.message });
1557
1618
  }
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 = '12.6.3';
8
+ export const VERSION = '12.6.5';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -925,6 +925,11 @@ function sendChat(){
925
925
  clearChatAttach();
926
926
  apiPost('/api/chat',payload).then(function(r){
927
927
  chatHistory.pop();
928
+ // Save llmContent in the user message so file context persists across turns
929
+ if(r&&r.llmContent){
930
+ var lastUser=chatHistory[chatHistory.length-1];
931
+ if(lastUser&&lastUser.role==='user')lastUser.llmContent=r.llmContent;
932
+ }
928
933
  if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
929
934
  else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
930
935
  else{chatHistory.push({role:'assistant',content:'Error: no response'})}
@@ -943,7 +948,7 @@ function sendChat(){
943
948
  chatHistory.push({role:'assistant',content:''});
944
949
  renderMessages();
945
950
  var streamIdx=chatHistory.length-1;
946
- var allHistory=chatHistory.slice(0,-1).map(function(m){return{role:m.role,content:(m.content||'').replace(/!\\[Screenshot\\]\\(data:image\\/[^)]+\\)/g,'[Screenshot taken]')};});
951
+ var allHistory=chatHistory.slice(0,-1).map(function(m){var h={role:m.role,content:(m.content||'').replace(/!\\[Screenshot\\]\\(data:image\\/[^)]+\\)/g,'[Screenshot taken]')};if(m.llmContent)h.llmContent=m.llmContent;return h;});
947
952
  var payload={message:msg,history:allHistory,conversationId:activeConvId,isRetry:isRetry};
948
953
 
949
954
  fetch(API+'/api/chat/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload),signal:chatAbortController.signal}).then(function(response){