nothumanallowed 14.3.8 → 14.4.0
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": "14.
|
|
3
|
+
"version": "14.4.0",
|
|
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/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.
|
|
8
|
+
export const VERSION = '14.4.0';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
|
@@ -61,24 +61,81 @@ function interpolateObj(obj, ctx) {
|
|
|
61
61
|
async function executeNode(node, nodeDef, ctx, config) {
|
|
62
62
|
const cfg = interpolateObj(node.config ?? {}, ctx);
|
|
63
63
|
|
|
64
|
-
// AI nodes
|
|
64
|
+
// ── AI nodes ──
|
|
65
65
|
if (nodeDef.type === 'ai') {
|
|
66
|
+
if (node.defId === 'ai_code') {
|
|
67
|
+
// Code node — execute JS with `input` variable
|
|
68
|
+
try {
|
|
69
|
+
const fn = new Function('input', 'output', 'ctx', cfg.code || 'return input;');
|
|
70
|
+
const result = fn(ctx.output || '', ctx.output || '', ctx);
|
|
71
|
+
return String(result ?? '');
|
|
72
|
+
} catch (e) { throw new Error(`Code error: ${e.message}`); }
|
|
73
|
+
}
|
|
66
74
|
const prompt = cfg.prompt || `Process this: ${ctx.output || ''}`;
|
|
67
|
-
const
|
|
75
|
+
const agentName = cfg.agent || '';
|
|
76
|
+
const systemPrompt = agentName
|
|
77
|
+
? `You are ${agentName}, a specialist AI agent. Process the input and return a concise result.`
|
|
78
|
+
: 'You are a helpful AI assistant. Process the input and return a concise result.';
|
|
68
79
|
const result = await callLLM(config, systemPrompt, prompt);
|
|
69
80
|
return result?.content || result || '';
|
|
70
81
|
}
|
|
71
82
|
|
|
72
|
-
//
|
|
83
|
+
// ── Logic nodes ──
|
|
84
|
+
if (nodeDef.type === 'logic') {
|
|
85
|
+
if (node.defId === 'logic_if') {
|
|
86
|
+
try {
|
|
87
|
+
const fn = new Function('output', 'input', 'ctx', `return Boolean(${cfg.condition || 'false'});`);
|
|
88
|
+
const result = fn(ctx.output || '', ctx.output || '', ctx);
|
|
89
|
+
return JSON.stringify({ __branch: result ? 'true' : 'false', value: ctx.output || '' });
|
|
90
|
+
} catch (e) { throw new Error(`Condition error: ${e.message}`); }
|
|
91
|
+
}
|
|
92
|
+
if (node.defId === 'logic_switch') {
|
|
93
|
+
const expr = cfg.expression || ctx.output || '';
|
|
94
|
+
const cases = (cfg.cases || '').split(',').map((c) => c.trim());
|
|
95
|
+
const matched = cases.find((c) => expr.toLowerCase().includes(c.toLowerCase()));
|
|
96
|
+
return JSON.stringify({ __branch: matched || 'default', value: ctx.output || '' });
|
|
97
|
+
}
|
|
98
|
+
if (node.defId === 'logic_loop') {
|
|
99
|
+
const sep = cfg.separator === ',' ? ',' : '\n';
|
|
100
|
+
const items = (ctx.output || '').split(sep).filter(Boolean);
|
|
101
|
+
return JSON.stringify({ __loop: true, items, count: items.length });
|
|
102
|
+
}
|
|
103
|
+
if (node.defId === 'logic_merge') {
|
|
104
|
+
// Merge collects from ctx.__mergeInputs (set by the executor)
|
|
105
|
+
const inputs = ctx.__mergeInputs || [ctx.output || ''];
|
|
106
|
+
if (cfg.mode === 'json_array') return JSON.stringify(inputs);
|
|
107
|
+
if (cfg.mode === 'first_non_empty') return inputs.find((i) => i && i.trim()) || '';
|
|
108
|
+
return inputs.join('\n');
|
|
109
|
+
}
|
|
110
|
+
if (node.defId === 'logic_delay') {
|
|
111
|
+
const ms = Math.min(parseInt(cfg.seconds || '1') * 1000, 60_000);
|
|
112
|
+
await new Promise((r) => setTimeout(r, ms));
|
|
113
|
+
return ctx.output || '';
|
|
114
|
+
}
|
|
115
|
+
if (node.defId === 'logic_error') {
|
|
116
|
+
// Error handler wraps previous node execution — handled in runWorkflow
|
|
117
|
+
return ctx.output || cfg.fallback || '';
|
|
118
|
+
}
|
|
119
|
+
return ctx.output || '';
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ── Action nodes ──
|
|
73
123
|
const ACTION_MAP = {
|
|
74
|
-
action_email:
|
|
75
|
-
action_slack:
|
|
76
|
-
action_calendar:
|
|
77
|
-
action_task:
|
|
78
|
-
action_drive:
|
|
79
|
-
action_notion:
|
|
80
|
-
action_github:
|
|
81
|
-
action_webhook:
|
|
124
|
+
action_email: ['gmail_send', { to: cfg.to, subject: cfg.subject || 'NHA Workflow', body: cfg.body || ctx.output }],
|
|
125
|
+
action_slack: ['slack_message', { channel: cfg.channel || '#general', text: cfg.text || ctx.output }],
|
|
126
|
+
action_calendar: ['calendar_create', { title: cfg.title || ctx.output, date: cfg.date || new Date().toISOString().split('T')[0], time: cfg.time || '09:00', duration: cfg.duration || '60' }],
|
|
127
|
+
action_task: ['task_create', { title: cfg.title || ctx.output, priority: cfg.priority || 'medium' }],
|
|
128
|
+
action_drive: ['drive_upload', { name: cfg.name || 'workflow-output.txt', content: cfg.content || ctx.output }],
|
|
129
|
+
action_notion: ['notion_page', { title: cfg.title || 'Workflow Output', content: cfg.content || ctx.output }],
|
|
130
|
+
action_github: ['github_issue', { repo: cfg.repo, title: cfg.title || ctx.output, body: cfg.body || '' }],
|
|
131
|
+
action_webhook: ['fetch_url', { url: cfg.url, method: cfg.method || 'POST', body: cfg.body || ctx.output }],
|
|
132
|
+
action_browser: ['browser_open', { url: cfg.url || ctx.output }],
|
|
133
|
+
action_file_read: ['file_read', { path: cfg.path }],
|
|
134
|
+
action_file_write: ['file_write', { path: cfg.path, content: cfg.content || ctx.output }],
|
|
135
|
+
action_contact: ['contact_search', { query: cfg.query || ctx.output }],
|
|
136
|
+
action_screen: ['screen_capture', {}],
|
|
137
|
+
action_maps: ['maps_directions', { from: cfg.from, to: cfg.to }],
|
|
138
|
+
action_notify: ['notify_remind', { message: cfg.message || ctx.output, channel: cfg.channel || 'system' }],
|
|
82
139
|
};
|
|
83
140
|
|
|
84
141
|
const mapped = ACTION_MAP[node.defId];
|
|
@@ -91,7 +148,7 @@ async function executeNode(node, nodeDef, ctx, config) {
|
|
|
91
148
|
}
|
|
92
149
|
}
|
|
93
150
|
|
|
94
|
-
// Trigger nodes
|
|
151
|
+
// Trigger nodes
|
|
95
152
|
if (nodeDef.type === 'trigger') {
|
|
96
153
|
return ctx.output || cfg.input || '';
|
|
97
154
|
}
|
|
@@ -108,19 +165,25 @@ async function runWorkflow(wf, initialInput, config) {
|
|
|
108
165
|
const nodeMap = Object.fromEntries(wf.nodes.map((n) => [n.id, n]));
|
|
109
166
|
const defMap = Object.fromEntries((wf.nodeDefs || []).map((d) => [d.id, d]));
|
|
110
167
|
|
|
111
|
-
// Build adjacency: from → to
|
|
168
|
+
// Build adjacency: from → [{to, fromPort}]
|
|
112
169
|
const next = {};
|
|
113
170
|
for (const e of wf.edges ?? []) {
|
|
114
171
|
if (!next[e.from]) next[e.from] = [];
|
|
115
|
-
next[e.from].push(e.to);
|
|
172
|
+
next[e.from].push({ to: e.to, port: e.fromPort || 'default' });
|
|
116
173
|
}
|
|
117
174
|
|
|
118
|
-
//
|
|
175
|
+
// Check if next node is an error handler
|
|
176
|
+
const isErrorHandler = (nodeId) => {
|
|
177
|
+
const n = nodeMap[nodeId];
|
|
178
|
+
return n?.defId === 'logic_error';
|
|
179
|
+
};
|
|
180
|
+
|
|
181
|
+
// Find start nodes
|
|
119
182
|
const hasIncoming = new Set((wf.edges ?? []).map((e) => e.to));
|
|
120
183
|
const startCandidates = wf.nodes.filter((n) => !hasIncoming.has(n.id));
|
|
121
|
-
if (startCandidates.length === 0) return [{ nodeId: '__error', output: 'No start node found.' }];
|
|
184
|
+
if (startCandidates.length === 0) return [{ nodeId: '__error', nodeLabel: 'Error', nodeIcon: '❌', output: 'No start node found.' }];
|
|
122
185
|
|
|
123
|
-
// BFS execution
|
|
186
|
+
// BFS execution with branching support
|
|
124
187
|
const queue = startCandidates.map((n) => ({ nodeId: n.id, ctx: { output: initialInput || '', input: initialInput || '' } }));
|
|
125
188
|
const visited = new Set();
|
|
126
189
|
|
|
@@ -136,18 +199,75 @@ async function runWorkflow(wf, initialInput, config) {
|
|
|
136
199
|
|
|
137
200
|
let output = '';
|
|
138
201
|
let error = null;
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
202
|
+
|
|
203
|
+
// Error handler: wrap with retry
|
|
204
|
+
const maxRetries = node.defId === 'logic_error' ? parseInt(node.config?.retries || '0') : 0;
|
|
205
|
+
let attempt = 0;
|
|
206
|
+
while (attempt <= maxRetries) {
|
|
207
|
+
try {
|
|
208
|
+
output = await executeNode(node, nodeDef, ctx, config);
|
|
209
|
+
error = null;
|
|
210
|
+
break;
|
|
211
|
+
} catch (e) {
|
|
212
|
+
error = e.message;
|
|
213
|
+
output = node.config?.fallback || '';
|
|
214
|
+
attempt++;
|
|
215
|
+
if (attempt <= maxRetries) {
|
|
216
|
+
steps.push({ nodeId, nodeLabel: nodeDef.label, nodeIcon: '🔄', output: `Retry ${attempt}/${maxRetries}: ${e.message}`, error: null });
|
|
217
|
+
}
|
|
218
|
+
}
|
|
144
219
|
}
|
|
145
220
|
|
|
146
|
-
steps.push({ nodeId, nodeLabel: nodeDef.label, nodeIcon: nodeDef.icon, output, error });
|
|
221
|
+
steps.push({ nodeId, nodeLabel: nodeDef.label, nodeIcon: nodeDef.icon, output: output?.slice?.(0, 2000) || '', error });
|
|
222
|
+
|
|
223
|
+
// If error and no error handler downstream, stop this branch
|
|
224
|
+
if (error) {
|
|
225
|
+
const hasHandler = (next[nodeId] ?? []).some((n) => isErrorHandler(n.to));
|
|
226
|
+
if (!hasHandler) continue;
|
|
227
|
+
}
|
|
147
228
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
229
|
+
// Determine which downstream nodes to execute based on branching
|
|
230
|
+
const downstream = next[nodeId] ?? [];
|
|
231
|
+
let branch = null;
|
|
232
|
+
try {
|
|
233
|
+
const parsed = JSON.parse(output || '{}');
|
|
234
|
+
if (parsed.__branch) branch = parsed.__branch;
|
|
235
|
+
if (parsed.__loop && Array.isArray(parsed.items)) {
|
|
236
|
+
// Loop: execute downstream for each item
|
|
237
|
+
for (const item of parsed.items) {
|
|
238
|
+
const loopCtx = { ...ctx, output: item, loopItem: item };
|
|
239
|
+
for (const { to: toId } of downstream) {
|
|
240
|
+
// Don't mark as visited — allow loop iterations
|
|
241
|
+
const toNode = nodeMap[toId];
|
|
242
|
+
if (!toNode) continue;
|
|
243
|
+
const toDef = defMap[toNode.defId];
|
|
244
|
+
if (!toDef) continue;
|
|
245
|
+
try {
|
|
246
|
+
const loopOut = await executeNode(toNode, toDef, loopCtx, config);
|
|
247
|
+
steps.push({ nodeId: toId, nodeLabel: `${toDef.label} [${item.slice(0, 20)}]`, nodeIcon: toDef.icon, output: loopOut?.slice?.(0, 2000) || '', error: null });
|
|
248
|
+
} catch (e) {
|
|
249
|
+
steps.push({ nodeId: toId, nodeLabel: `${toDef.label} [${item.slice(0, 20)}]`, nodeIcon: toDef.icon, output: '', error: e.message });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
continue; // Loop handles its own downstream
|
|
254
|
+
}
|
|
255
|
+
} catch { /* not JSON — no branching */ }
|
|
256
|
+
|
|
257
|
+
const nextCtx = { ...ctx, output: output || '', [`${node.defId}_output`]: output || '' };
|
|
258
|
+
|
|
259
|
+
if (branch) {
|
|
260
|
+
// Branching: only follow edges that match the branch port
|
|
261
|
+
for (const { to: toId, port } of downstream) {
|
|
262
|
+
if (port === branch || port === 'default') {
|
|
263
|
+
queue.push({ nodeId: toId, ctx: nextCtx });
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
} else {
|
|
267
|
+
// Normal: follow all downstream edges
|
|
268
|
+
for (const { to: toId } of downstream) {
|
|
269
|
+
queue.push({ nodeId: toId, ctx: nextCtx });
|
|
270
|
+
}
|
|
151
271
|
}
|
|
152
272
|
}
|
|
153
273
|
|