nothumanallowed 13.2.96 → 13.2.97

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.96",
3
+ "version": "13.2.97",
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": {
@@ -3280,122 +3280,146 @@ ${context ? `## OUTPUT FROM PREVIOUS AGENTS (use only what is RELEVANT to the wo
3280
3280
  }
3281
3281
 
3282
3282
  // ── Stream LLM response ───────────────────────────────────────
3283
+ // Specialist (non-canvas, non-live-data) agents use Structured Generation:
3284
+ // Phase 1 — Outline: one call that returns ONLY section headings as a numbered list
3285
+ // Phase 2 — Section fill: one call per section, with full context of sections already written
3286
+ // Live-data + Canvas agents use the classic single-stream approach.
3283
3287
  let fullOutput = '';
3284
- let inThinkBlock = false;
3285
- let thinkBuf = '';
3286
- sendToken(isCanvasAgent ? 'Generating visual report...' : '');
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 };
3292
- try {
3288
+ let fullOutputClean = '';
3289
+
3290
+ // ── Helper: strip <think> tags from a string ─────────────────
3291
+ const stripThink = (s) => s.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
3292
+
3293
+ // ── Helper: stream a single LLM call, strip think tags, return full text ─
3294
+ const streamCall = async (sysPr, usrMsg, opts, timeout, onToken) => {
3295
+ let raw = '';
3296
+ let inThink = false;
3297
+ let tBuf = '';
3293
3298
  await withTimeout(
3294
- callLLMStream(config, sysPrompt, userMsg,
3295
- (token) => {
3296
- fullOutput += token;
3297
- if (!isCanvasAgent) {
3298
- // Buffer and strip <think>...</think> blocks before sending to client
3299
- thinkBuf += token;
3300
- // Process thinkBuf: emit only content outside think tags
3301
- let out = '';
3302
- let buf = thinkBuf;
3303
- while (buf.length > 0) {
3304
- if (inThinkBlock) {
3305
- const closeIdx = buf.indexOf('</think>');
3306
- if (closeIdx >= 0) {
3307
- buf = buf.slice(closeIdx + 8);
3308
- inThinkBlock = false;
3309
- } else {
3310
- buf = '';
3311
- }
3312
- } else {
3313
- const openIdx = buf.indexOf('<think>');
3314
- if (openIdx >= 0) {
3315
- out += buf.slice(0, openIdx);
3316
- buf = buf.slice(openIdx + 7);
3317
- inThinkBlock = true;
3318
- } else {
3319
- out += buf;
3320
- buf = '';
3321
- }
3322
- }
3299
+ callLLMStream(config, sysPr, usrMsg, (tok) => {
3300
+ raw += tok;
3301
+ if (onToken) {
3302
+ tBuf += tok;
3303
+ let out = '';
3304
+ let buf = tBuf;
3305
+ while (buf.length > 0) {
3306
+ if (inThink) {
3307
+ const ci = buf.indexOf('</think>');
3308
+ if (ci >= 0) { buf = buf.slice(ci + 8); inThink = false; }
3309
+ else { buf = ''; }
3310
+ } else {
3311
+ const oi = buf.indexOf('<think>');
3312
+ if (oi >= 0) { out += buf.slice(0, oi); buf = buf.slice(oi + 7); inThink = true; }
3313
+ else { out += buf; buf = ''; }
3323
3314
  }
3324
- thinkBuf = '';
3325
- // Strip JSON tool calls from specialist agent output
3326
- const stripped = out.replace(/\{[\s\S]*?"action"[\s\S]*?\}/g, '').trim();
3327
- if (stripped) sendToken(stripped);
3328
3315
  }
3329
- },
3330
- stepLlmOpts,
3331
- ),
3332
- llmTimeout
3316
+ tBuf = '';
3317
+ const stripped = out.replace(/\{[\s\S]*?"action"[\s\S]*?\}/g, '').trim();
3318
+ if (stripped) onToken(stripped);
3319
+ }
3320
+ }, opts),
3321
+ timeout
3333
3322
  );
3334
- } catch (e) {
3335
- if (!isCanvasAgent) sendToken(`[Error: ${e.message}]`);
3336
- }
3323
+ return stripThink(raw);
3324
+ };
3337
3325
 
3338
- // Strip think tags from fullOutput before emptiness check
3339
- let fullOutputClean = fullOutput.replace(/<think>[\s\S]*?<\/think>/g, '').trim();
3326
+ const useStructuredGen = !isCanvasAgent && !isLiveDataAgent;
3340
3327
 
3341
- // ── Fill empty sections — iterative loop until no empty sections remain ─────
3342
- // Detect headings with no body: heading immediately followed by another heading or end.
3343
- // We loop because filling section A may expose B, C, D that were skipped by detection.
3344
- const getEmptySections = (text) => {
3345
- const rx = /^(#{1,4}\s+[^\n]+)\n+(?=#{1,4}\s|$)/gm;
3346
- const found = [];
3347
- let m;
3348
- while ((m = rx.exec(text)) !== null) found.push(m[1].trim());
3349
- return found;
3350
- };
3328
+ if (isCanvasAgent) {
3329
+ // ── Canvas: single stream, HTML output ────────────────────
3330
+ sendToken('Generating visual report...');
3331
+ try {
3332
+ fullOutput = await streamCall(sysPrompt, userMsg, { max_tokens: 12288, thinking: 'off' }, 180000, null);
3333
+ } catch (e) { /* canvas errors surfaced below */ }
3334
+ fullOutputClean = fullOutput;
3335
+
3336
+ } else if (useStructuredGen) {
3337
+ // ── Structured Generation ─────────────────────────────────
3338
+ // Phase 1: ask for ONLY the section outline (headings list), fast and cheap
3339
+ const structConfig = Object.assign({}, config, { thinking: 'off' });
3340
+ const outlineSys = `You are ${agent}, a specialist AI agent. Today is ${today}. Respond in ${language}.
3341
+ Your task: plan the sections of a complete structured report for this goal: ${task}
3342
+ Instruction: ${stepPrompt}
3343
+
3344
+ Output ONLY a numbered list of section headings — one per line, no body text, no explanation.
3345
+ Use ## prefix for each heading. Maximum 8 sections. Example:
3346
+ ## 1. Executive Summary
3347
+ ## 2. Key Findings
3348
+ ## 3. Analysis`;
3349
+ let outlineRaw = '';
3350
+ try {
3351
+ outlineRaw = await withTimeout(
3352
+ new Promise((res) => {
3353
+ let acc = '';
3354
+ callLLMStream(structConfig, outlineSys, 'List the section headings only.', (tok) => { acc += tok; }, { max_tokens: 300, thinking: 'off' }).then(() => res(acc)).catch(() => res(acc));
3355
+ }),
3356
+ 20000
3357
+ );
3358
+ } catch {}
3359
+
3360
+ // Parse headings: lines starting with ## (or plain numbered lines as fallback)
3361
+ const headingLines = stripThink(outlineRaw)
3362
+ .split('\n')
3363
+ .map(l => l.trim())
3364
+ .filter(l => l.match(/^#{1,4}\s+\S/) || l.match(/^\d+[\.\)]\s+\S/))
3365
+ .map(l => l.match(/^#{1,4}\s+/) ? l : '## ' + l.replace(/^\d+[\.\)]\s+/, ''))
3366
+ .slice(0, 8);
3367
+
3368
+ if (headingLines.length === 0) {
3369
+ // Outline failed — fall back to single stream
3370
+ sendToken('');
3371
+ try {
3372
+ fullOutput = await streamCall(sysPrompt, userMsg, { max_tokens: 8192, thinking_budget: 2048 }, 120000, sendToken);
3373
+ } catch (e) { sendToken(`[Error: ${e.message}]`); }
3374
+ fullOutputClean = stripThink(fullOutput);
3375
+ } else {
3376
+ // Phase 2: one call per section, each sees the sections already written
3377
+ const dataBlock = [
3378
+ attachmentText ? `## ATTACHED FILE:\n${attachmentText}` : '',
3379
+ toolData ? `## LIVE DATA:\n${toolData}` : '',
3380
+ context ? `## PREVIOUS AGENTS OUTPUT:\n${context}` : '',
3381
+ ].filter(Boolean).join('\n\n');
3382
+ const sectionParts = [];
3383
+ for (let si = 0; si < headingLines.length; si++) {
3384
+ const heading = headingLines[si];
3385
+ const writtenSoFar = sectionParts.join('\n\n');
3386
+ const secSys = `You are ${agent}, a specialist AI agent inside NHA Studio. Today is ${today}. Respond entirely in ${language}.
3387
+
3388
+ ## OVERALL WORKFLOW GOAL:
3389
+ ${task}
3351
3390
 
3352
- if (!isCanvasAgent) {
3353
- const dataForFill = (toolData || context || '').slice(0, 4000);
3354
- const fillConfig = Object.assign({}, config, { thinking: 'off' });
3355
- let fillRound = 0;
3356
- const MAX_FILL_ROUNDS = 3; // safety cap never more than 3 passes
3357
- let emptySections = getEmptySections(fullOutputClean);
3358
-
3359
- while (emptySections.length > 0 && fillRound < MAX_FILL_ROUNDS) {
3360
- fillRound++;
3361
- if (fillRound === 1) sendToken('\n\n[Completando sezioni mancanti...] ');
3362
- for (const emptyHeading of emptySections) {
3363
- sendToken('\n' + emptyHeading + '\n');
3364
- let filledContent = '';
3365
- const reportSoFar = fullOutputClean.slice(-3000);
3391
+ CRITICAL RULES:
3392
+ - Write ONLY the body content for the section heading given — do NOT repeat the heading
3393
+ - Do NOT open new headings or sub-sections — write flowing prose + bullet points
3394
+ - Do NOT invent data — use ONLY what is in the DATA block below
3395
+ - Be thorough, specific, and completethis is one section of an executive report
3396
+
3397
+ ${dataBlock}
3398
+ ${writtenSoFar ? `## REPORT WRITTEN SO FAR (for consistency):\n${writtenSoFar.slice(-3000)}` : ''}`;
3399
+ const secUser = `Write the complete body content for this section (do NOT repeat the heading):\n${heading}`;
3400
+ sendToken('\n\n' + heading + '\n');
3401
+ let secContent = '';
3366
3402
  try {
3367
- await withTimeout(
3368
- callLLMStream(
3369
- fillConfig,
3370
- `You are ${agent}. You wrote a report and accidentally left one section empty. Fill in ONLY the missing section — do NOT repeat the heading, do NOT add other sections. Be specific, consistent with the rest of the report, and thorough. Write in ${language}.\n\nSource data:\n${dataForFill}\n\nOverall task: ${task}\n\nReport written so far (for context and style consistency):\n${reportSoFar}`,
3371
- `Write ONLY the body content for this section (heading already printed, do not repeat it):\n${emptyHeading}`,
3372
- (tok) => { filledContent += tok; sendToken(tok); },
3373
- { max_tokens: 1500, thinking: 'off' }
3374
- ),
3375
- 45000
3376
- );
3403
+ secContent = await streamCall(secSys, secUser, { max_tokens: 2000, thinking: 'off' }, 60000, sendToken);
3377
3404
  } catch {}
3378
- if (filledContent.trim()) {
3379
- const escapedH = emptyHeading.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
3380
- // Inject after heading when followed by another heading
3381
- fullOutputClean = fullOutputClean.replace(
3382
- new RegExp('(' + escapedH + ')\\n+(#{1,4}\\s)', 'g'),
3383
- '$1\n\n' + filledContent.trim() + '\n\n$2'
3384
- );
3385
- // Inject at end of document (trailing heading)
3386
- fullOutputClean = fullOutputClean.replace(
3387
- new RegExp('(' + escapedH + ')\\s*$', 'g'),
3388
- '$1\n\n' + filledContent.trim()
3389
- );
3405
+ if (secContent.trim()) {
3406
+ sectionParts.push(heading + '\n\n' + secContent.trim());
3407
+ fullOutput += '\n\n' + heading + '\n\n' + secContent.trim();
3390
3408
  }
3391
3409
  }
3392
- // Re-detect after this round — newly injected content may close all gaps
3393
- emptySections = getEmptySections(fullOutputClean);
3410
+ fullOutputClean = fullOutput.trim();
3394
3411
  }
3395
- // Strip any unfillable trailing headings
3396
- fullOutputClean = fullOutputClean.replace(/^(#{1,4}\s+[^\n]+)\n+(?=#{1,4}\s|$)/gm, '');
3412
+
3413
+ } else {
3414
+ // ── Live-data agents: single stream ───────────────────────
3415
+ sendToken('');
3416
+ try {
3417
+ fullOutput = await streamCall(sysPrompt, userMsg, { max_tokens: 8192, thinking_budget: 2048 }, 120000, sendToken);
3418
+ } catch (e) { sendToken(`[Error: ${e.message}]`); }
3419
+ fullOutputClean = stripThink(fullOutput);
3397
3420
  }
3398
- // Strip trailing lone headings at end of output (truly unfillable)
3421
+
3422
+ // Strip trailing lone headings at end of output (truly unfillable edge cases)
3399
3423
  fullOutputClean = fullOutputClean.replace(/(#{1,4}\s+[^\n]+)\s*$/g, '').trim();
3400
3424
 
3401
3425
  // Fallback: if LLM returned empty and we have tool data, send it directly
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.96';
8
+ export const VERSION = '13.2.97';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11