nothumanallowed 13.2.91 → 13.2.93

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": "13.2.91",
3
+ "version": "13.2.93",
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": {
@@ -189,11 +189,11 @@ function sendHTML(res, html) {
189
189
  res.end(html);
190
190
  }
191
191
 
192
- function parseBody(req) {
192
+ function parseBody(req, maxBytes) {
193
193
  return new Promise((resolve, reject) => {
194
194
  const chunks = [];
195
195
  let size = 0;
196
- const MAX = 1_048_576; // 1 MB
196
+ const MAX = maxBytes || 1_048_576; // 1 MB default
197
197
  req.on('data', chunk => {
198
198
  size += chunk.length;
199
199
  if (size > MAX) { reject(new Error('Body too large')); req.destroy(); return; }
@@ -2814,7 +2814,7 @@ export async function cmdUI(args) {
2814
2814
 
2815
2815
  // ── Studio: run single step (SSE streaming) ──────────────────────
2816
2816
  if (pathname === '/api/studio/run' && method === 'POST') {
2817
- const body = await parseBody(req);
2817
+ const body = await parseBody(req, 4_194_304); // 4MB — context can be up to 120KB + task + PDF
2818
2818
  const { agent, task, context, stepDef } = body;
2819
2819
  const stepPdfBase64 = body.pdfBase64 || null;
2820
2820
  const stepPdfName = body.pdfName || null;
@@ -3218,15 +3218,16 @@ RULES:
3218
3218
  if (isCanvasAgent) {
3219
3219
  sysPrompt = canvasSystemPrompt;
3220
3220
  const canvasData = [attachmentText, context].filter(Boolean).join('\n\n');
3221
- userMsg = `Create a professional dashboard report in ${language}. CRITICAL RULES:
3222
- 1. Start immediately with <div class="header"> — no preamble
3223
- 2. Every <div class="section"> MUST contain actual content from the data below — NEVER leave a section empty
3224
- 3. If data for a section exists, include it verbatim with all details
3225
- 4. If data for a section does NOT exist, omit that section entirely do NOT create empty placeholder sections
3226
- 5. Copy specific facts, names, dates, numbers exactly as they appear in the data
3227
- 6. Output ONLY HTML starting with <div class="header">
3228
-
3229
- DATA FROM AGENTS:
3221
+ userMsg = `Create a complete professional dashboard report in ${language} using the CSS classes defined in the system prompt.
3222
+
3223
+ RULES:
3224
+ - Start with <div class="header"><h1>TITLE</h1><p>Subtitle</p><div class="meta"><span>DATE</span></div></div>
3225
+ - Use <div class="section"><div class="section-title">SECTION NAME</div> ... </div> for EACH agent's findings
3226
+ - Each section MUST reproduce the agent's actual findings in full use <h3>, <p>, <ul><li>, tables
3227
+ - Output ONLY HTML (no markdown, no \`\`\`html fences, no explanations)
3228
+ - End with <div class="footer">NHA Studio · ${today}</div>
3229
+
3230
+ AGENT DATA TO INCLUDE IN FULL:
3230
3231
  ${canvasData}`;
3231
3232
  } else if (isLiveDataAgent) {
3232
3233
  // These agents fetched real data — use a focused prompt (no tool definitions to avoid JSON output)
@@ -3283,7 +3284,11 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS (use only what is RELEVANT to the wo
3283
3284
  let inThinkBlock = false;
3284
3285
  let thinkBuf = '';
3285
3286
  sendToken(isCanvasAgent ? 'Generating visual report...' : '');
3286
- const llmTimeout = isCanvasAgent ? 120000 : 90000;
3287
+ const llmTimeout = isCanvasAgent ? 180000 : 120000;
3288
+ // Canvas: no thinking (needs full tokens for HTML). Specialists: cap thinking to 2048 to leave room for answer.
3289
+ const stepLlmOpts = isCanvasAgent
3290
+ ? { max_tokens: 12288, thinking: 'off' }
3291
+ : { max_tokens: 8192, thinking_budget: 2048 };
3287
3292
  try {
3288
3293
  await withTimeout(
3289
3294
  callLLMStream(config, sysPrompt, userMsg,
@@ -3322,7 +3327,7 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS (use only what is RELEVANT to the wo
3322
3327
  if (stripped) sendToken(stripped);
3323
3328
  }
3324
3329
  },
3325
- { max_tokens: 8192 },
3330
+ stepLlmOpts,
3326
3331
  ),
3327
3332
  llmTimeout
3328
3333
  );
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 = '13.2.91';
8
+ export const VERSION = '13.2.93';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -502,21 +502,33 @@ export async function callLLMStream(config, systemPrompt, userMessage, onToken,
502
502
  .replace(/\|\|\(/g, '||(')
503
503
  .replace(/\)\|\|/g, ')||');
504
504
 
505
+ // opts.thinking === 'off' forces thinking off regardless of config
506
+ // opts.max_tokens overrides the default token budget
507
+ const forceThinkingOff = opts.thinking === 'off' || opts.thinking === false;
508
+
505
509
  let thinkingEnabled = false;
506
- try {
507
- const fs2 = await import('fs');
508
- const path2 = await import('path');
509
- const os2 = await import('os');
510
- const cfgFile2 = path2.default.join(os2.default.homedir(), '.nha', 'config.json');
511
- if (fs2.default.existsSync(cfgFile2)) {
512
- const cfg2 = JSON.parse(fs2.default.readFileSync(cfgFile2, 'utf-8'));
513
- thinkingEnabled = cfg2.thinking === true || cfg2.thinking === 'on' || cfg2.thinking === 'true';
514
- }
515
- } catch {}
510
+ if (!forceThinkingOff) {
511
+ try {
512
+ const fs2 = await import('fs');
513
+ const path2 = await import('path');
514
+ const os2 = await import('os');
515
+ const cfgFile2 = path2.default.join(os2.default.homedir(), '.nha', 'config.json');
516
+ if (fs2.default.existsSync(cfgFile2)) {
517
+ const cfg2 = JSON.parse(fs2.default.readFileSync(cfgFile2, 'utf-8'));
518
+ thinkingEnabled = cfg2.thinking === true || cfg2.thinking === 'on' || cfg2.thinking === 'true';
519
+ }
520
+ } catch {}
521
+ }
522
+
523
+ // Determine effective max_tokens:
524
+ // 1. If opts.max_tokens explicitly set, use it
525
+ // 2. If thinking is on, default to 8192 (need room for think + answer)
526
+ // 3. Otherwise default to 8192 (full context for specialist agents)
527
+ const effectiveMaxTokens = opts.max_tokens || (thinkingEnabled ? 8192 : 8192);
516
528
 
517
529
  const nhaBody = {
518
530
  model: model || '/opt/models/qwen3-32b',
519
- max_tokens: thinkingEnabled ? 8192 : 4096,
531
+ max_tokens: effectiveMaxTokens,
520
532
  messages: [
521
533
  { role: 'system', content: sanitize(systemPrompt) },
522
534
  { role: 'user', content: sanitize(userMessage) },
@@ -3395,14 +3395,36 @@ function studioScrollToAgent(agentLabel) {
3395
3395
  if (!logEl) return;
3396
3396
  var entries = logEl.querySelectorAll('.studio-log-entry');
3397
3397
  var target = null;
3398
+ var labelLow = agentLabel.toLowerCase();
3399
+ // Pass 1: exact match
3398
3400
  for (var i2 = 0; i2 < entries.length; i2++) {
3399
3401
  var agentSpan = entries[i2].querySelector('.studio-log-entry__agent');
3400
3402
  if (agentSpan && agentSpan.textContent.trim() === agentLabel) { target = entries[i2]; break; }
3401
3403
  }
3404
+ // Pass 2: startsWith (handles Parliament label that changes to "ATHENA ⇄ ...")
3405
+ if (!target) {
3406
+ for (var i3 = 0; i3 < entries.length; i3++) {
3407
+ var sp = entries[i3].querySelector('.studio-log-entry__agent');
3408
+ if (sp && (sp.textContent.trim().toLowerCase().indexOf(labelLow) === 0 || labelLow.indexOf(sp.textContent.trim().toLowerCase()) === 0)) { target = entries[i3]; break; }
3409
+ }
3410
+ }
3411
+ // Pass 3: contains (for Parliament: "Parlamento" matches any log entry with that word)
3412
+ if (!target) {
3413
+ for (var i4 = 0; i4 < entries.length; i4++) {
3414
+ var sp2 = entries[i4].querySelector('.studio-log-entry__agent');
3415
+ if (sp2 && sp2.textContent.trim().toLowerCase().indexOf(labelLow.slice(0,8)) >= 0) { target = entries[i4]; break; }
3416
+ }
3417
+ }
3402
3418
  if (target) {
3403
- target.scrollIntoView({behavior:'smooth', block:'start'});
3419
+ var logContainer = document.querySelector('.studio-log');
3420
+ if (logContainer) {
3421
+ var entryTop = target.offsetTop - logContainer.offsetTop;
3422
+ logContainer.scrollTo({top: entryTop - 8, behavior: 'smooth'});
3423
+ } else {
3424
+ target.scrollIntoView({behavior:'smooth', block:'start'});
3425
+ }
3404
3426
  target.style.outline = '2px solid var(--green)';
3405
- setTimeout(function(){ target.style.outline = ''; }, 1500);
3427
+ setTimeout(function(){ target.style.outline = ''; }, 1800);
3406
3428
  }
3407
3429
  }
3408
3430
 
@@ -4152,7 +4174,7 @@ function runStudioStep(idx, node, task, context, stepDef, signal) {
4152
4174
  // Inject attachment into first step only — pass PDF/image as dedicated fields,
4153
4175
  // NOT as raw base64 in context (would cause 100k+ token overflow for any real PDF).
4154
4176
  // Cap accumulated context to ~40KB to avoid token overflow — keep the most recent content
4155
- var cappedContext = context && context.length > 40000 ? context.slice(-40000) : context;
4177
+ var cappedContext = context && context.length > 120000 ? context.slice(-120000) : context;
4156
4178
  var bodyObj = {stepIdx: idx, agent: node.agent, task: task, context: cappedContext, stepDef: stepDef};
4157
4179
  if (idx === 0 && studioState.attachmentContext) {
4158
4180
  var ac = studioState.attachmentContext;