nothumanallowed 9.1.0 → 9.2.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 +1 -1
- package/src/cli.mjs +8 -1
- package/src/commands/chat.mjs +111 -4
- package/src/commands/ui.mjs +215 -3
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +110 -26
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.2.0",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents + 50 tools + web search. Streaming chat, multi-conversation, export. Gmail, Calendar, Drive, GitHub, Notion, Slack. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli.mjs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import fs from 'fs';
|
|
4
4
|
import path from 'path';
|
|
5
|
-
import { VERSION, NHA_DIR, AGENTS_DIR, EXTENSIONS_DIR, AGENTS, EXTENSIONS, BASE_URL } from './constants.mjs';
|
|
5
|
+
import { VERSION, NHA_DIR, AGENTS_DIR, EXTENSIONS_DIR, AGENTS, EXTENSIONS, BASE_URL, API_BASE } from './constants.mjs';
|
|
6
6
|
import { needsBootstrap, bootstrap } from './bootstrap.mjs';
|
|
7
7
|
import { spawnCore } from './spawn.mjs';
|
|
8
8
|
import { loadConfig, setConfigValue } from './config.mjs';
|
|
@@ -42,6 +42,13 @@ export async function main(argv) {
|
|
|
42
42
|
}
|
|
43
43
|
}).catch(() => {});
|
|
44
44
|
|
|
45
|
+
// Anonymous usage ping — fire-and-forget, no user data
|
|
46
|
+
fetch(`${API_BASE}/telemetry/ping`, {
|
|
47
|
+
method: 'POST',
|
|
48
|
+
headers: { 'Content-Type': 'application/json' },
|
|
49
|
+
body: JSON.stringify({ platform: 'npm-cli', version: VERSION, command: cmd }),
|
|
50
|
+
}).catch(() => {});
|
|
51
|
+
|
|
45
52
|
// npm version check (non-blocking)
|
|
46
53
|
checkNpmVersion().then(result => {
|
|
47
54
|
if (result?.updateAvailable) {
|
package/src/commands/chat.mjs
CHANGED
|
@@ -422,6 +422,107 @@ async function handleSlashCommand(input, config, conv, rl) {
|
|
|
422
422
|
return { handled: false };
|
|
423
423
|
}
|
|
424
424
|
|
|
425
|
+
// ── Tool Indicators ──────────────────────────────────────────────────────────
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Format a user-visible label while a tool is executing.
|
|
429
|
+
*/
|
|
430
|
+
function formatToolLabel(action, params) {
|
|
431
|
+
switch (action) {
|
|
432
|
+
case 'web_search':
|
|
433
|
+
return `Searching the web for "${params.query || '...'}"...`;
|
|
434
|
+
case 'fetch_url':
|
|
435
|
+
return `Fetching ${params.url || 'URL'}...`;
|
|
436
|
+
case 'gmail_list':
|
|
437
|
+
return `Searching emails...`;
|
|
438
|
+
case 'gmail_read':
|
|
439
|
+
return `Reading email...`;
|
|
440
|
+
case 'gmail_send':
|
|
441
|
+
case 'gmail_send_attach':
|
|
442
|
+
return `Sending email to ${params.to || '...'}...`;
|
|
443
|
+
case 'gmail_reply':
|
|
444
|
+
return `Sending reply...`;
|
|
445
|
+
case 'calendar_create':
|
|
446
|
+
return `Creating event "${params.summary || '...'}"...`;
|
|
447
|
+
case 'calendar_today':
|
|
448
|
+
case 'calendar_tomorrow':
|
|
449
|
+
case 'calendar_upcoming':
|
|
450
|
+
case 'calendar_week':
|
|
451
|
+
return `Loading calendar...`;
|
|
452
|
+
case 'github_issues':
|
|
453
|
+
case 'github_prs':
|
|
454
|
+
return `Fetching from GitHub...`;
|
|
455
|
+
case 'notion_search':
|
|
456
|
+
return `Searching Notion...`;
|
|
457
|
+
case 'slack_messages':
|
|
458
|
+
case 'slack_channels':
|
|
459
|
+
return `Loading Slack...`;
|
|
460
|
+
default:
|
|
461
|
+
return `Executing ${action}...`;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Format a result header with visual indicator based on tool type.
|
|
467
|
+
*/
|
|
468
|
+
function formatToolResult(action, params, result) {
|
|
469
|
+
switch (action) {
|
|
470
|
+
case 'web_search': {
|
|
471
|
+
const count = (result.match(/\d+\. /g) || []).length;
|
|
472
|
+
const deep = params.deep ? ', deep mode' : '';
|
|
473
|
+
return `${C}[Web Search: ${count} results${deep}]${NC}`;
|
|
474
|
+
}
|
|
475
|
+
case 'fetch_url': {
|
|
476
|
+
const domain = (params.url || '').replace(/^https?:\/\//, '').split('/')[0];
|
|
477
|
+
return `${C}[Fetched: ${domain}]${NC}`;
|
|
478
|
+
}
|
|
479
|
+
case 'gmail_list':
|
|
480
|
+
case 'gmail_read':
|
|
481
|
+
case 'gmail_send':
|
|
482
|
+
case 'gmail_send_attach':
|
|
483
|
+
case 'gmail_reply':
|
|
484
|
+
case 'gmail_draft':
|
|
485
|
+
case 'gmail_mark_read':
|
|
486
|
+
case 'gmail_mark_unread':
|
|
487
|
+
case 'gmail_archive':
|
|
488
|
+
case 'gmail_delete':
|
|
489
|
+
return `${G}[Email]${NC}`;
|
|
490
|
+
case 'calendar_today':
|
|
491
|
+
case 'calendar_tomorrow':
|
|
492
|
+
case 'calendar_upcoming':
|
|
493
|
+
case 'calendar_week':
|
|
494
|
+
case 'calendar_create':
|
|
495
|
+
case 'calendar_move':
|
|
496
|
+
case 'calendar_find':
|
|
497
|
+
case 'calendar_update':
|
|
498
|
+
case 'schedule_meeting':
|
|
499
|
+
case 'schedule_draft_email':
|
|
500
|
+
return `${G}[Calendar]${NC}`;
|
|
501
|
+
case 'task_list':
|
|
502
|
+
case 'task_add':
|
|
503
|
+
case 'task_done':
|
|
504
|
+
case 'task_move':
|
|
505
|
+
case 'task_delete':
|
|
506
|
+
case 'task_clear':
|
|
507
|
+
case 'task_edit':
|
|
508
|
+
return `${G}[Tasks]${NC}`;
|
|
509
|
+
case 'github_issues':
|
|
510
|
+
case 'github_prs':
|
|
511
|
+
case 'github_notifications':
|
|
512
|
+
case 'github_create_issue':
|
|
513
|
+
return `${G}[GitHub]${NC}`;
|
|
514
|
+
case 'notion_search':
|
|
515
|
+
case 'notion_page':
|
|
516
|
+
return `${G}[Notion]${NC}`;
|
|
517
|
+
case 'slack_channels':
|
|
518
|
+
case 'slack_messages':
|
|
519
|
+
case 'slack_send':
|
|
520
|
+
return `${G}[Slack]${NC}`;
|
|
521
|
+
default:
|
|
522
|
+
return `${G}[${action}]${NC}`;
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
425
526
|
// ── Main REPL ────────────────────────────────────────────────────────────────
|
|
426
527
|
|
|
427
528
|
export async function cmdChat(args) {
|
|
@@ -569,14 +670,20 @@ export async function cmdChat(args) {
|
|
|
569
670
|
}
|
|
570
671
|
|
|
571
672
|
try {
|
|
572
|
-
|
|
673
|
+
// Show action-specific indicator
|
|
674
|
+
const toolLabel = formatToolLabel(action, params);
|
|
675
|
+
process.stdout.write(` ${D}${toolLabel}${NC}`);
|
|
573
676
|
const result = await executeTool(action, params, config);
|
|
574
|
-
process.stdout.write('\r' + ' '.repeat(
|
|
575
|
-
|
|
677
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
678
|
+
|
|
679
|
+
// Show action-specific result header
|
|
680
|
+
const resultHeader = formatToolResult(action, params, result);
|
|
681
|
+
console.log(` ${resultHeader}`);
|
|
682
|
+
console.log(` ${result.split('\n').join('\n ')}\n`);
|
|
576
683
|
|
|
577
684
|
addMessages(conv, input, response + `\n\n[Tool ${action} executed. Result: ${result}]`);
|
|
578
685
|
} catch (err) {
|
|
579
|
-
process.stdout.write('\r' + ' '.repeat(
|
|
686
|
+
process.stdout.write('\r' + ' '.repeat(80) + '\r');
|
|
580
687
|
console.log(` ${R}Error executing ${action}: ${err.message}${NC}\n`);
|
|
581
688
|
addMessages(conv, input, response + `\n\n[Tool ${action} failed: ${err.message}]`);
|
|
582
689
|
}
|
package/src/commands/ui.mjs
CHANGED
|
@@ -15,7 +15,7 @@ import fs from 'fs';
|
|
|
15
15
|
import path from 'path';
|
|
16
16
|
import { loadConfig } from '../config.mjs';
|
|
17
17
|
import { detectMailProvider, hasMailProvider, getProviderStatus } from '../services/mail-router.mjs';
|
|
18
|
-
import { callLLM, callAgent, parseAgentFile } from '../services/llm.mjs';
|
|
18
|
+
import { callLLM, callLLMStream, callAgent, parseAgentFile } from '../services/llm.mjs';
|
|
19
19
|
import { getUnreadImportant, getMessage, listMessages, sendEmail, createDraft } from '../services/mail-router.mjs';
|
|
20
20
|
import { getTodayEvents, getUpcomingEvents, createEvent, updateEvent, getEventsForDate } from '../services/mail-router.mjs';
|
|
21
21
|
import {
|
|
@@ -28,6 +28,20 @@ import { runPlanningPipeline } from '../services/ops-pipeline.mjs';
|
|
|
28
28
|
import { AGENTS, AGENTS_DIR, NHA_DIR, VERSION } from '../constants.mjs';
|
|
29
29
|
import { getHTML } from '../services/web-ui.mjs';
|
|
30
30
|
import { loadChatHistory, saveChatHistory, extractMemory, buildMemoryContext } from '../services/memory.mjs';
|
|
31
|
+
import {
|
|
32
|
+
createConversation,
|
|
33
|
+
loadConversation,
|
|
34
|
+
saveConversation,
|
|
35
|
+
deleteConversation,
|
|
36
|
+
listConversations,
|
|
37
|
+
getOrCreateActive,
|
|
38
|
+
setActiveId,
|
|
39
|
+
getHistory,
|
|
40
|
+
addMessages,
|
|
41
|
+
exportAsMarkdown,
|
|
42
|
+
exportAsJson,
|
|
43
|
+
migrateOldHistory,
|
|
44
|
+
} from '../services/conversations.mjs';
|
|
31
45
|
import { info, ok, fail, warn, C, G, D, NC, BOLD } from '../ui.mjs';
|
|
32
46
|
import {
|
|
33
47
|
parseActions,
|
|
@@ -78,7 +92,7 @@ function sendJSON(res, statusCode, data) {
|
|
|
78
92
|
res.writeHead(statusCode, {
|
|
79
93
|
'Content-Type': 'application/json',
|
|
80
94
|
'Access-Control-Allow-Origin': '*',
|
|
81
|
-
'Access-Control-Allow-Methods': 'GET,POST,PATCH,OPTIONS',
|
|
95
|
+
'Access-Control-Allow-Methods': 'GET,POST,PATCH,DELETE,OPTIONS',
|
|
82
96
|
'Access-Control-Allow-Headers': 'Content-Type',
|
|
83
97
|
'Cache-Control': 'no-cache',
|
|
84
98
|
});
|
|
@@ -151,6 +165,9 @@ export async function cmdUI(args) {
|
|
|
151
165
|
const config = loadConfig();
|
|
152
166
|
const htmlPage = getHTML(port);
|
|
153
167
|
|
|
168
|
+
// Migrate old chat history to multi-conversation format
|
|
169
|
+
migrateOldHistory();
|
|
170
|
+
|
|
154
171
|
// Pre-load agent cards once at startup
|
|
155
172
|
const agentCards = loadAgentCards();
|
|
156
173
|
|
|
@@ -173,7 +190,7 @@ export async function cmdUI(args) {
|
|
|
173
190
|
if (method === 'OPTIONS') {
|
|
174
191
|
res.writeHead(204, {
|
|
175
192
|
'Access-Control-Allow-Origin': '*',
|
|
176
|
-
'Access-Control-Allow-Methods': 'GET,POST,PATCH,OPTIONS',
|
|
193
|
+
'Access-Control-Allow-Methods': 'GET,POST,PATCH,DELETE,OPTIONS',
|
|
177
194
|
'Access-Control-Allow-Headers': 'Content-Type',
|
|
178
195
|
});
|
|
179
196
|
res.end();
|
|
@@ -999,6 +1016,201 @@ export async function cmdUI(args) {
|
|
|
999
1016
|
return;
|
|
1000
1017
|
}
|
|
1001
1018
|
|
|
1019
|
+
// ── Conversations API ────────────────────────────────────────────
|
|
1020
|
+
|
|
1021
|
+
// GET /api/conversations — list all
|
|
1022
|
+
if (method === 'GET' && pathname === '/api/conversations') {
|
|
1023
|
+
const convs = listConversations();
|
|
1024
|
+
sendJSON(res, 200, { conversations: convs });
|
|
1025
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// POST /api/conversations — create new
|
|
1030
|
+
if (method === 'POST' && pathname === '/api/conversations') {
|
|
1031
|
+
const conv = createConversation();
|
|
1032
|
+
setActiveId(conv.id);
|
|
1033
|
+
sendJSON(res, 201, { conversation: conv });
|
|
1034
|
+
logRequest(method, pathname, 201, Date.now() - start);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
|
|
1038
|
+
// GET /api/conversations/:id
|
|
1039
|
+
if (method === 'GET' && pathname.match(/^\/api\/conversations\/[a-z0-9-]+$/)) {
|
|
1040
|
+
const id = pathname.split('/')[3];
|
|
1041
|
+
const conv = loadConversation(id);
|
|
1042
|
+
if (!conv) { sendJSON(res, 404, { error: 'Conversation not found' }); }
|
|
1043
|
+
else { sendJSON(res, 200, { conversation: conv }); }
|
|
1044
|
+
logRequest(method, pathname, conv ? 200 : 404, Date.now() - start);
|
|
1045
|
+
return;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// DELETE /api/conversations/:id
|
|
1049
|
+
if (method === 'DELETE' && pathname.match(/^\/api\/conversations\/[a-z0-9-]+$/)) {
|
|
1050
|
+
const id = pathname.split('/')[3];
|
|
1051
|
+
const ok = deleteConversation(id);
|
|
1052
|
+
sendJSON(res, ok ? 200 : 404, { ok });
|
|
1053
|
+
logRequest(method, pathname, ok ? 200 : 404, Date.now() - start);
|
|
1054
|
+
return;
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
// PATCH /api/conversations/:id — rename
|
|
1058
|
+
if (method === 'PATCH' && pathname.match(/^\/api\/conversations\/[a-z0-9-]+$/)) {
|
|
1059
|
+
const id = pathname.split('/')[3];
|
|
1060
|
+
const body = await parseBody(req);
|
|
1061
|
+
const conv = loadConversation(id);
|
|
1062
|
+
if (!conv) { sendJSON(res, 404, { error: 'Not found' }); }
|
|
1063
|
+
else {
|
|
1064
|
+
if (body.title) conv.title = body.title;
|
|
1065
|
+
saveConversation(conv);
|
|
1066
|
+
sendJSON(res, 200, { conversation: conv });
|
|
1067
|
+
}
|
|
1068
|
+
logRequest(method, pathname, conv ? 200 : 404, Date.now() - start);
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
1071
|
+
|
|
1072
|
+
// GET /api/conversations/:id/export?format=md|json
|
|
1073
|
+
if (method === 'GET' && pathname.match(/^\/api\/conversations\/[a-z0-9-]+\/export$/)) {
|
|
1074
|
+
const id = pathname.split('/')[3];
|
|
1075
|
+
const conv = loadConversation(id);
|
|
1076
|
+
if (!conv) { sendJSON(res, 404, { error: 'Not found' }); logRequest(method, pathname, 404, Date.now() - start); return; }
|
|
1077
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
1078
|
+
const format = url.searchParams.get('format') || 'md';
|
|
1079
|
+
if (format === 'json') {
|
|
1080
|
+
const exported = exportAsJson(conv);
|
|
1081
|
+
res.writeHead(200, { 'Content-Type': 'application/json', 'Content-Disposition': `attachment; filename="nha-chat-${id}.json"` });
|
|
1082
|
+
res.end(exported);
|
|
1083
|
+
} else {
|
|
1084
|
+
const exported = exportAsMarkdown(conv);
|
|
1085
|
+
res.writeHead(200, { 'Content-Type': 'text/markdown', 'Content-Disposition': `attachment; filename="nha-chat-${id}.md"` });
|
|
1086
|
+
res.end(exported);
|
|
1087
|
+
}
|
|
1088
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
1089
|
+
return;
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
// ── Streaming Chat API ─────────────────────────────────────────────
|
|
1093
|
+
|
|
1094
|
+
// POST /api/chat/stream — SSE streaming chat with conversation persistence
|
|
1095
|
+
if (method === 'POST' && pathname === '/api/chat/stream') {
|
|
1096
|
+
const body = await parseBody(req);
|
|
1097
|
+
if (!body.message) { sendJSON(res, 400, { error: 'message required' }); logRequest(method, pathname, 400, Date.now() - start); return; }
|
|
1098
|
+
if (!config.llm.apiKey) { sendJSON(res, 200, { error: 'no_api_key' }); logRequest(method, pathname, 200, Date.now() - start); return; }
|
|
1099
|
+
|
|
1100
|
+
const msg = body.message.trim();
|
|
1101
|
+
const convId = body.conversationId;
|
|
1102
|
+
|
|
1103
|
+
// Build system prompt
|
|
1104
|
+
let effectiveSystemPrompt = config._chatAgent?.systemPrompt || null;
|
|
1105
|
+
let effectiveMsg = msg;
|
|
1106
|
+
const atMatch = msg.match(/^@(\w+)\s+([\s\S]*)/);
|
|
1107
|
+
if (atMatch) {
|
|
1108
|
+
const inlineAgent = atMatch[1].toLowerCase();
|
|
1109
|
+
effectiveMsg = atMatch[2];
|
|
1110
|
+
const agentFile = path.join(AGENTS_DIR, `${inlineAgent}.mjs`);
|
|
1111
|
+
if (fs.existsSync(agentFile)) {
|
|
1112
|
+
const src = fs.readFileSync(agentFile, 'utf-8');
|
|
1113
|
+
const parsed = parseAgentFile(src, inlineAgent);
|
|
1114
|
+
if (parsed.systemPrompt) effectiveSystemPrompt = parsed.systemPrompt;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
1117
|
+
|
|
1118
|
+
const basePrompt = effectiveSystemPrompt || chatSystemPrompt;
|
|
1119
|
+
let enrichedPrompt = basePrompt;
|
|
1120
|
+
try { const m = buildMemoryContext('chat', effectiveMsg); if (m) enrichedPrompt = basePrompt + m; } catch {}
|
|
1121
|
+
|
|
1122
|
+
// Build message with conversation history
|
|
1123
|
+
const history = body.history || [];
|
|
1124
|
+
const parts = [];
|
|
1125
|
+
for (const turn of history) {
|
|
1126
|
+
const prefix = turn.role === 'user' ? '[User]' : '[Assistant]';
|
|
1127
|
+
parts.push(`${prefix} ${turn.content}`);
|
|
1128
|
+
}
|
|
1129
|
+
parts.push(`[User] ${effectiveMsg}`);
|
|
1130
|
+
const userMessage = parts.join('\n\n');
|
|
1131
|
+
|
|
1132
|
+
// Handle file/image/pdf attachments — fall back to non-streaming
|
|
1133
|
+
if (body.imageBase64 || body.pdfBase64 || body.fileContent) {
|
|
1134
|
+
// Redirect to regular /api/chat for attachment handling
|
|
1135
|
+
sendJSON(res, 200, { error: 'attachments_use_regular', redirect: '/api/chat' });
|
|
1136
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// SSE headers
|
|
1141
|
+
res.writeHead(200, {
|
|
1142
|
+
'Content-Type': 'text/event-stream',
|
|
1143
|
+
'Cache-Control': 'no-cache',
|
|
1144
|
+
'Connection': 'keep-alive',
|
|
1145
|
+
'Access-Control-Allow-Origin': '*',
|
|
1146
|
+
});
|
|
1147
|
+
|
|
1148
|
+
const sendSSE = (event, data) => {
|
|
1149
|
+
res.write(`event: ${event}\ndata: ${JSON.stringify(data)}\n\n`);
|
|
1150
|
+
};
|
|
1151
|
+
|
|
1152
|
+
sendSSE('processing', {});
|
|
1153
|
+
|
|
1154
|
+
try {
|
|
1155
|
+
let fullResponse = '';
|
|
1156
|
+
fullResponse = await callLLMStream(config, enrichedPrompt, userMessage, (chunk) => {
|
|
1157
|
+
sendSSE('token', { content: chunk });
|
|
1158
|
+
});
|
|
1159
|
+
|
|
1160
|
+
// Parse and execute tools
|
|
1161
|
+
const { textParts, actions } = parseActions(fullResponse);
|
|
1162
|
+
const toolResults = [];
|
|
1163
|
+
|
|
1164
|
+
for (const { action, params } of actions) {
|
|
1165
|
+
sendSSE('tool', { action, status: 'executing' });
|
|
1166
|
+
try {
|
|
1167
|
+
const result = await executeTool(action, params, config);
|
|
1168
|
+
toolResults.push({ action, result: typeof result === 'object' ? JSON.stringify(result) : String(result) });
|
|
1169
|
+
sendSSE('tool', { action, status: 'done', result: typeof result === 'string' ? result.slice(0, 500) : '' });
|
|
1170
|
+
} catch (e) {
|
|
1171
|
+
toolResults.push({ action, result: `Error: ${e.message}` });
|
|
1172
|
+
sendSSE('tool', { action, status: 'error', error: e.message });
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
|
|
1176
|
+
// If tools were executed, make a second LLM call with results
|
|
1177
|
+
let finalResponse = fullResponse;
|
|
1178
|
+
if (toolResults.length > 0) {
|
|
1179
|
+
const toolContext = toolResults.map(t => `[${t.action} result]: ${t.result}`).join('\n\n');
|
|
1180
|
+
const followUp = `The user asked: "${msg}"\n\nI executed these tools and got REAL results:\n\n${toolContext}\n\nNow respond to the user based ONLY on the REAL data above. Present the actual results clearly.`;
|
|
1181
|
+
sendSSE('tool_synthesis', {});
|
|
1182
|
+
try {
|
|
1183
|
+
finalResponse = await callLLMStream(config, enrichedPrompt, followUp, (chunk) => {
|
|
1184
|
+
sendSSE('token', { content: chunk });
|
|
1185
|
+
});
|
|
1186
|
+
} catch {
|
|
1187
|
+
finalResponse = toolResults.map(t => `${t.action}: ${t.result}`).join('\n\n');
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
|
|
1191
|
+
// Persist to conversation
|
|
1192
|
+
if (convId) {
|
|
1193
|
+
try {
|
|
1194
|
+
const conv = loadConversation(convId);
|
|
1195
|
+
if (conv) {
|
|
1196
|
+
addMessages(conv, msg, finalResponse);
|
|
1197
|
+
}
|
|
1198
|
+
} catch {}
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Extract memory
|
|
1202
|
+
try { extractMemory('chat', msg, finalResponse); } catch {}
|
|
1203
|
+
|
|
1204
|
+
sendSSE('done', { content: finalResponse, toolResults });
|
|
1205
|
+
} catch (e) {
|
|
1206
|
+
sendSSE('error', { message: e.message });
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
res.end();
|
|
1210
|
+
logRequest(method, pathname, 200, Date.now() - start);
|
|
1211
|
+
return;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1002
1214
|
// GET /api/agents
|
|
1003
1215
|
if (method === 'GET' && pathname === '/api/agents') {
|
|
1004
1216
|
sendJSON(res, 200, { agents: agentCards });
|
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
|
+
export const VERSION = '9.2.0';
|
|
9
9
|
export const BASE_URL = 'https://nothumanallowed.com/cli';
|
|
10
10
|
export const API_BASE = 'https://nothumanallowed.com/api/v1';
|
|
11
11
|
|
package/src/services/web-ui.mjs
CHANGED
|
@@ -198,11 +198,17 @@ input:focus,textarea:focus{border-color:var(--green3)}
|
|
|
198
198
|
const JS = `
|
|
199
199
|
var API = '';
|
|
200
200
|
var currentView = 'dashboard';
|
|
201
|
-
var chatHistory =
|
|
201
|
+
var chatHistory = [];
|
|
202
|
+
var activeConvId = null;
|
|
203
|
+
var convList = [];
|
|
202
204
|
var dash = {emails:[],events:[],tasks:[],plan:null,status:null};
|
|
205
|
+
var chatStreaming = false;
|
|
203
206
|
|
|
204
|
-
function
|
|
205
|
-
function
|
|
207
|
+
function loadConvList(){return apiGet('/api/conversations').then(function(r){convList=(r&&r.conversations)||[];renderConvSidebar();})}
|
|
208
|
+
function loadConv(id){return apiGet('/api/conversations/'+id).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=r.conversation.messages||[];renderMessages();renderConvSidebar();}})}
|
|
209
|
+
function createNewConv(){return apiPost('/api/conversations',{}).then(function(r){if(r&&r.conversation){activeConvId=r.conversation.id;chatHistory=[];renderMessages();loadConvList();}})}
|
|
210
|
+
function deleteConv(id){return fetch(API+'/api/conversations/'+id,{method:'DELETE'}).then(function(){loadConvList();if(id===activeConvId)createNewConv();})}
|
|
211
|
+
function clearChatHistory(){createNewConv()}
|
|
206
212
|
var agentsList = [];
|
|
207
213
|
var selectedAgent = null;
|
|
208
214
|
|
|
@@ -316,18 +322,55 @@ function renderDash(el){
|
|
|
316
322
|
var chatReady=false;
|
|
317
323
|
function renderChat(el){
|
|
318
324
|
if(!chatReady||!document.getElementById('chatMessages')){
|
|
319
|
-
el.innerHTML='<div
|
|
325
|
+
el.innerHTML='<div style="display:flex;height:calc(100vh - 56px)">'+
|
|
326
|
+
'<div id="convSidebar" style="width:220px;border-right:1px solid var(--border);overflow-y:auto;flex-shrink:0;background:var(--bg);display:none">'+
|
|
327
|
+
'<div style="padding:8px"><button onclick="createNewConv()" style="width:100%;padding:8px;border-radius:var(--r);border:1px solid var(--green);background:transparent;color:var(--green);cursor:pointer;font-size:11px">+ New Chat</button></div>'+
|
|
328
|
+
'<div id="convList"></div>'+
|
|
329
|
+
'</div>'+
|
|
330
|
+
'<div style="flex:1;display:flex;flex-direction:column;min-width:0">'+
|
|
331
|
+
'<div style="padding:6px 12px;border-bottom:1px solid var(--border);display:flex;align-items:center;gap:8px">'+
|
|
332
|
+
'<button onclick="toggleConvSidebar()" style="background:none;border:none;cursor:pointer;font-size:14px;color:var(--dim)" title="History">☰</button>'+
|
|
333
|
+
'<span id="convTitle" style="flex:1;font-size:12px;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">New Chat</span>'+
|
|
334
|
+
'<button onclick="createNewConv()" style="background:none;border:1px solid var(--green);color:var(--green);padding:4px 10px;border-radius:var(--r);cursor:pointer;font-size:10px">+ New</button>'+
|
|
335
|
+
'<button onclick="exportConvMd()" style="background:none;border:1px solid var(--border);color:var(--dim);padding:4px 8px;border-radius:var(--r);cursor:pointer;font-size:10px" title="Export Markdown">Export</button>'+
|
|
336
|
+
'</div>'+
|
|
337
|
+
'<div class="chat"><div class="chat__messages" id="chatMessages"></div>'+
|
|
338
|
+
'<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">×</button></div>'+
|
|
339
|
+
'<div class="chat__bar"><button class="chat__mic" id="chatMic" onclick="toggleVoiceInput()" title="Voice input">🎤</button><button onclick="document.getElementById(\\x27chatFileInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach file">📎</button><button onclick="document.getElementById(\\x27chatImageInput\\x27).click()" style="background:none;border:none;cursor:pointer;font-size:16px;padding:4px" title="Attach image">📷</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></div>'+
|
|
340
|
+
'</div>'+
|
|
341
|
+
'</div>'+
|
|
342
|
+
'</div>';
|
|
320
343
|
chatReady=true;
|
|
321
344
|
document.getElementById('chatSend').onclick=sendChat;
|
|
322
345
|
document.getElementById('chatInput').onkeydown=function(e){if(e.key==='Enter'&&!e.shiftKey){e.preventDefault();sendChat()}};
|
|
323
|
-
|
|
346
|
+
loadConvList().then(function(){
|
|
347
|
+
if(!activeConvId&&convList.length>0){loadConv(convList[0].id)}
|
|
348
|
+
else if(!activeConvId){createNewConv()}
|
|
349
|
+
else{loadConv(activeConvId)}
|
|
350
|
+
});
|
|
324
351
|
setTimeout(function(){var i=document.getElementById('chatInput');if(i)i.focus()},100);
|
|
325
352
|
}
|
|
326
353
|
}
|
|
354
|
+
function toggleConvSidebar(){var s=document.getElementById('convSidebar');if(!s)return;s.style.display=s.style.display==='none'?'':'none';}
|
|
355
|
+
function renderConvSidebar(){
|
|
356
|
+
var el=document.getElementById('convList');if(!el)return;
|
|
357
|
+
var h='';convList.forEach(function(c){
|
|
358
|
+
var active=c.id===activeConvId;
|
|
359
|
+
var turns=Math.floor(c.messageCount/2);
|
|
360
|
+
h+='<div onclick="loadConv(\\x27'+c.id+'\\x27)" style="padding:8px 12px;cursor:pointer;border-left:3px solid '+(active?'var(--green)':'transparent')+';background:'+(active?'var(--bg2)':'transparent')+'" onmouseover="this.style.background=\\x27var(--bg2)\\x27" onmouseout="this.style.background='+(active?"\\x27var(--bg2)\\x27":"\\x27transparent\\x27")+'">'+
|
|
361
|
+
'<div style="font-size:11px;color:var(--fg);overflow:hidden;text-overflow:ellipsis;white-space:nowrap">'+esc(c.title)+'</div>'+
|
|
362
|
+
'<div style="font-size:9px;color:var(--dim);display:flex;gap:6px;margin-top:2px"><span>'+turns+' turns</span>'+(active?'':'<span onclick="event.stopPropagation();deleteConv(\\x27'+c.id+'\\x27)" style="color:var(--red);cursor:pointer">del</span>')+'</div>'+
|
|
363
|
+
'</div>';
|
|
364
|
+
});
|
|
365
|
+
el.innerHTML=h;
|
|
366
|
+
var t=document.getElementById('convTitle');
|
|
367
|
+
if(t){var ac=convList.find(function(c){return c.id===activeConvId});t.textContent=ac?ac.title:'New Chat';}
|
|
368
|
+
}
|
|
369
|
+
function exportConvMd(){if(!activeConvId)return;window.open(API+'/api/conversations/'+activeConvId+'/export?format=md','_blank');}
|
|
327
370
|
function renderMessages(){
|
|
328
371
|
var el=document.getElementById('chatMessages');if(!el)return;
|
|
329
372
|
if(chatHistory.length===0){
|
|
330
|
-
el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant</div><div class="chat__empty-hint">Try: Show my unread emails / What is on my calendar
|
|
373
|
+
el.innerHTML='<div class="chat__empty"><div class="chat__empty-title">NHA Chat</div><div>Personal Operations Assistant — Streaming + Web Search</div><div class="chat__empty-hint">Try: Show my unread emails / Search the web for React 19 / What is on my calendar?</div></div>';
|
|
331
374
|
return;
|
|
332
375
|
}
|
|
333
376
|
var h='';chatHistory.forEach(function(m){
|
|
@@ -389,37 +432,78 @@ function sendChat(){
|
|
|
389
432
|
var msg=inp.value.trim();
|
|
390
433
|
var hasAttach=!!chatAttachedFile||!!chatAttachedImage;
|
|
391
434
|
if(!msg&&!hasAttach)return;
|
|
435
|
+
if(chatStreaming)return;
|
|
392
436
|
|
|
393
437
|
var displayMsg=msg;
|
|
394
438
|
if(chatAttachedFile)displayMsg=(msg?msg+' ':'')+'[File: '+chatAttachedFile.name+']';
|
|
395
439
|
if(chatAttachedImage)displayMsg=(msg?msg+' ':'')+'[Image: '+chatAttachedImage.name+']';
|
|
396
440
|
|
|
397
441
|
chatHistory.push({role:'user',content:displayMsg});
|
|
398
|
-
inp.value='';
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
payload.fileContent=chatAttachedFile.content;payload.fileName=chatAttachedFile.name;
|
|
442
|
+
inp.value='';renderMessages();
|
|
443
|
+
|
|
444
|
+
// If attachment, use regular (non-streaming) endpoint
|
|
445
|
+
if(chatAttachedFile||chatAttachedImage){
|
|
446
|
+
chatHistory.push({role:'assistant',content:'Thinking...'});renderMessages();
|
|
447
|
+
var payload={message:msg||'Analyze this attachment',history:chatHistory.slice(0,-1)};
|
|
448
|
+
if(chatAttachedFile){
|
|
449
|
+
if(chatAttachedFile.isPDF&&chatAttachedFile.base64){payload.pdfBase64=chatAttachedFile.base64;payload.pdfName=chatAttachedFile.name;}
|
|
450
|
+
else{payload.fileContent=chatAttachedFile.content;payload.fileName=chatAttachedFile.name;}
|
|
407
451
|
}
|
|
452
|
+
if(chatAttachedImage){payload.imageBase64=chatAttachedImage.base64;payload.imageMimeType=chatAttachedImage.mimeType;}
|
|
453
|
+
clearChatAttach();
|
|
454
|
+
apiPost('/api/chat',payload).then(function(r){
|
|
455
|
+
chatHistory.pop();
|
|
456
|
+
if(r&&r.response){chatHistory.push({role:'assistant',content:r.response})}
|
|
457
|
+
else if(r&&r.error){chatHistory.push({role:'assistant',content:'Error: '+r.error})}
|
|
458
|
+
else{chatHistory.push({role:'assistant',content:'Error: no response'})}
|
|
459
|
+
renderMessages();loadConvList();
|
|
460
|
+
});
|
|
461
|
+
return;
|
|
408
462
|
}
|
|
409
|
-
if(chatAttachedImage){payload.imageBase64=chatAttachedImage.base64;payload.imageMimeType=chatAttachedImage.mimeType;payload.imageName=chatAttachedImage.name;}
|
|
410
463
|
clearChatAttach();
|
|
411
464
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
465
|
+
// Streaming SSE
|
|
466
|
+
chatStreaming=true;
|
|
467
|
+
chatHistory.push({role:'assistant',content:''});
|
|
468
|
+
renderMessages();
|
|
469
|
+
var streamIdx=chatHistory.length-1;
|
|
470
|
+
var payload={message:msg,history:chatHistory.slice(0,-1),conversationId:activeConvId};
|
|
471
|
+
|
|
472
|
+
fetch(API+'/api/chat/stream',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(payload)}).then(function(response){
|
|
473
|
+
if(!response.ok||!response.body){chatHistory[streamIdx].content='Error: connection failed';chatStreaming=false;renderMessages();return;}
|
|
474
|
+
var reader=response.body.getReader();var decoder=new TextDecoder();var buffer='';var currentEvent='';
|
|
475
|
+
function pump(){
|
|
476
|
+
reader.read().then(function(result){
|
|
477
|
+
if(result.done){chatStreaming=false;renderMessages();loadConvList();return;}
|
|
478
|
+
buffer+=decoder.decode(result.value,{stream:true});
|
|
479
|
+
var lines=buffer.split('\\n');buffer=lines.pop()||'';
|
|
480
|
+
for(var i=0;i<lines.length;i++){
|
|
481
|
+
var line=lines[i];
|
|
482
|
+
if(line.startsWith('event: ')){currentEvent=line.slice(7).trim();}
|
|
483
|
+
else if(line.startsWith('data: ')){
|
|
484
|
+
try{
|
|
485
|
+
var data=JSON.parse(line.slice(6));
|
|
486
|
+
if(currentEvent==='token'&&data.content){
|
|
487
|
+
chatHistory[streamIdx].content+=data.content;
|
|
488
|
+
var el=document.getElementById('chatMessages');
|
|
489
|
+
if(el){var msgs=el.querySelectorAll('.msg');var last=msgs[msgs.length-1];if(last){var bub=last.querySelector('.msg__bubble');if(bub)bub.textContent=chatHistory[streamIdx].content;}el.scrollTop=el.scrollHeight;}
|
|
490
|
+
}
|
|
491
|
+
if(currentEvent==='tool'){
|
|
492
|
+
var indicator=data.status==='executing'?'['+data.action+' executing...]':'['+data.action+': '+data.status+']';
|
|
493
|
+
chatHistory[streamIdx].content+=indicator+'\\n';
|
|
494
|
+
renderMessages();
|
|
495
|
+
}
|
|
496
|
+
if(currentEvent==='tool_synthesis'){chatHistory[streamIdx].content='';renderMessages();}
|
|
497
|
+
if(currentEvent==='done'){chatStreaming=false;if(data.content)chatHistory[streamIdx].content=data.content;renderMessages();loadConvList();}
|
|
498
|
+
if(currentEvent==='error'){chatStreaming=false;chatHistory[streamIdx].content='Error: '+(data.message||'Unknown');renderMessages();}
|
|
499
|
+
}catch(e){}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
pump();
|
|
503
|
+
}).catch(function(e){chatStreaming=false;chatHistory[streamIdx].content='Error: '+e.message;renderMessages();});
|
|
421
504
|
}
|
|
422
|
-
|
|
505
|
+
pump();
|
|
506
|
+
}).catch(function(e){chatStreaming=false;chatHistory[streamIdx].content='Error: '+e.message;renderMessages();});
|
|
423
507
|
}
|
|
424
508
|
|
|
425
509
|
// ---- TASKS ----
|