nothumanallowed 10.4.0 → 10.6.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": "10.4.0",
3
+ "version": "10.6.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": {
@@ -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
- sendJSON(res, 200, await r.json());
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 (ws package) relay daemon events to browser ──
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,85 @@ 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 — connect to server WS for each channel ──────
2099
+ const collabDir = path.join(NHA_DIR, 'collab');
2100
+ const alexWsConnections = new Map(); // channelId → ws
2101
+
2102
+ function connectAlexandriaWs(channelId, channelName) {
2103
+ if (alexWsConnections.has(channelId)) return;
2104
+ try {
2105
+ const { WebSocket: WsClient } = require('ws');
2106
+ const wsUrl = 'wss://nothumanallowed.com/ws/alexandria/' + channelId;
2107
+ const alexWs = new WsClient(wsUrl);
2108
+
2109
+ alexWs.on('open', () => {
2110
+ alexWsConnections.set(channelId, alexWs);
2111
+ });
2112
+
2113
+ alexWs.on('message', (data) => {
2114
+ try {
2115
+ const msg = JSON.parse(data.toString());
2116
+ if (msg.type === 'new_message' && msg.message) {
2117
+ // Decrypt the message
2118
+ const channelKey = crypto.createHash('sha256').update('alexandria-channel-key-v1').update(channelId).digest();
2119
+ let content = '[encrypted]';
2120
+ try {
2121
+ const nonce = Buffer.from(msg.message.nonce, 'base64');
2122
+ const raw = Buffer.from(msg.message.ciphertext, 'base64');
2123
+ const tag = raw.subarray(raw.length - 16);
2124
+ const encrypted = raw.subarray(0, raw.length - 16);
2125
+ const decipher = crypto.createDecipheriv('aes-256-gcm', channelKey, nonce);
2126
+ decipher.setAuthTag(tag);
2127
+ content = decipher.update(encrypted) + decipher.final('utf-8');
2128
+ } catch {}
2129
+
2130
+ // Push to browser via local WS
2131
+ wsBroadcast({
2132
+ type: 'collab_message',
2133
+ channelId,
2134
+ channelName,
2135
+ message: {
2136
+ senderName: msg.message.senderName || msg.message.senderFingerprint?.slice(0, 8) || 'Unknown',
2137
+ senderFingerprint: msg.message.senderFingerprint,
2138
+ content,
2139
+ timestamp: msg.message.timestamp,
2140
+ type: msg.message.type,
2141
+ },
2142
+ });
2143
+ }
2144
+ } catch {}
2145
+ });
2146
+
2147
+ alexWs.on('close', () => {
2148
+ alexWsConnections.delete(channelId);
2149
+ // Reconnect after 5s
2150
+ setTimeout(() => connectAlexandriaWs(channelId, channelName), 5000);
2151
+ });
2152
+ alexWs.on('error', () => {
2153
+ alexWsConnections.delete(channelId);
2154
+ });
2155
+ } catch {}
2156
+ }
2157
+
2158
+ // Connect to all channels on startup
2159
+ setTimeout(() => {
2160
+ try {
2161
+ const chFile = path.join(collabDir, 'channels.json');
2162
+ if (fs.existsSync(chFile)) {
2163
+ const channels = JSON.parse(fs.readFileSync(chFile, 'utf-8'));
2164
+ for (const ch of channels) {
2165
+ connectAlexandriaWs(ch.id, ch.name);
2166
+ }
2167
+ }
2168
+ } catch {}
2169
+ }, 2000);
2170
+
2074
2171
  } catch {
2075
- // ws package not available — live updates disabled, everything else works
2172
+ // ws package not available — live updates disabled
2076
2173
  }
2077
2174
 
2078
2175
  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.4.0';
8
+ export const VERSION = '10.6.0';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -1600,42 +1600,12 @@ var collabLastMessageCount=0;
1600
1600
  var collabUnreadCount=0;
1601
1601
  var collabGlobalPolling=null;
1602
1602
 
1603
- // Global polling — check for new messages across ALL channels every 3s (even when not on Collab tab)
1603
+ // No client-side polling needed server pushes via WebSocket
1604
1604
  function startCollabGlobalPolling(){
1605
- if(collabGlobalPolling)return;
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&&currentView!=='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&&currentView==='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