@agent-relay/dashboard-server 2.0.47 → 2.0.48

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 (54) hide show
  1. package/dist/server.js +145 -6
  2. package/dist/server.js.map +1 -1
  3. package/out/404.html +1 -1
  4. package/out/about.html +1 -1
  5. package/out/about.txt +1 -1
  6. package/out/app/onboarding.html +1 -1
  7. package/out/app/onboarding.txt +1 -1
  8. package/out/app.html +1 -1
  9. package/out/app.txt +1 -1
  10. package/out/blog.html +1 -1
  11. package/out/blog.txt +1 -1
  12. package/out/careers.html +1 -1
  13. package/out/careers.txt +1 -1
  14. package/out/changelog.html +1 -1
  15. package/out/changelog.txt +1 -1
  16. package/out/cloud/link.html +1 -1
  17. package/out/cloud/link.txt +1 -1
  18. package/out/complete-profile.html +1 -1
  19. package/out/complete-profile.txt +1 -1
  20. package/out/connect-repos.html +1 -1
  21. package/out/connect-repos.txt +1 -1
  22. package/out/contact.html +1 -1
  23. package/out/contact.txt +1 -1
  24. package/out/docs.html +1 -1
  25. package/out/docs.txt +1 -1
  26. package/out/history.html +1 -1
  27. package/out/history.txt +1 -1
  28. package/out/index.html +1 -1
  29. package/out/index.txt +1 -1
  30. package/out/login.html +1 -1
  31. package/out/login.txt +1 -1
  32. package/out/metrics.html +1 -1
  33. package/out/metrics.txt +1 -1
  34. package/out/pricing.html +1 -1
  35. package/out/pricing.txt +1 -1
  36. package/out/privacy.html +1 -1
  37. package/out/privacy.txt +1 -1
  38. package/out/providers/setup/claude.html +1 -1
  39. package/out/providers/setup/claude.txt +1 -1
  40. package/out/providers/setup/codex.html +1 -1
  41. package/out/providers/setup/codex.txt +1 -1
  42. package/out/providers/setup/cursor.html +1 -1
  43. package/out/providers/setup/cursor.txt +1 -1
  44. package/out/providers.html +1 -1
  45. package/out/providers.txt +1 -1
  46. package/out/security.html +1 -1
  47. package/out/security.txt +1 -1
  48. package/out/signup.html +1 -1
  49. package/out/signup.txt +1 -1
  50. package/out/terms.html +1 -1
  51. package/out/terms.txt +1 -1
  52. package/package.json +10 -10
  53. /package/out/_next/static/{8HZf56LQSyhTavUG83J66 → HwEoE2BFhgZdESAQLY9dG}/_buildManifest.js +0 -0
  54. /package/out/_next/static/{8HZf56LQSyhTavUG83J66 → HwEoE2BFhgZdESAQLY9dG}/_ssgManifest.js +0 -0
package/dist/server.js CHANGED
@@ -549,6 +549,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
549
549
  });
550
550
  // Track log subscriptions: agentName -> Set of WebSocket clients
551
551
  const logSubscriptions = new Map();
552
+ // Track file watchers for externally-spawned worker logs (module scope to avoid duplicates)
553
+ const fileWatchers = new Map();
554
+ const fileLastSize = new Map();
552
555
  // Track alive status for ping/pong keepalive on main dashboard connections
553
556
  // This prevents TCP/proxy timeouts from killing idle workspace connections
554
557
  const mainClientAlive = new WeakMap();
@@ -1743,6 +1746,23 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
1743
1746
  }
1744
1747
  }
1745
1748
  }
1749
+ // Also check workers.json for externally-spawned workers (e.g., from agentswarm)
1750
+ // These workers have log files but weren't spawned by the dashboard's spawner
1751
+ const workersJsonPath = path.join(teamDir, 'workers.json');
1752
+ if (fs.existsSync(workersJsonPath)) {
1753
+ try {
1754
+ const workersData = JSON.parse(fs.readFileSync(workersJsonPath, 'utf-8'));
1755
+ for (const worker of workersData.workers || []) {
1756
+ const agent = agentsMap.get(worker.name);
1757
+ if (agent && !agent.isSpawned && worker.logFile && fs.existsSync(worker.logFile)) {
1758
+ agent.isSpawned = true; // Mark as spawned so log button appears
1759
+ }
1760
+ }
1761
+ }
1762
+ catch (err) {
1763
+ // Ignore errors reading workers.json
1764
+ }
1765
+ }
1746
1766
  // Set team from teams.json for agents that don't have a team yet
1747
1767
  // This ensures agents defined in teams.json are associated with their team
1748
1768
  // even if they weren't spawned via auto-spawn
@@ -2039,6 +2059,89 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
2039
2059
  return false;
2040
2060
  }
2041
2061
  };
2062
+ const getExternalWorkerInfo = (agentName) => {
2063
+ const workersPath = path.join(teamDir, 'workers.json');
2064
+ if (!fs.existsSync(workersPath))
2065
+ return null;
2066
+ try {
2067
+ const data = JSON.parse(fs.readFileSync(workersPath, 'utf-8'));
2068
+ const worker = data.workers?.find((w) => w.name === agentName);
2069
+ return worker ?? null;
2070
+ }
2071
+ catch {
2072
+ return null;
2073
+ }
2074
+ };
2075
+ // Helper to read logs from a log file (for externally-spawned workers)
2076
+ const readLogsFromFile = (logFile, limit = 5000) => {
2077
+ if (!fs.existsSync(logFile))
2078
+ return [];
2079
+ try {
2080
+ const content = fs.readFileSync(logFile, 'utf-8');
2081
+ const lines = content.split('\n');
2082
+ // Return last `limit` lines
2083
+ return lines.slice(-limit);
2084
+ }
2085
+ catch {
2086
+ return [];
2087
+ }
2088
+ };
2089
+ // Helper to start watching a log file for live updates
2090
+ const watchLogFile = (agentName, logFile) => {
2091
+ if (fileWatchers.has(agentName))
2092
+ return; // Already watching
2093
+ if (!fs.existsSync(logFile))
2094
+ return;
2095
+ try {
2096
+ // Track current file size for incremental reads
2097
+ const stats = fs.statSync(logFile);
2098
+ fileLastSize.set(agentName, stats.size);
2099
+ const watcher = fs.watch(logFile, (eventType) => {
2100
+ if (eventType !== 'change')
2101
+ return;
2102
+ const clients = logSubscriptions.get(agentName);
2103
+ if (!clients || clients.size === 0) {
2104
+ // No subscribers, stop watching
2105
+ watcher.close();
2106
+ fileWatchers.delete(agentName);
2107
+ fileLastSize.delete(agentName);
2108
+ return;
2109
+ }
2110
+ try {
2111
+ const newStats = fs.statSync(logFile);
2112
+ const lastSize = fileLastSize.get(agentName) || 0;
2113
+ if (newStats.size > lastSize) {
2114
+ // Read only the new content
2115
+ const fd = fs.openSync(logFile, 'r');
2116
+ const buffer = Buffer.alloc(newStats.size - lastSize);
2117
+ fs.readSync(fd, buffer, 0, buffer.length, lastSize);
2118
+ fs.closeSync(fd);
2119
+ const newContent = buffer.toString('utf-8');
2120
+ fileLastSize.set(agentName, newStats.size);
2121
+ // Broadcast to subscribed clients
2122
+ const payload = JSON.stringify({
2123
+ type: 'output',
2124
+ agent: agentName,
2125
+ data: newContent,
2126
+ timestamp: new Date().toISOString(),
2127
+ });
2128
+ for (const client of clients) {
2129
+ if (client.readyState === WebSocket.OPEN) {
2130
+ client.send(payload);
2131
+ }
2132
+ }
2133
+ }
2134
+ }
2135
+ catch (err) {
2136
+ console.error(`[dashboard] Error reading log file updates for ${agentName}:`, err);
2137
+ }
2138
+ });
2139
+ fileWatchers.set(agentName, watcher);
2140
+ }
2141
+ catch (err) {
2142
+ console.error(`[dashboard] Failed to watch log file for ${agentName}:`, err);
2143
+ }
2144
+ };
2042
2145
  // Helper to subscribe to an agent (async to handle spawn timing)
2043
2146
  const subscribeToAgent = async (agentName) => {
2044
2147
  let isSpawned = spawner?.hasWorker(agentName) ?? false;
@@ -2093,12 +2196,27 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
2093
2196
  }));
2094
2197
  }
2095
2198
  else {
2096
- // For daemon-connected agents, explain that PTY output isn't available
2097
- ws.send(JSON.stringify({
2098
- type: 'history',
2099
- agent: agentName,
2100
- lines: [`[${agentName} is a daemon-connected agent - PTY output not available. Showing relay messages only.]`],
2101
- }));
2199
+ // Check if this is an externally-spawned worker with a log file
2200
+ const externalWorker = getExternalWorkerInfo(agentName);
2201
+ if (externalWorker?.logFile && fs.existsSync(externalWorker.logFile)) {
2202
+ // Read logs from the external worker's log file
2203
+ const lines = readLogsFromFile(externalWorker.logFile, 5000);
2204
+ ws.send(JSON.stringify({
2205
+ type: 'history',
2206
+ agent: agentName,
2207
+ lines,
2208
+ }));
2209
+ // Start watching the log file for live updates
2210
+ watchLogFile(agentName, externalWorker.logFile);
2211
+ }
2212
+ else {
2213
+ // For daemon-connected agents without log files, explain that PTY output isn't available
2214
+ ws.send(JSON.stringify({
2215
+ type: 'history',
2216
+ agent: agentName,
2217
+ lines: [`[${agentName} is a daemon-connected agent - PTY output not available. Showing relay messages only.]`],
2218
+ }));
2219
+ }
2102
2220
  }
2103
2221
  ws.send(JSON.stringify({
2104
2222
  type: 'subscribed',
@@ -2129,6 +2247,16 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
2129
2247
  const agentName = msg.unsubscribe;
2130
2248
  clientSubscriptions.delete(agentName);
2131
2249
  logSubscriptions.get(agentName)?.delete(ws);
2250
+ // Clean up file watcher if no more subscribers
2251
+ const remainingClients = logSubscriptions.get(agentName);
2252
+ if (!remainingClients || remainingClients.size === 0) {
2253
+ const watcher = fileWatchers.get(agentName);
2254
+ if (watcher) {
2255
+ watcher.close();
2256
+ fileWatchers.delete(agentName);
2257
+ fileLastSize.delete(agentName);
2258
+ }
2259
+ }
2132
2260
  console.log(`[dashboard] Client unsubscribed from logs for: ${agentName}`);
2133
2261
  ws.send(JSON.stringify({
2134
2262
  type: 'unsubscribed',
@@ -2174,6 +2302,17 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
2174
2302
  // Clean up subscriptions on disconnect
2175
2303
  for (const agentName of clientSubscriptions) {
2176
2304
  logSubscriptions.get(agentName)?.delete(ws);
2305
+ // Clean up file watchers if no more subscribers
2306
+ const remainingClients = logSubscriptions.get(agentName);
2307
+ if (!remainingClients || remainingClients.size === 0) {
2308
+ const watcher = fileWatchers.get(agentName);
2309
+ if (watcher) {
2310
+ watcher.close();
2311
+ fileWatchers.delete(agentName);
2312
+ fileLastSize.delete(agentName);
2313
+ console.log(`[dashboard] Stopped watching log file for: ${agentName}`);
2314
+ }
2315
+ }
2177
2316
  }
2178
2317
  const reasonStr = reason?.toString() || 'no reason';
2179
2318
  console.log(`[dashboard] Logs WebSocket client disconnected (code: ${code}, reason: ${reasonStr})`);