@agent-relay/dashboard-server 2.0.45 → 2.0.47
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 +112 -20
- package/dist/server.js.map +1 -1
- package/out/404.html +1 -1
- package/out/_next/static/chunks/873-604131545363afd2.js +1 -0
- 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.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 +10 -10
- package/out/_next/static/chunks/873-ca999501cec1e494.js +0 -1
- /package/out/_next/static/{CM3RazJRkqfUHgWQkurFD → 8HZf56LQSyhTavUG83J66}/_buildManifest.js +0 -0
- /package/out/_next/static/{CM3RazJRkqfUHgWQkurFD → 8HZf56LQSyhTavUG83J66}/_ssgManifest.js +0 -0
package/dist/server.js
CHANGED
|
@@ -311,10 +311,11 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
311
311
|
const disableStorage = process.env.RELAY_DISABLE_STORAGE === 'true';
|
|
312
312
|
// Use createStorageAdapter to match daemon's storage type (JSONL by default)
|
|
313
313
|
// This ensures dashboard reads from the same storage as daemon writes to
|
|
314
|
+
// Enable watchForChanges so JSONL adapter auto-reloads when daemon writes new messages
|
|
314
315
|
const storagePath = dbPath ?? path.join(dataDir, 'messages.sqlite');
|
|
315
316
|
const storage = disableStorage
|
|
316
317
|
? undefined
|
|
317
|
-
: await createStorageAdapter(storagePath);
|
|
318
|
+
: await createStorageAdapter(storagePath, { watchForChanges: true });
|
|
318
319
|
const defaultWorkspaceId = process.env.RELAY_WORKSPACE_ID ?? process.env.AGENT_RELAY_WORKSPACE_ID;
|
|
319
320
|
const resolveWorkspaceId = (req) => {
|
|
320
321
|
const fromQuery = req.query.workspaceId;
|
|
@@ -831,6 +832,8 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
831
832
|
cli: 'dashboard',
|
|
832
833
|
reconnect: true,
|
|
833
834
|
maxReconnectAttempts: 5,
|
|
835
|
+
// Dashboard is a reserved name, so we need to mark it as a system component
|
|
836
|
+
_isSystemComponent: senderName === 'Dashboard',
|
|
834
837
|
});
|
|
835
838
|
client.onError = (err) => {
|
|
836
839
|
console.error(`[dashboard] Relay client error for ${senderName}:`, err.message);
|
|
@@ -872,6 +875,52 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
872
875
|
timestamp: new Date().toISOString(),
|
|
873
876
|
});
|
|
874
877
|
};
|
|
878
|
+
// Set up direct message handler to forward messages to presence WebSocket
|
|
879
|
+
// This enables agents to send replies that appear in the dashboard UI
|
|
880
|
+
client.onMessage = (from, payload, messageId) => {
|
|
881
|
+
const body = typeof payload === 'object' && payload !== null && 'body' in payload
|
|
882
|
+
? payload.body
|
|
883
|
+
: String(payload);
|
|
884
|
+
console.log(`[dashboard] *** DIRECT MESSAGE RECEIVED *** for ${senderName}: ${from} -> ${senderName}: ${body.substring(0, 50)}...`);
|
|
885
|
+
// Look up sender's info from presence (if they're an online user)
|
|
886
|
+
const senderPresence = onlineUsers.get(from);
|
|
887
|
+
const fromAvatarUrl = senderPresence?.info.avatarUrl;
|
|
888
|
+
// Determine entity type: user if they have presence state, agent otherwise
|
|
889
|
+
const fromEntityType = senderPresence ? 'user' : 'agent';
|
|
890
|
+
const timestamp = new Date().toISOString();
|
|
891
|
+
// Persist the message to storage so it survives page refresh
|
|
892
|
+
if (storage) {
|
|
893
|
+
storage.saveMessage({
|
|
894
|
+
id: messageId || `dm-${crypto.randomUUID()}`,
|
|
895
|
+
ts: Date.now(),
|
|
896
|
+
from,
|
|
897
|
+
to: senderName,
|
|
898
|
+
topic: undefined,
|
|
899
|
+
kind: 'message',
|
|
900
|
+
body,
|
|
901
|
+
data: {
|
|
902
|
+
fromAvatarUrl,
|
|
903
|
+
fromEntityType,
|
|
904
|
+
},
|
|
905
|
+
status: 'unread',
|
|
906
|
+
is_urgent: false,
|
|
907
|
+
is_broadcast: false,
|
|
908
|
+
}).catch((err) => {
|
|
909
|
+
console.error('[dashboard] Failed to persist direct message', err);
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
// Broadcast to presence WebSocket clients so cloud/dashboard can display the message
|
|
913
|
+
broadcastDirectMessage({
|
|
914
|
+
type: 'direct_message',
|
|
915
|
+
targetUser: senderName,
|
|
916
|
+
from,
|
|
917
|
+
fromAvatarUrl,
|
|
918
|
+
fromEntityType,
|
|
919
|
+
body,
|
|
920
|
+
messageId,
|
|
921
|
+
timestamp,
|
|
922
|
+
});
|
|
923
|
+
};
|
|
875
924
|
try {
|
|
876
925
|
await client.connect();
|
|
877
926
|
relayClients.set(senderName, client);
|
|
@@ -892,8 +941,8 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
892
941
|
return connectionPromise;
|
|
893
942
|
};
|
|
894
943
|
// Start default relay client connection (non-blocking)
|
|
895
|
-
// Use '
|
|
896
|
-
getRelayClient('
|
|
944
|
+
// Use 'Dashboard' to avoid conflicts with agents named 'Dashboard'
|
|
945
|
+
getRelayClient('Dashboard').catch(() => { });
|
|
897
946
|
// User bridge for human-to-human and human-to-agent messaging
|
|
898
947
|
userBridge = new UserBridge({
|
|
899
948
|
socketPath,
|
|
@@ -1105,10 +1154,10 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1105
1154
|
}
|
|
1106
1155
|
targets = [to];
|
|
1107
1156
|
}
|
|
1108
|
-
// Always use '
|
|
1157
|
+
// Always use 'Dashboard' client to avoid name conflicts with user agents
|
|
1109
1158
|
// (underscore prefix indicates system client, prevents collision if user names an agent "Dashboard")
|
|
1110
1159
|
// The sender name is preserved in message history/logs but not used for the relay connection
|
|
1111
|
-
const relayClient = await getRelayClient('
|
|
1160
|
+
const relayClient = await getRelayClient('Dashboard');
|
|
1112
1161
|
if (!relayClient || relayClient.state !== 'READY') {
|
|
1113
1162
|
return res.status(503).json({ error: 'Relay daemon not connected' });
|
|
1114
1163
|
}
|
|
@@ -1126,7 +1175,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1126
1175
|
}
|
|
1127
1176
|
// Include attachments, channel context, and sender info in the message data field
|
|
1128
1177
|
// For broadcasts (to='*'), include channel: 'general' so replies can be routed back
|
|
1129
|
-
// For dashboard messages, include senderName so frontend can display actual user instead of '
|
|
1178
|
+
// For dashboard messages, include senderName so frontend can display actual user instead of 'Dashboard'
|
|
1130
1179
|
const isBroadcast = targets.length === 1 && targets[0] === '*';
|
|
1131
1180
|
const messageData = {};
|
|
1132
1181
|
if (attachments && attachments.length > 0) {
|
|
@@ -1135,7 +1184,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1135
1184
|
if (isBroadcast) {
|
|
1136
1185
|
messageData.channel = 'general';
|
|
1137
1186
|
}
|
|
1138
|
-
// Include actual sender name for dashboard messages (relay client uses '
|
|
1187
|
+
// Include actual sender name for dashboard messages (relay client uses 'Dashboard' but
|
|
1139
1188
|
// we want the real user's name displayed in message history)
|
|
1140
1189
|
if (senderName) {
|
|
1141
1190
|
messageData.senderName = senderName;
|
|
@@ -1149,6 +1198,25 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1149
1198
|
allSent = false;
|
|
1150
1199
|
console.error(`[dashboard] Failed to send message to ${target}`);
|
|
1151
1200
|
}
|
|
1201
|
+
else if (storage) {
|
|
1202
|
+
// Persist outgoing message to storage so it survives page refresh
|
|
1203
|
+
const messageId = `out-${crypto.randomUUID()}`;
|
|
1204
|
+
storage.saveMessage({
|
|
1205
|
+
id: messageId,
|
|
1206
|
+
ts: Date.now(),
|
|
1207
|
+
from: senderName || 'Dashboard',
|
|
1208
|
+
to: target,
|
|
1209
|
+
topic: thread,
|
|
1210
|
+
kind: 'message',
|
|
1211
|
+
body: message,
|
|
1212
|
+
data: hasMessageData ? messageData : undefined,
|
|
1213
|
+
status: 'read', // Outgoing messages are already "read" by sender
|
|
1214
|
+
is_urgent: false,
|
|
1215
|
+
is_broadcast: isBroadcast,
|
|
1216
|
+
}).catch((err) => {
|
|
1217
|
+
console.error('[dashboard] Failed to persist outgoing message', err);
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1152
1220
|
}
|
|
1153
1221
|
if (allSent) {
|
|
1154
1222
|
// Broadcast updated data to all connected clients so they see the sent message
|
|
@@ -1408,9 +1476,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1408
1476
|
if ('channel' in row.data) {
|
|
1409
1477
|
channel = row.data.channel;
|
|
1410
1478
|
}
|
|
1411
|
-
// For dashboard messages sent via
|
|
1479
|
+
// For dashboard messages sent via Dashboard, use the actual sender name
|
|
1412
1480
|
// This provides proper attribution in message history
|
|
1413
|
-
if ('senderName' in row.data && row.from === '
|
|
1481
|
+
if ('senderName' in row.data && row.from === 'Dashboard') {
|
|
1414
1482
|
effectiveFrom = row.data.senderName;
|
|
1415
1483
|
}
|
|
1416
1484
|
}
|
|
@@ -1441,7 +1509,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1441
1509
|
// Fallback to daemon via RelayClient (for cases without local storage)
|
|
1442
1510
|
// Uses queryMessages if available (SDK >= 2.0.26)
|
|
1443
1511
|
try {
|
|
1444
|
-
const client = await getRelayClient('
|
|
1512
|
+
const client = await getRelayClient('Dashboard');
|
|
1445
1513
|
const clientAny = client;
|
|
1446
1514
|
if (client && client.state === 'READY' && typeof clientAny.queryMessages === 'function') {
|
|
1447
1515
|
const messages = await clientAny.queryMessages({ limit: 100, order: 'desc' });
|
|
@@ -1710,8 +1778,8 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1710
1778
|
// Exclude agents starting with __ (internal/system agents)
|
|
1711
1779
|
if (agent.name.startsWith('__'))
|
|
1712
1780
|
return false;
|
|
1713
|
-
// Exclude
|
|
1714
|
-
if (agent.name === '
|
|
1781
|
+
// Exclude Dashboard (system client for sending dashboard messages)
|
|
1782
|
+
if (agent.name === 'Dashboard')
|
|
1715
1783
|
return false;
|
|
1716
1784
|
// Exclude agents without a proper CLI (improperly registered or stale)
|
|
1717
1785
|
if (!agent.cli || agent.cli === 'Unknown')
|
|
@@ -2190,6 +2258,30 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2190
2258
|
}
|
|
2191
2259
|
});
|
|
2192
2260
|
};
|
|
2261
|
+
// Helper to broadcast direct messages to all connected clients
|
|
2262
|
+
// This enables agent replies to appear in the dashboard UI
|
|
2263
|
+
// Broadcasts to both main wss (local mode) and wssPresence (cloud mode)
|
|
2264
|
+
const broadcastDirectMessage = (message) => {
|
|
2265
|
+
const payload = JSON.stringify(message);
|
|
2266
|
+
// Broadcast to main WebSocket clients (local mode)
|
|
2267
|
+
const mainClients = Array.from(wss.clients).filter(c => c.readyState === WebSocket.OPEN);
|
|
2268
|
+
console.log(`[dashboard] Broadcasting direct_message to ${mainClients.length} main clients`);
|
|
2269
|
+
wss.clients.forEach((client) => {
|
|
2270
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
2271
|
+
client.send(payload);
|
|
2272
|
+
}
|
|
2273
|
+
});
|
|
2274
|
+
// Also broadcast to presence WebSocket clients (cloud mode)
|
|
2275
|
+
const presenceClients = Array.from(wssPresence.clients).filter(c => c.readyState === WebSocket.OPEN);
|
|
2276
|
+
if (presenceClients.length > 0) {
|
|
2277
|
+
console.log(`[dashboard] Broadcasting direct_message to ${presenceClients.length} presence clients`);
|
|
2278
|
+
wssPresence.clients.forEach((client) => {
|
|
2279
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
2280
|
+
client.send(payload);
|
|
2281
|
+
}
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
};
|
|
2193
2285
|
// Helper to get online users list (without ws references)
|
|
2194
2286
|
const getOnlineUsersList = () => {
|
|
2195
2287
|
return Array.from(onlineUsers.values()).map((state) => state.info);
|
|
@@ -3245,8 +3337,8 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
3245
3337
|
const uptime = process.uptime();
|
|
3246
3338
|
const memUsage = process.memoryUsage();
|
|
3247
3339
|
const socketExists = fs.existsSync(socketPath);
|
|
3248
|
-
// Check relay client connectivity (check if default
|
|
3249
|
-
const defaultClient = relayClients.get('
|
|
3340
|
+
// Check relay client connectivity (check if default Dashboard client is connected)
|
|
3341
|
+
const defaultClient = relayClients.get('Dashboard');
|
|
3250
3342
|
const relayConnected = defaultClient?.state === 'READY';
|
|
3251
3343
|
// If socket doesn't exist, daemon may not be running properly
|
|
3252
3344
|
if (!socketExists) {
|
|
@@ -3272,7 +3364,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
3272
3364
|
const uptime = process.uptime();
|
|
3273
3365
|
const memUsage = process.memoryUsage();
|
|
3274
3366
|
const socketExists = fs.existsSync(socketPath);
|
|
3275
|
-
const defaultClient = relayClients.get('
|
|
3367
|
+
const defaultClient = relayClients.get('Dashboard');
|
|
3276
3368
|
const relayConnected = defaultClient?.state === 'READY';
|
|
3277
3369
|
if (!socketExists) {
|
|
3278
3370
|
return res.status(503).json({
|
|
@@ -4818,7 +4910,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
4818
4910
|
}
|
|
4819
4911
|
// Try to send message to agent
|
|
4820
4912
|
try {
|
|
4821
|
-
const client = await getRelayClient('
|
|
4913
|
+
const client = await getRelayClient('Dashboard');
|
|
4822
4914
|
if (client) {
|
|
4823
4915
|
await client.sendMessage(agentName, responseMessage, 'message');
|
|
4824
4916
|
}
|
|
@@ -4848,7 +4940,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
4848
4940
|
responseMessage += `\nReason: ${reason}`;
|
|
4849
4941
|
}
|
|
4850
4942
|
try {
|
|
4851
|
-
const client = await getRelayClient('
|
|
4943
|
+
const client = await getRelayClient('Dashboard');
|
|
4852
4944
|
if (client) {
|
|
4853
4945
|
await client.sendMessage(agentName, responseMessage, 'message');
|
|
4854
4946
|
}
|
|
@@ -5036,7 +5128,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
5036
5128
|
tasks.set(task.id, task);
|
|
5037
5129
|
// Send task to agent via relay
|
|
5038
5130
|
try {
|
|
5039
|
-
const client = await getRelayClient('
|
|
5131
|
+
const client = await getRelayClient('Dashboard');
|
|
5040
5132
|
if (client) {
|
|
5041
5133
|
const taskMessage = `TASK ASSIGNED [${priority.toUpperCase()}]: ${title}\n\n${description || 'No additional details.'}`;
|
|
5042
5134
|
await client.sendMessage(agentName, taskMessage, 'message');
|
|
@@ -5084,7 +5176,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
5084
5176
|
// Notify agent of cancellation if task is still active
|
|
5085
5177
|
if (task.status === 'pending' || task.status === 'assigned' || task.status === 'in_progress') {
|
|
5086
5178
|
try {
|
|
5087
|
-
const client = await getRelayClient('
|
|
5179
|
+
const client = await getRelayClient('Dashboard');
|
|
5088
5180
|
if (client) {
|
|
5089
5181
|
await client.sendMessage(task.agentName, `TASK CANCELLED: ${task.title}`, 'message');
|
|
5090
5182
|
}
|
|
@@ -5158,7 +5250,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
5158
5250
|
return res.status(400).json({ success: false, error: 'Message content is required' });
|
|
5159
5251
|
}
|
|
5160
5252
|
try {
|
|
5161
|
-
const client = await getRelayClient('
|
|
5253
|
+
const client = await getRelayClient('Dashboard');
|
|
5162
5254
|
if (!client) {
|
|
5163
5255
|
return res.status(503).json({
|
|
5164
5256
|
success: false,
|