nothumanallowed 10.5.0 → 10.6.1

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.5.0",
3
+ "version": "10.6.1",
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": {
@@ -2095,43 +2095,31 @@ export async function cmdUI(args) {
2095
2095
  browserWs.on('error', () => { wsClients.delete(browserWs); if (daemonWs) try { daemonWs.close(); } catch {} });
2096
2096
  });
2097
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
2098
+ // ── Alexandria real-time connect to server WS for each channel ──────
2101
2099
  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
2100
+ const alexWsConnections = new Map(); // channelId → ws
2104
2101
 
2105
- setInterval(async () => {
2106
- if (wsClients.size === 0) return; // no browsers connected
2102
+ function connectAlexandriaWs(channelId, channelName) {
2103
+ if (alexWsConnections.has(channelId)) return;
2107
2104
  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;
2105
+ const { WebSocket: WsClient } = require('ws');
2106
+ const wsUrl = 'wss://nothumanallowed.com/ws/alexandria/' + channelId;
2107
+ const alexWs = new WsClient(wsUrl);
2123
2108
 
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);
2109
+ alexWs.on('open', () => {
2110
+ alexWsConnections.set(channelId, alexWs);
2111
+ });
2128
2112
 
2129
- for (const msg of newMsgs) {
2130
- if (msg.type === 'system' || !msg.ciphertext || !msg.nonce) continue;
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();
2131
2119
  let content = '[encrypted]';
2132
2120
  try {
2133
- const nonce = Buffer.from(msg.nonce, 'base64');
2134
- const raw = Buffer.from(msg.ciphertext, 'base64');
2121
+ const nonce = Buffer.from(msg.message.nonce, 'base64');
2122
+ const raw = Buffer.from(msg.message.ciphertext, 'base64');
2135
2123
  const tag = raw.subarray(raw.length - 16);
2136
2124
  const encrypted = raw.subarray(0, raw.length - 16);
2137
2125
  const decipher = crypto.createDecipheriv('aes-256-gcm', channelKey, nonce);
@@ -2139,25 +2127,46 @@ export async function cmdUI(args) {
2139
2127
  content = decipher.update(encrypted) + decipher.final('utf-8');
2140
2128
  } catch {}
2141
2129
 
2142
- const sender = data.members?.find(m => m.fingerprint === msg.senderFingerprint);
2130
+ // Push to browser via local WS
2143
2131
  wsBroadcast({
2144
2132
  type: 'collab_message',
2145
- channelId: ch.id,
2146
- channelName: ch.name,
2133
+ channelId,
2134
+ channelName,
2147
2135
  message: {
2148
- senderName: sender?.displayName || msg.senderFingerprint?.slice(0, 8) || 'Unknown',
2149
- senderFingerprint: msg.senderFingerprint,
2136
+ senderName: msg.message.senderName || msg.message.senderFingerprint?.slice(0, 8) || 'Unknown',
2137
+ senderFingerprint: msg.message.senderFingerprint,
2150
2138
  content,
2151
- timestamp: msg.timestamp,
2152
- type: msg.type,
2139
+ timestamp: msg.message.timestamp,
2140
+ type: msg.message.type,
2153
2141
  },
2154
2142
  });
2155
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);
2156
2166
  }
2157
- channelMessageCounts.set(ch.id, newCount);
2158
2167
  }
2159
2168
  } catch {}
2160
- }, 10000); // 10s interval — gentle on server, WS handles own messages instantly
2169
+ }, 2000);
2161
2170
 
2162
2171
  } catch {
2163
2172
  // ws package not available — live updates disabled
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.5.0';
8
+ export const VERSION = '10.6.1';
9
9
  export const BASE_URL = 'https://nothumanallowed.com/cli';
10
10
  export const API_BASE = 'https://nothumanallowed.com/api/v1';
11
11
 
@@ -1744,16 +1744,22 @@ function collabSelect(id){
1744
1744
 
1745
1745
  function collabLoadMessages(){
1746
1746
  if(!collabActiveChannel)return;
1747
- // Reset unread when viewing
1748
1747
  collabUnreadCount=0;
1749
1748
  updateCollabBadge();
1750
1749
  apiGet('/api/collab/messages?channelId='+collabActiveChannel).then(function(r){
1750
+ if(r&&r.error){
1751
+ var el=document.getElementById('collabMessages');
1752
+ if(el)el.innerHTML='<div style="text-align:center;color:var(--red);padding:20px;font-size:11px">'+esc(r.error)+'<br><span style="color:var(--dim);font-size:10px">This channel may have expired or the server was restarted.</span></div>';
1753
+ return;
1754
+ }
1751
1755
  if(!r||!r.messages)return;
1752
1756
  collabMessages=r.messages;
1753
- // Update last count for this channel
1754
1757
  var ch=collabChannels.find(function(c){return c.id===collabActiveChannel});
1755
1758
  if(ch)ch._lastCount=r.messages.length;
1756
1759
  renderCollabMessages();
1760
+ }).catch(function(e){
1761
+ var el=document.getElementById('collabMessages');
1762
+ if(el)el.innerHTML='<div style="text-align:center;color:var(--red);padding:20px;font-size:11px">Connection error</div>';
1757
1763
  });
1758
1764
  }
1759
1765