@agent-relay/dashboard-server 2.0.45 → 2.0.46

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