@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.
Files changed (56) hide show
  1. package/dist/server.js +112 -20
  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 → 8HZf56LQSyhTavUG83J66}/_buildManifest.js +0 -0
  56. /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 '_DashboardUI' to avoid conflicts with agents named 'Dashboard'
896
- getRelayClient('_DashboardUI').catch(() => { });
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 '_DashboardUI' client to avoid name conflicts with user agents
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('_DashboardUI');
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 '_DashboardUI'
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 '_DashboardUI' but
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 _DashboardUI, use the actual sender name
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 === '_DashboardUI') {
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('_DashboardUI');
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 _DashboardUI (system client for sending dashboard messages)
1714
- if (agent.name === '_DashboardUI')
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 _DashboardUI client is connected)
3249
- const defaultClient = relayClients.get('_DashboardUI');
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('_DashboardUI');
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('_DashboardUI');
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('_DashboardUI');
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('_DashboardUI');
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('_DashboardUI');
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('_DashboardUI');
5253
+ const client = await getRelayClient('Dashboard');
5162
5254
  if (!client) {
5163
5255
  return res.status(503).json({
5164
5256
  success: false,