nothumanallowed 10.3.3 → 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 +82 -17
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
|
@@ -1596,6 +1596,48 @@ var collabChannels=[];
|
|
|
1596
1596
|
var collabMessages=[];
|
|
1597
1597
|
var collabActiveChannel=null;
|
|
1598
1598
|
var collabPolling=null;
|
|
1599
|
+
var collabLastMessageCount=0;
|
|
1600
|
+
var collabUnreadCount=0;
|
|
1601
|
+
var collabGlobalPolling=null;
|
|
1602
|
+
|
|
1603
|
+
// No client-side polling needed — server pushes via WebSocket
|
|
1604
|
+
function startCollabGlobalPolling(){
|
|
1605
|
+
// Just load channels list once
|
|
1606
|
+
apiGet('/api/collab/channels').then(function(r){
|
|
1607
|
+
if(r&&r.channels)collabChannels=r.channels;
|
|
1608
|
+
}).catch(function(){});
|
|
1609
|
+
}
|
|
1610
|
+
|
|
1611
|
+
function updateCollabBadge(){
|
|
1612
|
+
var badge=document.getElementById('collabBadge');
|
|
1613
|
+
if(badge){
|
|
1614
|
+
if(collabUnreadCount>0){
|
|
1615
|
+
badge.textContent=collabUnreadCount>99?'99+':collabUnreadCount;
|
|
1616
|
+
badge.style.display='inline-block';
|
|
1617
|
+
} else {
|
|
1618
|
+
badge.style.display='none';
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
function renderCollabMessages(){
|
|
1624
|
+
var el=document.getElementById('collabMessages');if(!el)return;
|
|
1625
|
+
if(collabMessages.length===0){el.innerHTML='<div style="text-align:center;color:var(--dim);padding:20px;font-size:11px">No messages yet</div>';return;}
|
|
1626
|
+
var h='';
|
|
1627
|
+
for(var i=0;i<collabMessages.length;i++){
|
|
1628
|
+
var m=collabMessages[i];
|
|
1629
|
+
var time=new Date(m.timestamp).toLocaleTimeString();
|
|
1630
|
+
var sender=m.senderName||m.senderFingerprint?.slice(0,8)||'Unknown';
|
|
1631
|
+
var content=m.content||m.plaintext||'[encrypted]';
|
|
1632
|
+
if(m.type==='system'){h+='<div style="text-align:center;color:var(--dim);font-size:10px;margin:4px 0">'+esc(sender)+' joined</div>';continue;}
|
|
1633
|
+
h+='<div style="margin-bottom:8px"><span style="font-size:10px;color:var(--dim)">'+time+'</span> <span style="font-size:11px;color:var(--amber);font-weight:600">'+esc(sender)+'</span><div style="font-size:12px;color:var(--fg);margin-top:2px;white-space:pre-wrap">'+esc(content)+'</div></div>';
|
|
1634
|
+
}
|
|
1635
|
+
el.innerHTML=h;
|
|
1636
|
+
el.scrollTop=el.scrollHeight;
|
|
1637
|
+
}
|
|
1638
|
+
|
|
1639
|
+
// Start global polling on page load
|
|
1640
|
+
setTimeout(startCollabGlobalPolling,2000);
|
|
1599
1641
|
|
|
1600
1642
|
function renderCollab(el){
|
|
1601
1643
|
var h='<div style="max-width:800px;margin:0 auto;padding:20px">';
|
|
@@ -1691,29 +1733,27 @@ function collabDeleteChannel(id){
|
|
|
1691
1733
|
function collabSelect(id){
|
|
1692
1734
|
collabActiveChannel=id;
|
|
1693
1735
|
collabLoadMessages();
|
|
1694
|
-
// Auto-refresh every
|
|
1736
|
+
// Auto-refresh every 2s while channel is selected
|
|
1695
1737
|
if(collabPolling)clearInterval(collabPolling);
|
|
1696
|
-
collabPolling=setInterval(function(){
|
|
1738
|
+
collabPolling=setInterval(function(){
|
|
1739
|
+
if(currentView==='collab'&&collabActiveChannel===id){
|
|
1740
|
+
collabLoadMessages();
|
|
1741
|
+
}
|
|
1742
|
+
},2000);
|
|
1697
1743
|
}
|
|
1698
1744
|
|
|
1699
1745
|
function collabLoadMessages(){
|
|
1700
1746
|
if(!collabActiveChannel)return;
|
|
1747
|
+
// Reset unread when viewing
|
|
1748
|
+
collabUnreadCount=0;
|
|
1749
|
+
updateCollabBadge();
|
|
1701
1750
|
apiGet('/api/collab/messages?channelId='+collabActiveChannel).then(function(r){
|
|
1702
1751
|
if(!r||!r.messages)return;
|
|
1703
1752
|
collabMessages=r.messages;
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
var m=collabMessages[i];
|
|
1709
|
-
var time=new Date(m.timestamp).toLocaleTimeString();
|
|
1710
|
-
var sender=m.senderName||m.senderFingerprint?.slice(0,8)||'Unknown';
|
|
1711
|
-
var content=m.content||m.plaintext||'[encrypted]';
|
|
1712
|
-
if(m.type==='system'){h+='<div style="text-align:center;color:var(--dim);font-size:10px;margin:4px 0">'+esc(sender)+' joined</div>';continue;}
|
|
1713
|
-
h+='<div style="margin-bottom:8px"><span style="font-size:10px;color:var(--dim)">'+time+'</span> <span style="font-size:11px;color:var(--amber);font-weight:600">'+esc(sender)+'</span><div style="font-size:12px;color:var(--fg);margin-top:2px;white-space:pre-wrap">'+esc(content)+'</div></div>';
|
|
1714
|
-
}
|
|
1715
|
-
el.innerHTML=h;
|
|
1716
|
-
el.scrollTop=el.scrollHeight;
|
|
1753
|
+
// Update last count for this channel
|
|
1754
|
+
var ch=collabChannels.find(function(c){return c.id===collabActiveChannel});
|
|
1755
|
+
if(ch)ch._lastCount=r.messages.length;
|
|
1756
|
+
renderCollabMessages();
|
|
1717
1757
|
});
|
|
1718
1758
|
}
|
|
1719
1759
|
|
|
@@ -1721,9 +1761,13 @@ function collabSend(){
|
|
|
1721
1761
|
var inp=document.getElementById('collabInput');if(!inp)return;
|
|
1722
1762
|
var msg=inp.value.trim();if(!msg||!collabActiveChannel)return;
|
|
1723
1763
|
inp.value='';
|
|
1764
|
+
// Optimistic: show message immediately
|
|
1765
|
+
collabMessages.push({senderName:'You',timestamp:new Date().toISOString(),content:msg,type:'text'});
|
|
1766
|
+
renderCollabMessages();
|
|
1724
1767
|
apiPost('/api/collab/send',{channelId:collabActiveChannel,message:msg}).then(function(r){
|
|
1725
1768
|
if(r.error){alert(r.error);return;}
|
|
1726
|
-
|
|
1769
|
+
// Reload to get server timestamp
|
|
1770
|
+
setTimeout(collabLoadMessages,500);
|
|
1727
1771
|
});
|
|
1728
1772
|
}
|
|
1729
1773
|
|
|
@@ -2135,6 +2179,27 @@ function handleDaemonEvent(msg) {
|
|
|
2135
2179
|
showToast('plan', 'Daily Plan Ready', 'Your plan for ' + msg.data.date + ' has been generated.', 10000);
|
|
2136
2180
|
if (currentView === 'plan') renderPlan(document.getElementById('content'));
|
|
2137
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;
|
|
2138
2203
|
}
|
|
2139
2204
|
}
|
|
2140
2205
|
|
|
@@ -2278,7 +2343,7 @@ init();
|
|
|
2278
2343
|
<div class="sidebar__section">
|
|
2279
2344
|
<div class="sidebar__label">AI</div>
|
|
2280
2345
|
<div class="nav-item" data-view="agents" onclick="switchView('agents')"><span class="nav-item__icon">🤖</span> Agents</div>
|
|
2281
|
-
<div class="nav-item" data-view="collab" onclick="switchView('collab')"><span class="nav-item__icon">🔒</span> AgentMessenger</div>
|
|
2346
|
+
<div class="nav-item" data-view="collab" onclick="switchView('collab')"><span class="nav-item__icon">🔒</span> AgentMessenger <span id="collabBadge" style="display:none;background:var(--red);color:#fff;font-size:9px;padding:1px 5px;border-radius:8px;margin-left:4px;font-family:var(--mono)">0</span></div>
|
|
2282
2347
|
</div>
|
|
2283
2348
|
<div class="sidebar__section">
|
|
2284
2349
|
<div class="sidebar__label">Config</div>
|