nothumanallowed 14.2.6 → 14.2.8
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 +1 -1
- package/src/cli.mjs +23 -3
- package/src/constants.mjs +1 -1
- package/src/server/routes/webcraft.mjs +41 -18
- package/src/services/tool-executor.mjs +6 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "14.2.
|
|
3
|
+
"version": "14.2.8",
|
|
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/cli.mjs
CHANGED
|
@@ -200,14 +200,34 @@ export async function main(argv) {
|
|
|
200
200
|
return cmdHelp();
|
|
201
201
|
|
|
202
202
|
default: {
|
|
203
|
-
// Check if a plugin handles this command before falling through
|
|
203
|
+
// Check if a plugin handles this command before falling through
|
|
204
204
|
const pluginMatch = await findPluginForCommand(cmd);
|
|
205
205
|
if (pluginMatch && pluginMatch.plugin.run) {
|
|
206
206
|
const { cmdPlugin: runPlugin } = await import('./commands/plugin.mjs');
|
|
207
207
|
return runPlugin(['run', cmd, ...args]);
|
|
208
208
|
}
|
|
209
|
-
// Try as Legion command passthrough
|
|
210
|
-
|
|
209
|
+
// Try as Legion command passthrough (only if legion is installed)
|
|
210
|
+
try {
|
|
211
|
+
const { LEGION_FILE } = await import('./constants.mjs');
|
|
212
|
+
const { existsSync } = await import('fs');
|
|
213
|
+
if (existsSync(LEGION_FILE)) {
|
|
214
|
+
return spawnCore('legion', [cmd, ...args]);
|
|
215
|
+
}
|
|
216
|
+
} catch {}
|
|
217
|
+
// Unknown command — show helpful error
|
|
218
|
+
fail(`Unknown command: ${cmd}`);
|
|
219
|
+
console.log('');
|
|
220
|
+
info('Common commands:');
|
|
221
|
+
console.log(' nha chat Chat with AI');
|
|
222
|
+
console.log(' nha ui Open web UI');
|
|
223
|
+
console.log(' nha start Start ops daemon (Telegram/Discord)');
|
|
224
|
+
console.log(' nha stop Stop ops daemon');
|
|
225
|
+
console.log(' nha restart Restart ops daemon');
|
|
226
|
+
console.log(' nha status Check daemon status');
|
|
227
|
+
console.log(' nha update Update agents + npm package');
|
|
228
|
+
console.log(' nha config set <k> <v> Set configuration');
|
|
229
|
+
console.log(' nha help Full command list');
|
|
230
|
+
return;
|
|
211
231
|
}
|
|
212
232
|
}
|
|
213
233
|
}
|
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.2.
|
|
8
|
+
export const VERSION = '14.2.8';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -826,19 +826,6 @@ function countTokens(text) {
|
|
|
826
826
|
return Math.ceil((text || '').length / 4);
|
|
827
827
|
}
|
|
828
828
|
|
|
829
|
-
/**
|
|
830
|
-
* Simulated streaming: for providers that return full text at once (NHA/Liara),
|
|
831
|
-
* we split the text into ~20-char chunks and emit them with setImmediate gaps
|
|
832
|
-
* so the browser receives a real byte-by-byte stream over SSE.
|
|
833
|
-
*/
|
|
834
|
-
async function emitTextAsStream(text, onChunk) {
|
|
835
|
-
const CHUNK_SIZE = 20;
|
|
836
|
-
for (let i = 0; i < text.length; i += CHUNK_SIZE) {
|
|
837
|
-
onChunk(text.slice(i, i + CHUNK_SIZE));
|
|
838
|
-
await new Promise((r) => setImmediate(r));
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
|
|
842
829
|
/**
|
|
843
830
|
* Detects if an LLM output appears to be truncated / incomplete.
|
|
844
831
|
* Returns true if the file likely needs a continuation call.
|
|
@@ -932,7 +919,33 @@ function repairTruncation(content, filename) {
|
|
|
932
919
|
return result;
|
|
933
920
|
}
|
|
934
921
|
|
|
935
|
-
|
|
922
|
+
/** Severe truncation = needs LLM continuation. Minor diffs handled by repairTruncation(). */
|
|
923
|
+
function _isSeverelyTruncated(content, filename) {
|
|
924
|
+
if (!content || content.length < 10) return true;
|
|
925
|
+
const trimmed = content.trimEnd();
|
|
926
|
+
const ext = filename.split('.').pop()?.toLowerCase();
|
|
927
|
+
// Last line ends mid-expression (clear cut-off)
|
|
928
|
+
const lastLine = trimmed.split('\n').pop()?.trim() ?? '';
|
|
929
|
+
if (/[,({=+\-*/<>|&:]$/.test(lastLine) && lastLine.length > 3) return true;
|
|
930
|
+
// Large brace imbalance (>3) — repairTruncation can't reliably fix this
|
|
931
|
+
if (ext === 'js' || ext === 'mjs' || ext === 'ts') {
|
|
932
|
+
const diff = (trimmed.match(/\{/g) || []).length - (trimmed.match(/\}/g) || []).length;
|
|
933
|
+
if (diff > 3) return true;
|
|
934
|
+
}
|
|
935
|
+
if (ext === 'css') {
|
|
936
|
+
const diff = (trimmed.match(/\{/g) || []).length - (trimmed.match(/\}/g) || []).length;
|
|
937
|
+
if (diff > 3) return true;
|
|
938
|
+
}
|
|
939
|
+
// HTML completely missing closing tags (not just </html> — entire sections missing)
|
|
940
|
+
if (ext === 'html' && !trimmed.includes('</body>') && !trimmed.includes('</html>') && trimmed.length > 500) {
|
|
941
|
+
// Check if it ends mid-tag
|
|
942
|
+
const lastAngle = trimmed.lastIndexOf('<');
|
|
943
|
+
if (lastAngle > trimmed.lastIndexOf('>')) return true; // mid-tag
|
|
944
|
+
}
|
|
945
|
+
return false;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
async function runGenerate(config, projectName, description, blocks, authFields, emit, abortSignal) {
|
|
936
949
|
const blocksDesc = Object.entries(blocks)
|
|
937
950
|
.filter(([, enabled]) => enabled)
|
|
938
951
|
.map(([key]) => key)
|
|
@@ -1079,6 +1092,12 @@ ${prevContext ? `Recent files generated (for consistency):\n${prevContext}\n\n`
|
|
|
1079
1092
|
let syntaxError = null;
|
|
1080
1093
|
const fileTokensIn = countTokens(fileSys) + countTokens(filePrompt);
|
|
1081
1094
|
|
|
1095
|
+
// Check abort before each file
|
|
1096
|
+
if (abortSignal?.aborted) {
|
|
1097
|
+
emit({ type: 'status', msg: 'Generation stopped by user.' });
|
|
1098
|
+
break;
|
|
1099
|
+
}
|
|
1100
|
+
|
|
1082
1101
|
// Retry loop — up to 2 attempts per file
|
|
1083
1102
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1084
1103
|
try {
|
|
@@ -1095,8 +1114,8 @@ ${prevContext ? `Recent files generated (for consistency):\n${prevContext}\n\n`
|
|
|
1095
1114
|
.replace(/^```[\w]*\n/, '').replace(/\n```$/, '')
|
|
1096
1115
|
.replace(/^```[\w]*\r\n/, '').replace(/\r\n```$/, '').trim();
|
|
1097
1116
|
|
|
1098
|
-
// Continuation loop —
|
|
1099
|
-
for (let contRound = 0; contRound <
|
|
1117
|
+
// Continuation loop — only for SEVERE truncation (>3 missing braces or mid-line cut)
|
|
1118
|
+
for (let contRound = 0; contRound < 2 && !abortSignal?.aborted && _isSeverelyTruncated(rawOutput, fileSpec.name); contRound++) {
|
|
1100
1119
|
emit({ type: 'file_chunk', name: fileSpec.name, chunk: `\n/* ... continuing (${contRound + 1}) ... */\n`, fi: fi + 1, total: filePlan.length });
|
|
1101
1120
|
const contPrompt = `The file ${fileSpec.name} was truncated. Continue EXACTLY from the last line. Output ONLY the remaining code (no repetition, no explanation):
|
|
1102
1121
|
|
|
@@ -1325,10 +1344,14 @@ export function register(router) {
|
|
|
1325
1344
|
if (!projectName || !description) return sendError(res, 400, 'projectName and description required');
|
|
1326
1345
|
|
|
1327
1346
|
const sse = sendSSE(res);
|
|
1347
|
+
// Abort signal: triggered when client disconnects (user clicks Stop)
|
|
1348
|
+
const ac = new AbortController();
|
|
1349
|
+
req.on('close', () => ac.abort());
|
|
1350
|
+
res.on('close', () => ac.abort());
|
|
1328
1351
|
try {
|
|
1329
|
-
await runGenerate(config, projectName, description, blocks, authFields, sse.send);
|
|
1352
|
+
await runGenerate(config, projectName, description, blocks, authFields, sse.send, ac.signal);
|
|
1330
1353
|
} catch (e) {
|
|
1331
|
-
sse.send({ type: 'error', msg: e.message });
|
|
1354
|
+
if (e.name !== 'AbortError') sse.send({ type: 'error', msg: e.message });
|
|
1332
1355
|
}
|
|
1333
1356
|
sse.end();
|
|
1334
1357
|
});
|
|
@@ -161,8 +161,10 @@ TOOLS:
|
|
|
161
161
|
NEVER use calendar_find with month names — use calendar_month instead.
|
|
162
162
|
|
|
163
163
|
14. calendar_create(summary: string, start: string, end: string, attendees?: string[], description?: string)
|
|
164
|
-
Create a calendar event. start/end are ISO 8601 datetime strings.
|
|
165
|
-
|
|
164
|
+
Create a NEW calendar event. start/end are ISO 8601 datetime strings.
|
|
165
|
+
Use this when the user says: "inserisci", "aggiungi", "crea", "metti", "fissa", "prenota", "add", "create", "schedule", "book".
|
|
166
|
+
IMPORTANT: When user says "inserisci appuntamento" or "crea evento" → use calendar_create, NOT calendar_find.
|
|
167
|
+
Extract the summary, date, and time from the user message. If end time is not specified, default to 1 hour after start.
|
|
166
168
|
|
|
167
169
|
15. calendar_move(eventId: string, newStart: string, newEnd: string)
|
|
168
170
|
Reschedule an event. ALWAYS confirm before moving.
|
|
@@ -625,7 +627,8 @@ RULES:
|
|
|
625
627
|
- For write/send/delete operations (gmail_send, gmail_reply, gmail_delete, calendar_create, calendar_move, calendar_update, contact_delete, task_done, notify_remind, file_write), DESCRIBE what you're about to do and include the JSON block so the system can ask the user for confirmation.
|
|
626
628
|
- For schedule_meeting and schedule_draft_email, execute immediately — these are read operations that suggest slots.
|
|
627
629
|
- When presenting email results, show From, Subject, Date, and a brief snippet. Never dump raw JSON.
|
|
628
|
-
- When presenting calendar events, show Time, Title, Location/Link. Format times in a human-readable way.
|
|
630
|
+
- When presenting calendar events, show Time, Title, Location/Link. Format times in a human-readable way. NEVER show raw eventId to the user — it's internal.
|
|
631
|
+
- When confirming a created event, say something like "Ho creato l'appuntamento 'X' per il giorno Y alle ore Z." — natural, human, no IDs.
|
|
629
632
|
- When presenting tasks, show ID, Description, Priority, Status.
|
|
630
633
|
- When presenting slot proposals, show day, date, time range, and travel info clearly.
|
|
631
634
|
- If you need multiple actions in sequence (e.g., read an email then reply), do them ONE AT A TIME — wait for the result of each before proceeding.
|