nothumanallowed 9.8.3 → 9.8.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nothumanallowed",
3
- "version": "9.8.3",
3
+ "version": "9.8.5",
4
4
  "description": "NotHumanAllowed — 38 AI agents, 53 tools. Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, GitHub, Notion, Slack, voice chat, 28 languages. Zero-dependency CLI.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1095,14 +1095,35 @@ export async function cmdUI(args) {
1095
1095
  fullResponse = `![Screenshot](/api/screenshots/${fname})\n\n${fullResponse}`;
1096
1096
  }
1097
1097
  } else if (toolResults.length > 0) {
1098
- // Standard tool results flow
1098
+ // Extract canvas/screenshot markers from tool results BEFORE second LLM call
1099
+ // These markers must be preserved in the final response for the UI to render
1100
+ let preservedMarkers = '';
1099
1101
  const toolContext = toolResults.map(t => {
1100
- let clean = t.result.replace(/\[Screenshot[^\]]*\]/g, '').replace(/!\[.*?\]\(data:image[^)]+\)/g, '').slice(0, 3000);
1101
- return `[${t.action} result]: ${clean.trim()}`;
1102
+ let clean = t.result;
1103
+ // Extract and preserve canvas markers
1104
+ const canvasMatch = clean.match(/\[CANVAS_RENDER\][\s\S]*?\[\/CANVAS_RENDER\]/);
1105
+ if (canvasMatch) {
1106
+ preservedMarkers += canvasMatch[0] + '\n';
1107
+ clean = clean.replace(/\[CANVAS_RENDER\][\s\S]*?\[\/CANVAS_RENDER\]/, '').trim();
1108
+ }
1109
+ if (clean.includes('[CANVAS_CLEAR]')) {
1110
+ preservedMarkers += '[CANVAS_CLEAR]Canvas cleared.[/CANVAS_CLEAR]\n';
1111
+ clean = clean.replace(/\[CANVAS_CLEAR\][\s\S]*?\[\/CANVAS_CLEAR\]/, '').trim();
1112
+ }
1113
+ // Extract screenshot file markers
1114
+ const ssMatch = clean.match(/\[SCREENSHOT_FILE\].*?\[\/SCREENSHOT_FILE\]/);
1115
+ if (ssMatch) {
1116
+ preservedMarkers += ssMatch[0] + '\n';
1117
+ clean = clean.replace(/\[SCREENSHOT_FILE\].*?\[\/SCREENSHOT_FILE\]/, '').trim();
1118
+ }
1119
+ clean = clean.replace(/\[Screenshot[^\]]*\]/g, '').replace(/!\[.*?\]\(data:image[^)]+\)/g, '').slice(0, 3000);
1120
+ return `[${t.action} result]: ${clean.trim() || 'Done.'}`;
1102
1121
  }).join('\n\n');
1103
1122
  const followUp = `The user asked: "${body.message}"\n\nI executed these tools and got REAL results:\n\n${toolContext}\n\nNow respond conversationally based ONLY on the REAL data above. Do NOT output any JSON blocks, base64, or image markdown — just natural text.`;
1104
1123
  try {
1105
1124
  fullResponse = await callLLM(config, enrichedSystemPrompt, followUp);
1125
+ // Prepend preserved markers so the UI can render canvas/screenshots
1126
+ if (preservedMarkers) fullResponse = preservedMarkers + fullResponse;
1106
1127
  } catch {
1107
1128
  fullResponse = toolResults.map(t => `${t.action}: ${t.result}`).join('\n\n');
1108
1129
  }
@@ -1437,23 +1458,35 @@ export async function cmdUI(args) {
1437
1458
  // If tools were executed, make a second LLM call with results
1438
1459
  let finalResponse = fullResponse;
1439
1460
  if (toolResults.length > 0) {
1461
+ // Extract canvas/screenshot markers BEFORE sending to LLM
1462
+ let preservedMarkers = '';
1440
1463
  const toolContext = toolResults.map(t => {
1441
- // Strip screenshot file references and base64 from tool results — the screenshot was already sent to the UI
1442
- let clean = t.result.replace(/\[Screenshot[^\]]*\]/g, '').replace(/!\[.*?\]\(data:image[^)]+\)/g, '').slice(0, 3000);
1443
- return `[${t.action} result]: ${clean.trim()}`;
1464
+ let clean = t.result;
1465
+ const canvasMatch = clean.match(/\[CANVAS_RENDER\][\s\S]*?\[\/CANVAS_RENDER\]/);
1466
+ if (canvasMatch) { preservedMarkers += canvasMatch[0] + '\n'; clean = clean.replace(/\[CANVAS_RENDER\][\s\S]*?\[\/CANVAS_RENDER\]/, '').trim(); }
1467
+ if (clean.includes('[CANVAS_CLEAR]')) { preservedMarkers += '[CANVAS_CLEAR]Canvas cleared.[/CANVAS_CLEAR]\n'; clean = clean.replace(/\[CANVAS_CLEAR\][\s\S]*?\[\/CANVAS_CLEAR\]/, '').trim(); }
1468
+ clean = clean.replace(/\[Screenshot[^\]]*\]/g, '').replace(/!\[.*?\]\(data:image[^)]+\)/g, '').slice(0, 3000);
1469
+ return `[${t.action} result]: ${clean.trim() || 'Done.'}`;
1444
1470
  }).join('\n\n');
1471
+
1472
+ // If we have canvas content, send it to client immediately via SSE
1473
+ if (preservedMarkers.includes('[CANVAS_RENDER]')) {
1474
+ sendSSE('canvas', { markers: preservedMarkers });
1475
+ }
1476
+
1445
1477
  const followUp = `The user asked: "${msg}"\n\nI executed these tools and got REAL results:\n\n${toolContext}\n\nNow respond to the user conversationally based ONLY on the REAL data above. Present the results clearly. Do NOT output any JSON blocks, any base64 data, or any image markdown — just natural text. If a screenshot was taken, just mention "Screenshot captured" without embedding it.`;
1446
1478
  sendSSE('tool_synthesis', {});
1447
1479
  try {
1448
1480
  finalResponse = await callLLMStream(config, enrichedPrompt, followUp, (chunk) => {
1449
1481
  sendSSE('token', { content: chunk });
1450
1482
  });
1451
- // Strip any JSON blocks and base64 the LLM might have emitted
1452
1483
  finalResponse = finalResponse
1453
1484
  .replace(/```json[\s\S]*?```/g, '')
1454
1485
  .replace(/!\[.*?\]\(data:image\/[^)]+\)/g, '')
1455
1486
  .replace(/data:image\/[a-z]+;base64,[A-Za-z0-9+/=]{100,}/g, '[image]')
1456
1487
  .trim();
1488
+ // Prepend preserved markers for persistence
1489
+ if (preservedMarkers) finalResponse = preservedMarkers + finalResponse;
1457
1490
  } catch {
1458
1491
  finalResponse = toolResults.map(t => `${t.action}: ${t.result}`).join('\n\n');
1459
1492
  }
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 = '9.8.3';
8
+ export const VERSION = '9.8.5';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -260,6 +260,8 @@ function showBrowserViewer(title,status){
260
260
  function updateBrowserFrame(base64,format){
261
261
  var f=document.getElementById('bvFrame');if(!f)return;
262
262
  f.innerHTML='<img src="data:image/'+(format||'jpeg')+';base64,'+base64+'" alt="Browser view">';
263
+ // Also save to canvas browser tab
264
+ lastBrowserFrame=base64;
263
265
  }
264
266
  function updateBrowserStatus(status){
265
267
  var s=document.getElementById('bvStatus');if(s)s.textContent=status;
@@ -407,7 +409,7 @@ function renderChat(el){
407
409
  '</div>'+
408
410
  '<div class="chat"><div class="chat__messages" id="chatMessages"></div>'+
409
411
  '<div id="chatAttachInfo" style="display:none;padding:4px 12px;font-size:11px;color:var(--cyan);background:var(--bg2);border-top:1px solid var(--border)"><span id="chatAttachName"></span> <button onclick="clearChatAttach()" style="background:none;border:none;color:#f44;cursor:pointer;font-size:14px;font-weight:700">&times;</button></div>'+
410
- '<div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">&#127908;</button><button onclick="document.getElementById(\\x27chatFileInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach file">&#128206;</button><button onclick="document.getElementById(\\x27chatImageInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach image">&#128247;</button><input type="file" id="chatFileInput" style="display:none" onchange="handleChatFile(this)"><input type="file" id="chatImageInput" accept="image/*" style="display:none" onchange="handleChatImage(this)"><textarea class="chat__input" id="chatInput" placeholder="Ask anything... (or attach file/image first)" rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button class="chat__stop" id="chatStop" onclick="stopChat()">Stop</button></div>'+
412
+ '<div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">&#127908;</button><button onclick="document.getElementById(\\x27chatFileInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach file">&#128206;</button><button onclick="document.getElementById(\\x27chatImageInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach image">&#128247;</button><input type="file" id="chatFileInput" style="display:none" onchange="handleChatFile(this)"><input type="file" id="chatImageInput" accept="image/*" style="display:none" onchange="handleChatImage(this)"><textarea class="chat__input" id="chatInput" placeholder="Ask anything... (or attach file/image first)" rows="1"></textarea><button class="chat__send" id="chatSend">Send</button><button class="chat__stop" id="chatStop" onclick="stopChat()">Stop</button><button onclick="reopenCanvas()" style="background:none;border:none;cursor:pointer;font-size:14px;padding:4px;color:var(--dim)" title="Open Canvas panel">&#x25A3;</button></div>'+
411
413
  '</div>'+
412
414
  '</div>'+
413
415
  '</div>';
@@ -522,17 +524,68 @@ function clearChatAttach(){
522
524
  document.getElementById('chatImageInput').value='';
523
525
  }
524
526
 
525
- // ---- CANVAS ----
527
+ // ---- CANVAS (with history + browser viewer) ----
528
+ var canvasHistory=[]; // [{html,title,timestamp}]
529
+ var canvasIdx=-1; // current index in history
530
+ var canvasMode='canvas'; // 'canvas' or 'browser'
531
+ var lastBrowserFrame=null;
532
+
526
533
  function showCanvas(html,title){
534
+ // Save to history
535
+ canvasHistory.push({html:html,title:title||'Canvas',timestamp:new Date().toLocaleTimeString()});
536
+ canvasIdx=canvasHistory.length-1;
537
+ canvasMode='canvas';
538
+ renderCanvasPanel();
539
+ // Persist canvas history to localStorage
540
+ try{localStorage.setItem('nha_canvas_history',JSON.stringify(canvasHistory.slice(-20)));}catch(e){}
541
+ }
542
+
543
+ function renderCanvasPanel(){
527
544
  var p=document.getElementById('canvasPanel');
545
+ if(!p)return;
546
+ p.classList.add('open');
547
+ var item=canvasMode==='browser'?null:canvasHistory[canvasIdx];
548
+ // Update header
528
549
  var t=document.getElementById('canvasTitle');
550
+ if(t){
551
+ if(canvasMode==='browser'){t.textContent='Browser';}
552
+ else if(item){t.textContent=item.title+(canvasHistory.length>1?' ('+(canvasIdx+1)+'/'+canvasHistory.length+')':'');}
553
+ else{t.textContent='Canvas';}
554
+ }
555
+ // Update nav buttons visibility
556
+ var navEl=document.getElementById('canvasNav');
557
+ if(navEl){navEl.style.display=canvasHistory.length>1&&canvasMode==='canvas'?'flex':'none';}
558
+ // Update tab buttons
559
+ var tabC=document.getElementById('canvasTabC');
560
+ var tabB=document.getElementById('canvasTabB');
561
+ if(tabC)tabC.style.borderBottom=canvasMode==='canvas'?'2px solid var(--green)':'none';
562
+ if(tabB)tabB.style.borderBottom=canvasMode==='browser'?'2px solid var(--green)':'none';
563
+ // Render content
529
564
  var f=document.getElementById('canvasFrame');
530
- if(!p||!f)return;
531
- if(t)t.textContent=title||'Canvas';
532
- p.classList.add('open');
533
- var doc=f.contentDocument||f.contentWindow.document;
534
- doc.open();doc.write(html);doc.close();
565
+ if(!f)return;
566
+ if(canvasMode==='browser'&&lastBrowserFrame){
567
+ var doc=f.contentDocument||f.contentWindow.document;
568
+ doc.open();doc.write('<html><body style="margin:0;background:#1a1a1a;display:flex;align-items:center;justify-content:center;height:100vh"><img src="data:image/jpeg;base64,'+lastBrowserFrame+'" style="max-width:100%;max-height:100%"/></body></html>');doc.close();
569
+ } else if(item){
570
+ var doc=f.contentDocument||f.contentWindow.document;
571
+ doc.open();doc.write(item.html);doc.close();
572
+ }
535
573
  }
574
+
575
+ function canvasPrev(){if(canvasIdx>0){canvasIdx--;canvasMode='canvas';renderCanvasPanel();}}
576
+ function canvasNext(){if(canvasIdx<canvasHistory.length-1){canvasIdx++;canvasMode='canvas';renderCanvasPanel();}}
577
+ function canvasShowBrowser(){canvasMode='browser';renderCanvasPanel();}
578
+ function canvasShowCanvas(){canvasMode='canvas';renderCanvasPanel();}
579
+
580
+ function reopenCanvas(){
581
+ if(canvasHistory.length===0){
582
+ // Try restore from localStorage
583
+ try{var saved=localStorage.getItem('nha_canvas_history');if(saved){canvasHistory=JSON.parse(saved);canvasIdx=canvasHistory.length-1;}}catch(e){}
584
+ }
585
+ if(canvasHistory.length>0){canvasIdx=canvasHistory.length-1;canvasMode='canvas';renderCanvasPanel();}
586
+ else if(lastBrowserFrame){canvasMode='browser';renderCanvasPanel();}
587
+ }
588
+
536
589
  function closeCanvas(){var p=document.getElementById('canvasPanel');if(p)p.classList.remove('open');}
537
590
  function toggleCanvasSize(){
538
591
  var p=document.getElementById('canvasPanel');if(!p)return;
@@ -652,6 +705,12 @@ function sendChat(){
652
705
  updateBrowserFrame(data.base64,data.format||'jpeg');
653
706
  if(data.url)updateBrowserStatus(data.url);
654
707
  }
708
+ if(currentEvent==='canvas'&&data.markers){
709
+ // Canvas content arrived — render it immediately
710
+ var cm=data.markers.match(/\\[CANVAS_RENDER\\]([\\s\\S]*?)\\[\\/CANVAS_RENDER\\]/);
711
+ if(cm){try{var cd=JSON.parse(cm[1]);showCanvas(cd.html,cd.title);}catch(e){}}
712
+ if(data.markers.indexOf('[CANVAS_CLEAR]')!==-1)closeCanvas();
713
+ }
655
714
  if(currentEvent==='tool_synthesis'){chatHistory[streamIdx].content='';renderMessages();}
656
715
  if(currentEvent==='done'){endStreaming();if(data.content)chatHistory[streamIdx].content=data.content;var ssf=data.screenshotFiles||[];for(var fi=0;fi<ssf.length;fi++){chatHistory[streamIdx].content+='\\n![Screenshot](/api/screenshots/'+ssf[fi]+')\\n';}renderMessages();loadConvList();if(ssf.length>0)setTimeout(closeBrowserViewer,5000);}
657
716
  if(currentEvent==='error'){endStreaming();chatHistory[streamIdx].content='Error: '+(data.message||'Unknown');renderMessages();}
@@ -2018,7 +2077,7 @@ init();
2018
2077
  </div>
2019
2078
  </div>
2020
2079
 
2021
- <div id="canvasPanel"><div class="cvs-header"><span id="canvasTitle">Canvas</span><div><button onclick="toggleCanvasSize()" title="Resize">&#x2922;</button><button onclick="closeCanvas()" title="Close">&times;</button></div></div><iframe id="canvasFrame" sandbox="allow-scripts allow-same-origin"></iframe></div>
2080
+ <div id="canvasPanel"><div class="cvs-header"><div style="display:flex;align-items:center;gap:8px"><button id="canvasTabC" onclick="canvasShowCanvas()" style="background:none;border:none;border-bottom:2px solid var(--green);color:var(--green);cursor:pointer;font-family:var(--mono);font-size:11px;padding:2px 6px">Canvas</button><button id="canvasTabB" onclick="canvasShowBrowser()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-family:var(--mono);font-size:11px;padding:2px 6px">Browser</button><span id="canvasTitle" style="font-family:var(--mono);color:var(--green);font-size:11px;margin-left:8px">Canvas</span></div><div style="display:flex;align-items:center;gap:4px"><span id="canvasNav" style="display:none;gap:4px"><button onclick="canvasPrev()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Previous">&#x25C0;</button><button onclick="canvasNext()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Next">&#x25B6;</button></span><button onclick="toggleCanvasSize()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Resize">&#x2922;</button><button onclick="closeCanvas()" style="background:none;border:none;color:var(--dim);cursor:pointer;font-size:14px" title="Close">&times;</button></div></div><iframe id="canvasFrame" sandbox="allow-scripts allow-same-origin"></iframe></div>
2022
2081
  <script>${JS}</script>
2023
2082
  </body>
2024
2083
  </html>`;