nothumanallowed 14.1.66 → 14.1.68

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": "14.1.66",
3
+ "version": "14.1.68",
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 = '14.1.66';
8
+ export const VERSION = '14.1.68';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -440,17 +440,24 @@ export function register(router) {
440
440
 
441
441
  router.get('/api/imap/messages', async (req, res) => {
442
442
  try {
443
- const { listMessages } = await import('../../services/email-db.mjs');
443
+ const emailDb = await import('../../services/email-db.mjs');
444
+ if (!emailDb.SQLITE_AVAILABLE) {
445
+ return sendJSON(res, 200, { messages: [], total: 0, error: 'SQLite not available' });
446
+ }
444
447
  const url = new URL(req.url, 'http://localhost');
445
448
  const accountId = url.searchParams.get('accountId');
449
+ if (!accountId) return sendJSON(res, 200, { messages: [], total: 0 });
446
450
  const labelId = url.searchParams.get('labelId') || null;
447
451
  const folder = url.searchParams.get('folder') || null;
448
452
  const limit = parseInt(url.searchParams.get('limit') || '50');
449
453
  const offset = parseInt(url.searchParams.get('offset') || '0');
450
454
  const search = url.searchParams.get('search') || '';
451
- const result = listMessages(accountId, labelId, limit, offset, search, folder);
455
+ const result = emailDb.listMessages(accountId, labelId, limit, offset, search, folder);
452
456
  sendJSON(res, 200, { messages: result.messages ?? result, total: result.total ?? 0 });
453
- } catch (e) { sendError(res, 500, e.message); }
457
+ } catch (e) {
458
+ console.error('[/api/imap/messages] Error:', e.message);
459
+ sendJSON(res, 200, { messages: [], total: 0, error: e.message });
460
+ }
454
461
  });
455
462
 
456
463
  router.get('/api/imap/message', async (req, res) => {
@@ -215,12 +215,24 @@ export function register(router) {
215
215
  const contextBlock = context ? `\n\n## CONTEXT FROM PREVIOUS STEPS:\n${context.slice(0, 8000)}` : '';
216
216
  const proposalContextBlock = body.proposalContext ? `\n\n## OTHER AGENTS' PROPOSALS (CROSS-READING):\n${body.proposalContext.slice(0, 6000)}` : '';
217
217
 
218
+ const formatInstructions = `\n\nFORMATTING RULES (CRITICAL — your output will be rendered as HTML):
219
+ - Use MARKDOWN TABLES with | pipes | for ALL tabular data. Example:
220
+ | Header 1 | Header 2 | Header 3 |
221
+ |----------|----------|----------|
222
+ | data | data | data |
223
+ - NEVER use ASCII art boxes (┌─┐│└─┘╔═╗║╚═╝). They render as ugly <pre> blocks.
224
+ - NEVER use ASCII art charts/graphs (├─●──┤). Describe data in tables instead.
225
+ - Use **bold**, *italic*, headers (## ##), bullet points, numbered lists, blockquotes (>).
226
+ - For emphasis boxes, use blockquotes: > **Key insight:** text here
227
+ - Write COMPLETE content under every heading — never leave a section empty.`;
228
+
218
229
  const sysParts = [
219
230
  agentSysDef || `You are ${agent}, a specialist AI agent in NHA Studio. Respond entirely in ${language}. Today is ${today}.`,
220
231
  `\n\n## WORKFLOW GOAL: ${task}`,
221
232
  contextBlock,
222
233
  proposalContextBlock,
223
234
  stepDef?.prompt ? `\n\n## YOUR SPECIFIC TASK:\n${stepDef.prompt}` : '',
235
+ formatInstructions,
224
236
  ];
225
237
  const systemPrompt = sysParts.join('');
226
238
  const userMessage = stepDef?.prompt || task;
@@ -571,51 +571,51 @@ export function listMessages(accountId, labelId, limit, offset, search, folder)
571
571
  const params = [accountId];
572
572
 
573
573
  if (labelId) {
574
- // Filter by label — find messages in the corresponding folder(s)
575
- const lbl = db.prepare('SELECT system_type FROM email_labels WHERE id = ?').get(labelId);
576
- if (lbl?.system_type) {
577
- // System label (inbox/sent/drafts/spam/trash) — match via folder_id JOIN
578
- // This works regardless of folder_type being correctly set, because we match
579
- // folders by BOTH folder_type AND common name patterns
580
- const knownNames = {
581
- inbox: ['inbox'],
582
- sent: ['sent', 'sent messages', 'sent items', 'posta inviata', 'inviati', 'inviata'],
583
- drafts: ['drafts', 'draft', 'bozze'],
584
- spam: ['spam', 'junk', 'junk e-mail', 'posta indesiderata'],
585
- trash: ['trash', 'cestino', 'deleted', 'deleted messages', 'deleted items'],
586
- starred: ['starred'],
587
- archived: ['archived', 'archive', 'all mail'],
588
- };
589
- const names = knownNames[lbl.system_type] || [lbl.system_type];
590
- // Match folders by folder_type OR by path/name containing known keywords
591
- // Uses LIKE to handle compound paths like "INBOX.Sent", "[Gmail]/Sent Mail", etc.
592
- const nameConditions = names.map(() => 'LOWER(f.path) LIKE ? OR LOWER(f.name) LIKE ?').join(' OR ');
593
- const folderSql = `SELECT id FROM email_folders WHERE account_id = ? AND (folder_type = ? OR ${nameConditions})`;
594
- const folderParams = [accountId, lbl.system_type, ...names.flatMap(n => [`%${n}%`, `%${n}%`])];
595
- const matchedFolders = db.prepare(folderSql).all(...folderParams);
596
- // For inbox, we need exact match on INBOX (not INBOX.Sent, INBOX.Drafts etc.)
597
- let filtered = matchedFolders;
598
- if (lbl.system_type === 'inbox') {
599
- const exact = db.prepare('SELECT id FROM email_folders WHERE account_id = ? AND UPPER(path) = ?').get(accountId, 'INBOX');
600
- filtered = exact ? [exact] : matchedFolders;
574
+ let labelFilterApplied = false;
575
+ try {
576
+ const lbl = db.prepare('SELECT system_type FROM email_labels WHERE id = ?').get(labelId);
577
+ if (lbl?.system_type) {
578
+ // System label find matching folders by folder_type and name patterns
579
+ const knownNames = {
580
+ inbox: ['inbox'],
581
+ sent: ['sent', 'sent messages', 'sent items', 'posta inviata', 'inviati', 'inviata'],
582
+ drafts: ['drafts', 'draft', 'bozze'],
583
+ spam: ['spam', 'junk', 'junk e-mail', 'posta indesiderata'],
584
+ trash: ['trash', 'cestino', 'deleted', 'deleted messages', 'deleted items'],
585
+ starred: ['starred'],
586
+ archived: ['archived', 'archive', 'all mail'],
587
+ };
588
+ const names = knownNames[lbl.system_type] || [lbl.system_type];
589
+
590
+ // For inbox, use exact path match to avoid INBOX.Sent etc.
591
+ let folderIds = [];
592
+ if (lbl.system_type === 'inbox') {
593
+ const exact = db.prepare('SELECT id FROM email_folders WHERE account_id = ? AND UPPER(path) = ?').get(accountId, 'INBOX');
594
+ if (exact) folderIds = [exact.id];
595
+ }
596
+ // For other types, match by folder_type or name LIKE
597
+ if (folderIds.length === 0) {
598
+ const nameConditions = names.map(() => 'LOWER(f.path) LIKE ? OR LOWER(f.name) LIKE ?').join(' OR ');
599
+ const sql = `SELECT id FROM email_folders f WHERE f.account_id = ? AND (f.folder_type = ? OR ${nameConditions})`;
600
+ const sqlParams = [accountId, lbl.system_type, ...names.flatMap(n => [`%${n}%`, `%${n}%`])];
601
+ folderIds = db.prepare(sql).all(...sqlParams).map(f => f.id);
602
+ }
603
+
604
+ if (folderIds.length > 0) {
605
+ const ph = folderIds.map(() => '?').join(',');
606
+ where += ` AND (m.folder_id IN (${ph}) OR EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?))`;
607
+ params.push(...folderIds, labelId);
608
+ labelFilterApplied = true;
609
+ }
601
610
  }
611
+ } catch { /* email_folders table might not exist on older DBs */ }
602
612
 
603
- if (filtered.length > 0) {
604
- const placeholders = filtered.map(() => '?').join(',');
605
- where += ` AND (m.folder_id IN (${placeholders}) OR EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?))`;
606
- params.push(...filtered.map(f => f.id), labelId);
607
- } else {
608
- // No folder found at all — fallback to message_labels join + LIKE on path
609
- where += ' AND (EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?) OR LOWER(m.imap_folder_path) LIKE ?)';
610
- params.push(labelId, `%${lbl.system_type}%`);
611
- }
612
- } else {
613
- // Custom label — only use message_labels join
613
+ if (!labelFilterApplied) {
614
+ // Fallback: use message_labels join only
614
615
  where += ' AND EXISTS (SELECT 1 FROM email_message_labels j WHERE j.message_id = m.id AND j.label_id = ?)';
615
616
  params.push(labelId);
616
617
  }
617
618
  } else if (folder) {
618
- // Filter by folder path (used by Gmail — messages stored with imap_folder_path like INBOX, SENT, etc.)
619
619
  where += ' AND m.imap_folder_path = ?';
620
620
  params.push(folder.toUpperCase());
621
621
  }