nothumanallowed 13.2.80 → 13.2.82

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.80",
3
+ "version": "13.2.82",
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": {
@@ -2692,7 +2692,7 @@ export async function cmdUI(args) {
2692
2692
 
2693
2693
  // ── Fast keyword-based planning (no LLM call needed for common patterns) ──────
2694
2694
  const taskLow = task.toLowerCase();
2695
- const hasPdf = !!(body.hasPdf) || /pdf|allegat|catalogo|scheda\s*tecnic|document/i.test(taskLow);
2695
+ const hasPdf = !!(body.hasPdf) || /pdf|allegat|catalogo|scheda\s*tecnic/i.test(taskLow);
2696
2696
  const hasEmail = /email|mail|inbox|posta/i.test(taskLow);
2697
2697
  const hasCalendar = /calendar|agenda|calendari|eventi|schedule/i.test(taskLow);
2698
2698
  const hasSearch = /cerca|search|notizie|news|ultime|latest|web|internet|tendenz|trend|acquista|compra|dove\s+trovare|where\s+to\s+buy|similar|simile/i.test(taskLow);
@@ -2958,12 +2958,30 @@ ${rawText.slice(0, 18000)}`;
2958
2958
  // If there is document context from a previous step, ask the LLM to derive
2959
2959
  // the optimal search queries. This is generic and works for any document/task.
2960
2960
  let searchQueries = [stepPrompt.slice(0, 120)];
2961
- if (context && context.length > 50) {
2961
+ // Only use LLM query generation when context is a PDF/document (not previous agent text output).
2962
+ // When context is email/github output from a prior step, ignore it — use task + stepPrompt directly.
2963
+ const contextIsPdf = context && context.length > 50 && context.startsWith('## ATTACHED PDF');
2964
+ if (contextIsPdf) {
2962
2965
  sendToken('[Building search queries from document...] ');
2963
2966
  try {
2964
- // Document context goes in system prompt — SENTINEL only scans user message
2965
2967
  const queryPlanSys = `You are a search query generator. Given a document summary and a user task, output a JSON array of 1-3 concise web search queries (strings, max 80 chars each) that will find the best results. Output ONLY the JSON array, no explanation.\n\nDocument content:\n${context.slice(0, 3000)}`;
2966
- const queryPlanUser = `User task: "${task.slice(0, 200)}". Generate search queries. If task asks for similar/alternative products use technical specs. If it asks where to buy include vendor queries. Output: ["query1","query2",...]`;
2968
+ const queryPlanUser = `User task: "${task.slice(0, 200)}". Generate search queries. If task asks for similar/alternative products use technical specs. Output: ["query1","query2",...]`;
2969
+ const planConfig2 = Object.assign({}, config, { thinking: 'off' });
2970
+ const queryRaw = await withTimeout(callLLM(planConfig2, queryPlanSys, queryPlanUser, { max_tokens: 200 }), 15000);
2971
+ const jsonMatch = queryRaw.match(/\[[\s\S]*?\]/);
2972
+ if (jsonMatch) {
2973
+ const parsed = JSON.parse(jsonMatch[0]);
2974
+ if (Array.isArray(parsed) && parsed.length > 0) {
2975
+ searchQueries = parsed.filter(q => typeof q === 'string' && q.length > 2).slice(0, 3);
2976
+ }
2977
+ }
2978
+ } catch {}
2979
+ } else {
2980
+ // No PDF — derive queries from task + stepPrompt using LLM for better queries
2981
+ sendToken('[Building search queries...] ');
2982
+ try {
2983
+ const queryPlanSys = `You are a search query generator. Given a user task and a search instruction, output a JSON array of 2-3 concise web search queries (strings, max 80 chars each). Focus on the specific topics in the task. Output ONLY the JSON array, no explanation.`;
2984
+ const queryPlanUser = `Task: "${task.slice(0, 300)}"\nSearch instruction: "${stepPrompt.slice(0, 200)}"\nOutput: ["query1","query2","query3"]`;
2967
2985
  const planConfig2 = Object.assign({}, config, { thinking: 'off' });
2968
2986
  const queryRaw = await withTimeout(callLLM(planConfig2, queryPlanSys, queryPlanUser, { max_tokens: 200 }), 15000);
2969
2987
  const jsonMatch = queryRaw.match(/\[[\s\S]*?\]/);
@@ -2974,8 +2992,8 @@ ${rawText.slice(0, 18000)}`;
2974
2992
  }
2975
2993
  }
2976
2994
  } catch {}
2977
- sendToken(`[Queries: ${searchQueries.map(q => '"' + q + '"').join(', ')}] `);
2978
2995
  }
2996
+ sendToken(`[Queries: ${searchQueries.map(q => '"' + q + '"').join(', ')}] `);
2979
2997
 
2980
2998
  // Run all queries sequentially, accumulate results
2981
2999
  for (let qi = 0; qi < searchQueries.length; qi++) {
@@ -3012,24 +3030,64 @@ ${rawText.slice(0, 18000)}`;
3012
3030
  toolData = 'GitHub token not configured. Run: nha config set github-token YOUR_PAT';
3013
3031
  } else {
3014
3032
  const parts = [];
3015
- // Notifications (always available)
3016
- try {
3017
- const notifs = await withTimeout(gh.listNotifications(config, 15), 'GitHubAgent-notifs');
3018
- if (notifs) parts.push('## GitHub Notifications\n' + notifs);
3019
- } catch (e) { /* skip */ }
3020
- // Issues/PRs on configured repo if available
3021
- const repo = config.github?.defaultRepo || '';
3022
- if (repo) {
3033
+ // Extract repo from prompt or task (e.g. "owner/repo" pattern)
3034
+ const repoMatch = (stepPrompt + ' ' + task).match(/([a-zA-Z0-9_.-]+\/[a-zA-Z0-9_.-]+)/);
3035
+ const targetRepo = repoMatch ? repoMatch[1].replace(/[`'"]/g, '') : (config.github?.defaultRepo || '');
3036
+
3037
+ if (targetRepo) {
3038
+ sendToken(`[Analyzing ${targetRepo}...] `);
3039
+ // Repo metadata
3040
+ try {
3041
+ const info = await withTimeout(gh.getRepoInfo(config, targetRepo), 20000);
3042
+ parts.push(`## Repository: ${info.full_name}\n` +
3043
+ `- Description: ${info.description || 'none'}\n` +
3044
+ `- Stars: ${info.stars} | Forks: ${info.forks} | Watchers: ${info.watchers}\n` +
3045
+ `- Open issues: ${info.open_issues}\n` +
3046
+ `- Primary language: ${info.language}\n` +
3047
+ `- Topics: ${info.topics}\n` +
3048
+ `- License: ${info.license}\n` +
3049
+ `- Last push: ${info.pushed_at} | Created: ${info.created_at}\n` +
3050
+ `- Homepage: ${info.homepage || 'none'}\n` +
3051
+ `- Archived: ${info.archived}`);
3052
+ } catch (e) { parts.push(`## Repository ${targetRepo}\nCould not fetch repo info: ${e.message}`); }
3053
+ // Languages
3054
+ try {
3055
+ const langs = await withTimeout(gh.getRepoLanguages(config, targetRepo), 10000);
3056
+ if (langs) parts.push('## Languages\n' + langs);
3057
+ } catch {}
3058
+ // README
3059
+ try {
3060
+ const readme = await withTimeout(gh.getReadme(config, targetRepo), 15000);
3061
+ if (readme) parts.push('## README\n' + readme.slice(0, 3000));
3062
+ } catch {}
3063
+ // Recent commits
3023
3064
  try {
3024
- const issues = await withTimeout(gh.listIssues(config, repo, 'open', 10), 'GitHubAgent-issues');
3025
- if (issues) parts.push('## Open Issues (' + repo + ')\n' + issues);
3026
- } catch (e) { /* skip */ }
3065
+ const commits = await withTimeout(gh.getRecentCommits(config, targetRepo, 10), 15000);
3066
+ if (commits) parts.push('## Recent Commits\n' + commits);
3067
+ } catch {}
3068
+ // Open issues
3027
3069
  try {
3028
- const prs = await withTimeout(gh.listPRs(config, repo, 'open', 10), 'GitHubAgent-prs');
3029
- if (prs) parts.push('## Open PRs (' + repo + ')\n' + prs);
3030
- } catch (e) { /* skip */ }
3070
+ const issues = await withTimeout(gh.listIssues(config, targetRepo, 'open', 10), 15000);
3071
+ if (issues) parts.push('## Open Issues\n' + issues);
3072
+ } catch {}
3073
+ // Open PRs
3074
+ try {
3075
+ const prs = await withTimeout(gh.listPRs(config, targetRepo, 'open', 10), 15000);
3076
+ if (prs) parts.push('## Open Pull Requests\n' + prs);
3077
+ } catch {}
3078
+ // Contributors
3079
+ try {
3080
+ const contributors = await withTimeout(gh.getContributors(config, targetRepo, 10), 10000);
3081
+ if (contributors) parts.push('## Contributors\n' + contributors);
3082
+ } catch {}
3083
+ } else {
3084
+ // No specific repo — read notifications + user repos
3085
+ try {
3086
+ const notifs = await withTimeout(gh.listNotifications(config, 15), 15000);
3087
+ if (notifs) parts.push('## GitHub Notifications\n' + notifs);
3088
+ } catch {}
3031
3089
  }
3032
- toolData = parts.length > 0 ? parts.join('\n\n') : 'No GitHub data available.';
3090
+ toolData = parts.length > 0 ? parts.join('\n\n') : 'No GitHub data could be retrieved.';
3033
3091
  }
3034
3092
  } catch (e) { toolData = `GitHub read failed: ${e.message}`; }
3035
3093
 
@@ -3154,6 +3212,11 @@ RULES:
3154
3212
  userMsg = `Create a professional dashboard report for this data. Output ONLY the inner HTML body content (starting with <div class="header">):\n\n${canvasData}`;
3155
3213
  } else if (isLiveDataAgent) {
3156
3214
  // These agents fetched real data — use a focused prompt (no tool definitions to avoid JSON output)
3215
+ // Live data agents that fetched their own data: do NOT inject previous context
3216
+ // (prevents EmailAgent output from being repeated by GitHubAgent, CalendarAgent, etc.)
3217
+ const contextBlock = toolData
3218
+ ? '' // Has own live data — ignore previous agent outputs to avoid repetition
3219
+ : (context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : '');
3157
3220
  const agentInstruction = `You are ${agent}, a specialist AI agent inside NHA Studio. Today is ${today}. Respond entirely in ${language}.
3158
3221
 
3159
3222
  ## OVERALL WORKFLOW GOAL:
@@ -3164,8 +3227,7 @@ Do NOT output JSON, tool calls, or code blocks. Write in plain text with markdow
3164
3227
  Always apply your analysis specifically to the subject mentioned in the WORKFLOW GOAL.
3165
3228
 
3166
3229
  ${attachmentText ? `## ATTACHED FILE CONTENT:\n${attachmentText}\n` : ''}${toolData ? `## DATA FROM TOOLS:\n${toolData}\n` : '## DATA: No data was retrieved by this agent.\n'}
3167
- ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}
3168
-
3230
+ ${contextBlock}
3169
3231
  Your task: ${stepPrompt}`;
3170
3232
  sysPrompt = agentInstruction;
3171
3233
  userMsg = toolData
@@ -3184,14 +3246,15 @@ ${task}
3184
3246
  CRITICAL RULES:
3185
3247
  - Do NOT output JSON, tool calls, function calls, or code blocks
3186
3248
  - NEVER invent, fabricate, or hallucinate data, events, emails, meetings, or news
3187
- - ONLY use the EXACT data provided in the DATA sections below if no data is provided, say so clearly
3188
- - Do NOT add fictional examples, placeholder content, or generic suggestions not grounded in the data
3249
+ - ONLY use data from the DATA sections that is RELEVANT to your specific domain and the WORKFLOW GOAL
3250
+ - If the previous agents' output contains irrelevant personal data (e.g. unrelated emails, purchases, subscriptions) — IGNORE it entirely
3251
+ - ONLY reference data that directly relates to the subject of the WORKFLOW GOAL
3252
+ - If genuinely no relevant data exists for your domain, say so clearly — do NOT invent analysis
3189
3253
  - Write in plain prose, structured with markdown headers (##) and bullet points (-)
3190
3254
  - Be thorough and specific — this is for an executive briefing based on REAL data only
3191
- - Always keep the OVERALL WORKFLOW GOAL in mind — apply your analysis specifically to the subject mentioned
3192
3255
 
3193
3256
  ${attachmentText ? `## ATTACHED FILE CONTENT:\n${attachmentText}\n` : ''}${toolData ? `## LIVE DATA FROM TOOLS:\n${toolData}\n` : '## LIVE DATA: No tool data was fetched for this step.\n'}
3194
- ${context ? `## OUTPUT FROM PREVIOUS AGENTS:\n${context}\n` : ''}`;
3257
+ ${context ? `## OUTPUT FROM PREVIOUS AGENTS (use only what is RELEVANT to the workflow goal):\n${context}\n` : ''}`;
3195
3258
  userMsg = hasRealData
3196
3259
  ? `Based ONLY on the real data above, complete this task specifically for the subject in the WORKFLOW GOAL: ${stepPrompt}`
3197
3260
  : `No real data is available for "${task}". State this clearly and explain what data would be needed to complete: ${stepPrompt}`;
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.80';
8
+ export const VERSION = '13.2.82';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -185,6 +185,85 @@ export async function listUserRepos(config, maxResults = 30) {
185
185
  };
186
186
  }
187
187
 
188
+ /**
189
+ * Get repository metadata: description, stars, forks, language, topics, license, last push.
190
+ */
191
+ export async function getRepoInfo(config, repo) {
192
+ const data = await ghFetch(config, `/repos/${repo}`);
193
+ return {
194
+ full_name: data.full_name,
195
+ description: data.description || '',
196
+ stars: data.stargazers_count || 0,
197
+ forks: data.forks_count || 0,
198
+ watchers: data.watchers_count || 0,
199
+ open_issues: data.open_issues_count || 0,
200
+ language: data.language || 'N/A',
201
+ topics: (data.topics || []).join(', ') || 'none',
202
+ license: data.license?.name || 'none',
203
+ default_branch: data.default_branch || 'main',
204
+ pushed_at: data.pushed_at ? data.pushed_at.slice(0, 10) : 'unknown',
205
+ created_at: data.created_at ? data.created_at.slice(0, 10) : 'unknown',
206
+ homepage: data.homepage || '',
207
+ private: data.private,
208
+ archived: data.archived,
209
+ };
210
+ }
211
+
212
+ /**
213
+ * Get programming languages breakdown for a repo.
214
+ */
215
+ export async function getRepoLanguages(config, repo) {
216
+ const data = await ghFetch(config, `/repos/${repo}/languages`);
217
+ const total = Object.values(data).reduce((a, b) => a + b, 0) || 1;
218
+ return Object.entries(data)
219
+ .map(([lang, bytes]) => `${lang}: ${((bytes / total) * 100).toFixed(1)}%`)
220
+ .join(', ');
221
+ }
222
+
223
+ /**
224
+ * Get recent commits for a repo (last N).
225
+ */
226
+ export async function getRecentCommits(config, repo, maxResults = 10) {
227
+ const data = await ghFetch(config, `/repos/${repo}/commits?per_page=${maxResults}`);
228
+ if (!Array.isArray(data) || data.length === 0) return `No commits found in ${repo}.`;
229
+ return data.map((c, i) => {
230
+ const sha = c.sha ? c.sha.slice(0, 7) : '?';
231
+ const msg = (c.commit?.message || '').split('\n')[0].slice(0, 100);
232
+ const author = c.commit?.author?.name || c.author?.login || 'unknown';
233
+ const date = c.commit?.author?.date ? c.commit.author.date.slice(0, 10) : '';
234
+ return `${i + 1}. [${sha}] ${date} — ${author}: ${msg}`;
235
+ }).join('\n');
236
+ }
237
+
238
+ /**
239
+ * Get README content for a repo.
240
+ */
241
+ export async function getReadme(config, repo) {
242
+ try {
243
+ const data = await ghFetch(config, `/repos/${repo}/readme`);
244
+ if (data.content) {
245
+ const text = Buffer.from(data.content, 'base64').toString('utf-8');
246
+ return text.slice(0, 6000); // cap at 6KB
247
+ }
248
+ return '';
249
+ } catch {
250
+ return '';
251
+ }
252
+ }
253
+
254
+ /**
255
+ * Get contributors for a repo.
256
+ */
257
+ export async function getContributors(config, repo, maxResults = 10) {
258
+ try {
259
+ const data = await ghFetch(config, `/repos/${repo}/contributors?per_page=${maxResults}`);
260
+ if (!Array.isArray(data) || data.length === 0) return 'No contributors data.';
261
+ return data.map((c, i) => `${i + 1}. @${c.login} — ${c.contributions} commits`).join('\n');
262
+ } catch {
263
+ return 'Contributors data unavailable.';
264
+ }
265
+ }
266
+
188
267
  export async function markNotificationsRead(config) {
189
268
  await ghFetch(config, '/notifications', {
190
269
  method: 'PUT',
@@ -3660,7 +3660,7 @@ function renderStudioResult() {
3660
3660
  var tokLine = (studioTokens && (studioTokens.in > 0 || studioTokens.out > 0))
3661
3661
  ? '<div style="margin-top:8px;font-size:11px;color:var(--dim);font-family:var(--mono)">&#x2B06; ' + (studioTokens.in||0).toLocaleString() + ' token in &nbsp;&#x2B07; ' + (studioTokens.out||0).toLocaleString() + ' token out &nbsp;&#x2022;&nbsp; <strong style="color:var(--green)">' + ((studioTokens.in||0)+(studioTokens.out||0)).toLocaleString() + '</strong> totale</div>'
3662
3662
  : '';
3663
- var dlBtn = '<div style="margin-top:12px;display:flex;align-items:center;gap:10px;flex-wrap:wrap">' +
3663
+ var dlBtn = '<div style="margin-top:14px;padding-top:12px;border-top:1px solid var(--border);display:flex;align-items:center;gap:10px;flex-wrap:wrap">' +
3664
3664
  '<button onclick="downloadStudioPDF()" title="Scarica il workflow come PDF" style="display:inline-flex;align-items:center;gap:6px;padding:8px 18px;background:linear-gradient(135deg,#4f46e5,#2563eb);border:none;border-radius:8px;color:#fff;font-size:12px;font-weight:600;cursor:pointer;letter-spacing:.3px;box-shadow:0 2px 8px rgba(79,70,229,.35)">&#x2913; Download PDF</button>' +
3665
3665
  '<span style="font-size:11px;color:var(--dim)">Scarica il workflow completo come documento PDF</span>' +
3666
3666
  '</div>';
@@ -3805,8 +3805,15 @@ async function runStudio() {
3805
3805
  var parliamentActive = studioState.parliamentMode || (parliamentChk && parliamentChk.checked);
3806
3806
  if (parliamentActive && studioState.nodes.length >= 1) {
3807
3807
  var proposals = studioState.nodes
3808
- .filter(function(n) { return n.output && n.output !== \x27(no output)\x27 && n.agent !== \x27CanvasAgent\x27; })
3809
- .map(function(n) { return {agent: n.agent, label: n.label, output: n.output}; });
3808
+ .filter(function(n) {
3809
+ if (!n.output || n.output === \x27(no output)\x27) return false;
3810
+ if (n.agent === \x27CanvasAgent\x27 || n.agent === \x27DocumentReaderAgent\x27) return false;
3811
+ if (n.status === \x27error\x27) return false;
3812
+ // Exclude nodes whose output is a short error message (< 80 chars containing "error"/"could not")
3813
+ if (n.output.length < 120 && /error|could not|fallito|errore/i.test(n.output)) return false;
3814
+ return true;
3815
+ })
3816
+ .map(function(n) { return {agent: n.agent, label: n.label, icon: n.icon, output: n.output}; });
3810
3817
  // Need at least 2 proposals for cross-reading; if only 1, include the full context as a second proposal
3811
3818
  if (proposals.length === 1 && context) {
3812
3819
  proposals.push({agent: \x27Context\x27, label: \x27Contesto workflow\x27, output: context});