@agent-relay/dashboard-server 2.0.69 → 2.0.71
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/dist/server.js +47 -39
- package/dist/server.js.map +1 -1
- package/out/404.html +1 -1
- package/out/_next/static/chunks/{285-271fc707e03bb4c5.js → 285-52fb0aee5b6b90a6.js} +1 -1
- package/out/about.html +1 -1
- package/out/about.txt +1 -1
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +1 -1
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +1 -1
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +1 -1
- package/out/blog.html +1 -1
- package/out/blog.txt +1 -1
- package/out/careers.html +1 -1
- package/out/careers.txt +1 -1
- package/out/changelog.html +1 -1
- package/out/changelog.txt +1 -1
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +1 -1
- package/out/complete-profile.html +1 -1
- package/out/complete-profile.txt +1 -1
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +1 -1
- package/out/contact.html +1 -1
- package/out/contact.txt +1 -1
- package/out/docs.html +1 -1
- package/out/docs.txt +1 -1
- package/out/history.html +1 -1
- package/out/history.txt +1 -1
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +1 -1
- package/out/login.txt +1 -1
- package/out/metrics.html +1 -1
- package/out/metrics.txt +1 -1
- package/out/pricing.html +1 -1
- package/out/pricing.txt +1 -1
- package/out/privacy.html +1 -1
- package/out/privacy.txt +1 -1
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +1 -1
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +1 -1
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +1 -1
- package/out/providers.html +1 -1
- package/out/providers.txt +1 -1
- package/out/security.html +1 -1
- package/out/security.txt +1 -1
- package/out/signup.html +1 -1
- package/out/signup.txt +1 -1
- package/out/terms.html +1 -1
- package/out/terms.txt +1 -1
- package/package.json +1 -1
- /package/out/_next/static/{ksklx6dOwf9cjVY6ez8ZH → 4b3iV9SX2_ON0klS0h-1g}/_buildManifest.js +0 -0
- /package/out/_next/static/{ksklx6dOwf9cjVY6ez8ZH → 4b3iV9SX2_ON0klS0h-1g}/_ssgManifest.js +0 -0
package/dist/server.js
CHANGED
|
@@ -989,6 +989,8 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
989
989
|
};
|
|
990
990
|
// Set up direct message handler to forward messages to presence WebSocket
|
|
991
991
|
// This enables agents to send replies that appear in the dashboard UI
|
|
992
|
+
// Note: the relay daemon already persists messages authoritatively, so we
|
|
993
|
+
// only need to broadcast the real-time event here (no storage.saveMessage).
|
|
992
994
|
client.onMessage = (from, payload, messageId) => {
|
|
993
995
|
const body = typeof payload === 'object' && payload !== null && 'body' in payload
|
|
994
996
|
? payload.body
|
|
@@ -1000,28 +1002,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1000
1002
|
// Determine entity type: user if they have presence state, agent otherwise
|
|
1001
1003
|
const fromEntityType = senderPresence ? 'user' : 'agent';
|
|
1002
1004
|
const timestamp = new Date().toISOString();
|
|
1003
|
-
//
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
id: messageId || `dm-${crypto.randomUUID()}`,
|
|
1007
|
-
ts: Date.now(),
|
|
1008
|
-
from,
|
|
1009
|
-
to: senderName,
|
|
1010
|
-
topic: undefined,
|
|
1011
|
-
kind: 'message',
|
|
1012
|
-
body,
|
|
1013
|
-
data: {
|
|
1014
|
-
fromAvatarUrl,
|
|
1015
|
-
fromEntityType,
|
|
1016
|
-
},
|
|
1017
|
-
status: 'unread',
|
|
1018
|
-
is_urgent: false,
|
|
1019
|
-
is_broadcast: false,
|
|
1020
|
-
}).catch((err) => {
|
|
1021
|
-
console.error('[dashboard] Failed to persist direct message', err);
|
|
1022
|
-
});
|
|
1023
|
-
}
|
|
1024
|
-
// Broadcast to presence WebSocket clients so cloud/dashboard can display the message
|
|
1005
|
+
// Broadcast real-time event so the dashboard UI updates immediately.
|
|
1006
|
+
// Pass id (= messageId) so the client has a stable identifier and
|
|
1007
|
+
// doesn't need to fabricate one from Date.now().
|
|
1025
1008
|
broadcastDirectMessage({
|
|
1026
1009
|
type: 'direct_message',
|
|
1027
1010
|
targetUser: senderName,
|
|
@@ -1029,6 +1012,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1029
1012
|
fromAvatarUrl,
|
|
1030
1013
|
fromEntityType,
|
|
1031
1014
|
body,
|
|
1015
|
+
id: messageId,
|
|
1032
1016
|
messageId,
|
|
1033
1017
|
timestamp,
|
|
1034
1018
|
});
|
|
@@ -1756,15 +1740,17 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1756
1740
|
existing.avatarUrl = user.avatarUrl;
|
|
1757
1741
|
}
|
|
1758
1742
|
else {
|
|
1759
|
-
|
|
1743
|
+
// Use stable timestamps from the user/file data, not new Date(),
|
|
1744
|
+
// so getAllData() produces deterministic output for dedup comparison
|
|
1745
|
+
const stableTimestamp = user.lastSeen || user.connectedAt || new Date(remoteData.updatedAt).toISOString();
|
|
1760
1746
|
agentsMap.set(user.name, {
|
|
1761
1747
|
name: user.name,
|
|
1762
1748
|
role: 'User',
|
|
1763
1749
|
cli: 'dashboard',
|
|
1764
1750
|
messageCount: 0,
|
|
1765
1751
|
status: 'online',
|
|
1766
|
-
lastSeen:
|
|
1767
|
-
lastActive:
|
|
1752
|
+
lastSeen: stableTimestamp,
|
|
1753
|
+
lastActive: stableTimestamp,
|
|
1768
1754
|
needsAttention: false,
|
|
1769
1755
|
avatarUrl: user.avatarUrl,
|
|
1770
1756
|
});
|
|
@@ -1954,18 +1940,21 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1954
1940
|
return true;
|
|
1955
1941
|
});
|
|
1956
1942
|
// Separate AI agents from human users
|
|
1943
|
+
// Sort by name for deterministic JSON serialization (enables dedup comparison)
|
|
1957
1944
|
const filteredAgents = validEntries
|
|
1958
1945
|
.filter(agent => agent.cli !== 'dashboard')
|
|
1959
1946
|
.map(agent => ({
|
|
1960
1947
|
...agent,
|
|
1961
1948
|
isHuman: false,
|
|
1962
|
-
}))
|
|
1949
|
+
}))
|
|
1950
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1963
1951
|
const humanUsers = validEntries
|
|
1964
1952
|
.filter(agent => agent.cli === 'dashboard')
|
|
1965
1953
|
.map(agent => ({
|
|
1966
1954
|
...agent,
|
|
1967
1955
|
isHuman: true,
|
|
1968
|
-
}))
|
|
1956
|
+
}))
|
|
1957
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
1969
1958
|
return {
|
|
1970
1959
|
agents: filteredAgents,
|
|
1971
1960
|
users: humanUsers,
|
|
@@ -1978,6 +1967,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1978
1967
|
// Track clients that are still initializing (haven't received first data yet)
|
|
1979
1968
|
// This prevents race conditions where broadcastData sends before initial data is sent
|
|
1980
1969
|
const initializingClients = new WeakSet();
|
|
1970
|
+
let lastBroadcastPayload = '';
|
|
1981
1971
|
const broadcastData = async () => {
|
|
1982
1972
|
try {
|
|
1983
1973
|
const data = await getAllData();
|
|
@@ -1987,6 +1977,11 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1987
1977
|
console.warn('[dashboard] Skipping broadcast - empty payload');
|
|
1988
1978
|
return;
|
|
1989
1979
|
}
|
|
1980
|
+
// Skip broadcast if data hasn't changed since last send
|
|
1981
|
+
if (rawPayload === lastBroadcastPayload) {
|
|
1982
|
+
return;
|
|
1983
|
+
}
|
|
1984
|
+
lastBroadcastPayload = rawPayload;
|
|
1990
1985
|
// Push into buffer and wrap with sequence ID for replay support
|
|
1991
1986
|
const seq = mainMessageBuffer.push('data', rawPayload);
|
|
1992
1987
|
const payload = JSON.stringify({ seq, ...data });
|
|
@@ -2066,6 +2061,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2066
2061
|
}
|
|
2067
2062
|
return { projects: [], messages: [], connected: false };
|
|
2068
2063
|
};
|
|
2064
|
+
let lastBridgeBroadcastPayload = '';
|
|
2069
2065
|
const broadcastBridgeData = async () => {
|
|
2070
2066
|
try {
|
|
2071
2067
|
const data = await getBridgeData();
|
|
@@ -2075,6 +2071,11 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2075
2071
|
console.warn('[dashboard] Skipping bridge broadcast - empty payload');
|
|
2076
2072
|
return;
|
|
2077
2073
|
}
|
|
2074
|
+
// Skip broadcast if data hasn't changed since last send
|
|
2075
|
+
if (payload === lastBridgeBroadcastPayload) {
|
|
2076
|
+
return;
|
|
2077
|
+
}
|
|
2078
|
+
lastBridgeBroadcastPayload = payload;
|
|
2078
2079
|
wssBridge.clients.forEach(client => {
|
|
2079
2080
|
if (client.readyState === WebSocket.OPEN) {
|
|
2080
2081
|
try {
|
|
@@ -2633,7 +2634,6 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2633
2634
|
};
|
|
2634
2635
|
// Helper to broadcast direct messages to all connected clients
|
|
2635
2636
|
// This enables agent replies to appear in the dashboard UI
|
|
2636
|
-
// Broadcasts to both main wss (local mode) and wssPresence (cloud mode)
|
|
2637
2637
|
const broadcastDirectMessage = (message) => {
|
|
2638
2638
|
// Push into buffer and wrap with sequence ID for replay support
|
|
2639
2639
|
const rawPayload = JSON.stringify(message);
|
|
@@ -2647,15 +2647,20 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2647
2647
|
client.send(payload);
|
|
2648
2648
|
}
|
|
2649
2649
|
});
|
|
2650
|
-
//
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
}
|
|
2658
|
-
|
|
2650
|
+
// Only broadcast to presence WS if UserBridge does NOT have a session for
|
|
2651
|
+
// the target user. When UserBridge is active it already delivers the DM
|
|
2652
|
+
// directly to the user's WebSocket(s), so broadcasting here would duplicate.
|
|
2653
|
+
const targetHandledByBridge = userBridge?.isUserRegistered(message.targetUser) ?? false;
|
|
2654
|
+
if (!targetHandledByBridge) {
|
|
2655
|
+
const presenceClients = Array.from(wssPresence.clients).filter(c => c.readyState === WebSocket.OPEN);
|
|
2656
|
+
if (presenceClients.length > 0) {
|
|
2657
|
+
debug(`[dashboard] Broadcasting direct_message to ${presenceClients.length} presence clients (no bridge session)`);
|
|
2658
|
+
wssPresence.clients.forEach((client) => {
|
|
2659
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
2660
|
+
client.send(payload);
|
|
2661
|
+
}
|
|
2662
|
+
});
|
|
2663
|
+
}
|
|
2659
2664
|
}
|
|
2660
2665
|
};
|
|
2661
2666
|
// Helper to get online users list (without ws references)
|
|
@@ -5816,12 +5821,15 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
5816
5821
|
}
|
|
5817
5822
|
return {};
|
|
5818
5823
|
}
|
|
5819
|
-
// Watch for changes
|
|
5824
|
+
// Watch for changes - poll as a safety net for DB-backed storage mode.
|
|
5825
|
+
// Real-time updates are already handled by explicit broadcastData() calls
|
|
5826
|
+
// at every data mutation point (message send, spawn, release, cwd update, etc.).
|
|
5827
|
+
// This interval only catches external/indirect changes (presence, DB edits).
|
|
5820
5828
|
if (storage) {
|
|
5821
5829
|
setInterval(() => {
|
|
5822
5830
|
broadcastData().catch((err) => console.error('Broadcast failed', err));
|
|
5823
5831
|
broadcastBridgeData().catch((err) => console.error('Bridge broadcast failed', err));
|
|
5824
|
-
},
|
|
5832
|
+
}, 5000);
|
|
5825
5833
|
}
|
|
5826
5834
|
else {
|
|
5827
5835
|
let fsWait = null;
|