nothumanallowed 10.4.0 → 10.5.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/commands/ui.mjs +95 -7
- package/src/constants.mjs +1 -1
- package/src/services/web-ui.mjs +23 -32
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nothumanallowed",
|
|
3
|
-
"version": "10.
|
|
3
|
+
"version": "10.5.0",
|
|
4
4
|
"description": "NotHumanAllowed — 38 AI agents, 53 tools. Email, calendar, browser automation, screen capture, canvas, cron/heartbeat, GitHub, Notion, Slack, voice chat, 28 languages. Zero-dependency CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/commands/ui.mjs
CHANGED
|
@@ -395,7 +395,20 @@ export async function cmdUI(args) {
|
|
|
395
395
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
396
396
|
body: JSON.stringify({ senderFingerprint: identity.fingerprint, nonce: nonce.toString('base64'), ciphertext, type: 'text' }),
|
|
397
397
|
});
|
|
398
|
-
|
|
398
|
+
const result = await r.json();
|
|
399
|
+
// Broadcast own message via WS immediately (so other tabs/views see it)
|
|
400
|
+
wsBroadcast({
|
|
401
|
+
type: 'collab_message',
|
|
402
|
+
channelId: body.channelId,
|
|
403
|
+
message: {
|
|
404
|
+
senderName: identity.displayName || 'You',
|
|
405
|
+
senderFingerprint: identity.fingerprint,
|
|
406
|
+
content: body.message,
|
|
407
|
+
timestamp: result.timestamp || new Date().toISOString(),
|
|
408
|
+
type: 'text',
|
|
409
|
+
},
|
|
410
|
+
});
|
|
411
|
+
sendJSON(res, 200, result);
|
|
399
412
|
logRequest(method, pathname, 200, Date.now() - start);
|
|
400
413
|
return;
|
|
401
414
|
}
|
|
@@ -2050,16 +2063,26 @@ export async function cmdUI(args) {
|
|
|
2050
2063
|
|
|
2051
2064
|
const server = http.createServer(handleRequest);
|
|
2052
2065
|
|
|
2053
|
-
// ── WebSocket server
|
|
2066
|
+
// ── WebSocket server — daemon relay + Alexandria real-time ──────────
|
|
2067
|
+
let wsBroadcast = (msg) => {}; // no-op until WS is ready
|
|
2054
2068
|
try {
|
|
2055
|
-
const { WebSocketServer } = await import('ws');
|
|
2069
|
+
const { WebSocketServer, WebSocket: WsClient } = await import('ws');
|
|
2056
2070
|
const wss = new WebSocketServer({ server });
|
|
2071
|
+
const wsClients = new Set();
|
|
2072
|
+
|
|
2073
|
+
wsBroadcast = (msg) => {
|
|
2074
|
+
const data = JSON.stringify(msg);
|
|
2075
|
+
for (const ws of wsClients) {
|
|
2076
|
+
if (ws.readyState === 1) try { ws.send(data); } catch {}
|
|
2077
|
+
}
|
|
2078
|
+
};
|
|
2057
2079
|
|
|
2058
2080
|
wss.on('connection', (browserWs) => {
|
|
2081
|
+
wsClients.add(browserWs);
|
|
2082
|
+
|
|
2059
2083
|
// Connect to daemon WS on port 3848 and relay messages
|
|
2060
2084
|
let daemonWs = null;
|
|
2061
2085
|
try {
|
|
2062
|
-
const { WebSocket: WsClient } = require('ws');
|
|
2063
2086
|
daemonWs = new WsClient('ws://127.0.0.1:3848');
|
|
2064
2087
|
daemonWs.on('message', (data) => {
|
|
2065
2088
|
if (browserWs.readyState === 1) browserWs.send(data.toString());
|
|
@@ -2068,11 +2091,76 @@ export async function cmdUI(args) {
|
|
|
2068
2091
|
daemonWs.on('close', () => { daemonWs = null; });
|
|
2069
2092
|
} catch {}
|
|
2070
2093
|
|
|
2071
|
-
browserWs.on('close', () => { if (daemonWs) try { daemonWs.close(); } catch {} });
|
|
2072
|
-
browserWs.on('error', () => { if (daemonWs) try { daemonWs.close(); } catch {} });
|
|
2094
|
+
browserWs.on('close', () => { wsClients.delete(browserWs); if (daemonWs) try { daemonWs.close(); } catch {} });
|
|
2095
|
+
browserWs.on('error', () => { wsClients.delete(browserWs); if (daemonWs) try { daemonWs.close(); } catch {} });
|
|
2073
2096
|
});
|
|
2097
|
+
|
|
2098
|
+
// ── Alexandria real-time polling → WS push ──────────────────────
|
|
2099
|
+
// Server-side polling: check Alexandria channels for new messages every 2s
|
|
2100
|
+
// Push new messages to browser via WebSocket — browser does ZERO polling
|
|
2101
|
+
const collabDir = path.join(NHA_DIR, 'collab');
|
|
2102
|
+
const ALEX_API = 'https://nothumanallowed.com/api/v1/alexandria';
|
|
2103
|
+
const channelMessageCounts = new Map(); // channelId → last known count
|
|
2104
|
+
|
|
2105
|
+
setInterval(async () => {
|
|
2106
|
+
if (wsClients.size === 0) return; // no browsers connected
|
|
2107
|
+
try {
|
|
2108
|
+
const chFile = path.join(collabDir, 'channels.json');
|
|
2109
|
+
if (!fs.existsSync(chFile)) return;
|
|
2110
|
+
const channels = JSON.parse(fs.readFileSync(chFile, 'utf-8'));
|
|
2111
|
+
const idFile = path.join(collabDir, 'identity.json');
|
|
2112
|
+
if (!fs.existsSync(idFile)) return;
|
|
2113
|
+
const identity = JSON.parse(fs.readFileSync(idFile, 'utf-8'));
|
|
2114
|
+
|
|
2115
|
+
for (const ch of channels) {
|
|
2116
|
+
const r = await fetch(ALEX_API + '/channels/' + ch.id + '/messages?fp=' + identity.fingerprint);
|
|
2117
|
+
if (!r.ok) continue;
|
|
2118
|
+
const data = await r.json();
|
|
2119
|
+
if (!data.messages) continue;
|
|
2120
|
+
|
|
2121
|
+
const prevCount = channelMessageCounts.get(ch.id) || 0;
|
|
2122
|
+
const newCount = data.messages.length;
|
|
2123
|
+
|
|
2124
|
+
if (newCount > prevCount && prevCount > 0) {
|
|
2125
|
+
// New messages! Decrypt and push via WS
|
|
2126
|
+
const channelKey = crypto.createHash('sha256').update('alexandria-channel-key-v1').update(ch.id).digest();
|
|
2127
|
+
const newMsgs = data.messages.slice(prevCount);
|
|
2128
|
+
|
|
2129
|
+
for (const msg of newMsgs) {
|
|
2130
|
+
if (msg.type === 'system' || !msg.ciphertext || !msg.nonce) continue;
|
|
2131
|
+
let content = '[encrypted]';
|
|
2132
|
+
try {
|
|
2133
|
+
const nonce = Buffer.from(msg.nonce, 'base64');
|
|
2134
|
+
const raw = Buffer.from(msg.ciphertext, 'base64');
|
|
2135
|
+
const tag = raw.subarray(raw.length - 16);
|
|
2136
|
+
const encrypted = raw.subarray(0, raw.length - 16);
|
|
2137
|
+
const decipher = crypto.createDecipheriv('aes-256-gcm', channelKey, nonce);
|
|
2138
|
+
decipher.setAuthTag(tag);
|
|
2139
|
+
content = decipher.update(encrypted) + decipher.final('utf-8');
|
|
2140
|
+
} catch {}
|
|
2141
|
+
|
|
2142
|
+
const sender = data.members?.find(m => m.fingerprint === msg.senderFingerprint);
|
|
2143
|
+
wsBroadcast({
|
|
2144
|
+
type: 'collab_message',
|
|
2145
|
+
channelId: ch.id,
|
|
2146
|
+
channelName: ch.name,
|
|
2147
|
+
message: {
|
|
2148
|
+
senderName: sender?.displayName || msg.senderFingerprint?.slice(0, 8) || 'Unknown',
|
|
2149
|
+
senderFingerprint: msg.senderFingerprint,
|
|
2150
|
+
content,
|
|
2151
|
+
timestamp: msg.timestamp,
|
|
2152
|
+
type: msg.type,
|
|
2153
|
+
},
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
}
|
|
2157
|
+
channelMessageCounts.set(ch.id, newCount);
|
|
2158
|
+
}
|
|
2159
|
+
} catch {}
|
|
2160
|
+
}, 10000); // 10s interval — gentle on server, WS handles own messages instantly
|
|
2161
|
+
|
|
2074
2162
|
} catch {
|
|
2075
|
-
// ws package not available — live updates disabled
|
|
2163
|
+
// ws package not available — live updates disabled
|
|
2076
2164
|
}
|
|
2077
2165
|
|
|
2078
2166
|
server.on('error', (err) => {
|
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 = '10.
|
|
8
|
+
export const VERSION = '10.5.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
|
@@ -1600,42 +1600,12 @@ var collabLastMessageCount=0;
|
|
|
1600
1600
|
var collabUnreadCount=0;
|
|
1601
1601
|
var collabGlobalPolling=null;
|
|
1602
1602
|
|
|
1603
|
-
//
|
|
1603
|
+
// No client-side polling needed — server pushes via WebSocket
|
|
1604
1604
|
function startCollabGlobalPolling(){
|
|
1605
|
-
|
|
1606
|
-
// Load channels first
|
|
1605
|
+
// Just load channels list once
|
|
1607
1606
|
apiGet('/api/collab/channels').then(function(r){
|
|
1608
1607
|
if(r&&r.channels)collabChannels=r.channels;
|
|
1609
1608
|
}).catch(function(){});
|
|
1610
|
-
collabGlobalPolling=setInterval(function(){
|
|
1611
|
-
if(collabChannels.length===0)return;
|
|
1612
|
-
// Check each channel for new messages
|
|
1613
|
-
for(var i=0;i<collabChannels.length;i++){
|
|
1614
|
-
(function(ch){
|
|
1615
|
-
apiGet('/api/collab/messages?channelId='+ch.id).then(function(r){
|
|
1616
|
-
if(!r||!r.messages)return;
|
|
1617
|
-
var prevCount=ch._lastCount||0;
|
|
1618
|
-
var newCount=r.messages.length;
|
|
1619
|
-
if(newCount>prevCount&&prevCount>0){
|
|
1620
|
-
var diff=newCount-prevCount;
|
|
1621
|
-
collabUnreadCount+=diff;
|
|
1622
|
-
updateCollabBadge();
|
|
1623
|
-
// Show toast notification
|
|
1624
|
-
var lastMsg=r.messages[r.messages.length-1];
|
|
1625
|
-
if(lastMsg&¤tView!=='collab'){
|
|
1626
|
-
showToast('collab','AgentMessenger','New message in '+ch.name+': '+(lastMsg.content||'[encrypted]').slice(0,80),5000);
|
|
1627
|
-
}
|
|
1628
|
-
// Update messages if viewing this channel
|
|
1629
|
-
if(collabActiveChannel===ch.id&¤tView==='collab'){
|
|
1630
|
-
collabMessages=r.messages;
|
|
1631
|
-
renderCollabMessages();
|
|
1632
|
-
}
|
|
1633
|
-
}
|
|
1634
|
-
ch._lastCount=newCount;
|
|
1635
|
-
}).catch(function(){});
|
|
1636
|
-
})(collabChannels[i]);
|
|
1637
|
-
}
|
|
1638
|
-
},3000);
|
|
1639
1609
|
}
|
|
1640
1610
|
|
|
1641
1611
|
function updateCollabBadge(){
|
|
@@ -2209,6 +2179,27 @@ function handleDaemonEvent(msg) {
|
|
|
2209
2179
|
showToast('plan', 'Daily Plan Ready', 'Your plan for ' + msg.data.date + ' has been generated.', 10000);
|
|
2210
2180
|
if (currentView === 'plan') renderPlan(document.getElementById('content'));
|
|
2211
2181
|
break;
|
|
2182
|
+
|
|
2183
|
+
case 'collab_message':
|
|
2184
|
+
// Real-time Alexandria message via WebSocket
|
|
2185
|
+
var cm = msg.message;
|
|
2186
|
+
if (cm) {
|
|
2187
|
+
// Add to messages if viewing this channel
|
|
2188
|
+
if (currentView === 'collab' && collabActiveChannel === msg.channelId) {
|
|
2189
|
+
// Avoid duplicates
|
|
2190
|
+
var isDup = collabMessages.some(function(m) { return m.timestamp === cm.timestamp && m.content === cm.content; });
|
|
2191
|
+
if (!isDup) {
|
|
2192
|
+
collabMessages.push({senderName: cm.senderName, timestamp: cm.timestamp, content: cm.content, type: cm.type});
|
|
2193
|
+
renderCollabMessages();
|
|
2194
|
+
}
|
|
2195
|
+
} else {
|
|
2196
|
+
// Not viewing this channel — show badge + toast
|
|
2197
|
+
collabUnreadCount++;
|
|
2198
|
+
updateCollabBadge();
|
|
2199
|
+
showToast('collab', 'AgentMessenger', cm.senderName + ': ' + (cm.content || '').slice(0, 100), 5000);
|
|
2200
|
+
}
|
|
2201
|
+
}
|
|
2202
|
+
break;
|
|
2212
2203
|
}
|
|
2213
2204
|
}
|
|
2214
2205
|
|