@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.
- package/dist/server.js +145 -6
- package/dist/server.js.map +1 -1
- package/out/404.html +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 +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 +1 -1
- 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/{8HZf56LQSyhTavUG83J66 → HwEoE2BFhgZdESAQLY9dG}/_buildManifest.js +0 -0
- /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
|
-
//
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
lines
|
|
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})`);
|