@agent-relay/dashboard-server 2.0.47 → 2.0.49
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 +148 -25
- package/dist/server.js.map +1 -1
- package/dist/start.js +33 -2
- package/dist/start.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 → SfyoLSw2xdXgcKQ_dT07o}/_buildManifest.js +0 -0
- /package/out/_next/static/{8HZf56LQSyhTavUG83J66 → SfyoLSw2xdXgcKQ_dT07o}/_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();
|
|
@@ -1191,6 +1194,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1191
1194
|
}
|
|
1192
1195
|
const hasMessageData = Object.keys(messageData).length > 0;
|
|
1193
1196
|
// Send to all targets (single agent, team members, or broadcast)
|
|
1197
|
+
// Note: We do NOT persist outgoing messages here because the relay daemon
|
|
1198
|
+
// already persists them when delivered (in router.persistDeliverEnvelope).
|
|
1199
|
+
// Persisting here would cause duplicate messages in storage.
|
|
1194
1200
|
let allSent = true;
|
|
1195
1201
|
for (const target of targets) {
|
|
1196
1202
|
const sent = relayClient.sendMessage(target, message, 'message', hasMessageData ? messageData : undefined, thread);
|
|
@@ -1198,25 +1204,6 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1198
1204
|
allSent = false;
|
|
1199
1205
|
console.error(`[dashboard] Failed to send message to ${target}`);
|
|
1200
1206
|
}
|
|
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
|
-
}
|
|
1220
1207
|
}
|
|
1221
1208
|
if (allSent) {
|
|
1222
1209
|
// Broadcast updated data to all connected clients so they see the sent message
|
|
@@ -1743,6 +1730,23 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1743
1730
|
}
|
|
1744
1731
|
}
|
|
1745
1732
|
}
|
|
1733
|
+
// Also check workers.json for externally-spawned workers (e.g., from agentswarm)
|
|
1734
|
+
// These workers have log files but weren't spawned by the dashboard's spawner
|
|
1735
|
+
const workersJsonPath = path.join(teamDir, 'workers.json');
|
|
1736
|
+
if (fs.existsSync(workersJsonPath)) {
|
|
1737
|
+
try {
|
|
1738
|
+
const workersData = JSON.parse(fs.readFileSync(workersJsonPath, 'utf-8'));
|
|
1739
|
+
for (const worker of workersData.workers || []) {
|
|
1740
|
+
const agent = agentsMap.get(worker.name);
|
|
1741
|
+
if (agent && !agent.isSpawned && worker.logFile && fs.existsSync(worker.logFile)) {
|
|
1742
|
+
agent.isSpawned = true; // Mark as spawned so log button appears
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
catch (err) {
|
|
1747
|
+
// Ignore errors reading workers.json
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1746
1750
|
// Set team from teams.json for agents that don't have a team yet
|
|
1747
1751
|
// This ensures agents defined in teams.json are associated with their team
|
|
1748
1752
|
// even if they weren't spawned via auto-spawn
|
|
@@ -2039,6 +2043,89 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2039
2043
|
return false;
|
|
2040
2044
|
}
|
|
2041
2045
|
};
|
|
2046
|
+
const getExternalWorkerInfo = (agentName) => {
|
|
2047
|
+
const workersPath = path.join(teamDir, 'workers.json');
|
|
2048
|
+
if (!fs.existsSync(workersPath))
|
|
2049
|
+
return null;
|
|
2050
|
+
try {
|
|
2051
|
+
const data = JSON.parse(fs.readFileSync(workersPath, 'utf-8'));
|
|
2052
|
+
const worker = data.workers?.find((w) => w.name === agentName);
|
|
2053
|
+
return worker ?? null;
|
|
2054
|
+
}
|
|
2055
|
+
catch {
|
|
2056
|
+
return null;
|
|
2057
|
+
}
|
|
2058
|
+
};
|
|
2059
|
+
// Helper to read logs from a log file (for externally-spawned workers)
|
|
2060
|
+
const readLogsFromFile = (logFile, limit = 5000) => {
|
|
2061
|
+
if (!fs.existsSync(logFile))
|
|
2062
|
+
return [];
|
|
2063
|
+
try {
|
|
2064
|
+
const content = fs.readFileSync(logFile, 'utf-8');
|
|
2065
|
+
const lines = content.split('\n');
|
|
2066
|
+
// Return last `limit` lines
|
|
2067
|
+
return lines.slice(-limit);
|
|
2068
|
+
}
|
|
2069
|
+
catch {
|
|
2070
|
+
return [];
|
|
2071
|
+
}
|
|
2072
|
+
};
|
|
2073
|
+
// Helper to start watching a log file for live updates
|
|
2074
|
+
const watchLogFile = (agentName, logFile) => {
|
|
2075
|
+
if (fileWatchers.has(agentName))
|
|
2076
|
+
return; // Already watching
|
|
2077
|
+
if (!fs.existsSync(logFile))
|
|
2078
|
+
return;
|
|
2079
|
+
try {
|
|
2080
|
+
// Track current file size for incremental reads
|
|
2081
|
+
const stats = fs.statSync(logFile);
|
|
2082
|
+
fileLastSize.set(agentName, stats.size);
|
|
2083
|
+
const watcher = fs.watch(logFile, (eventType) => {
|
|
2084
|
+
if (eventType !== 'change')
|
|
2085
|
+
return;
|
|
2086
|
+
const clients = logSubscriptions.get(agentName);
|
|
2087
|
+
if (!clients || clients.size === 0) {
|
|
2088
|
+
// No subscribers, stop watching
|
|
2089
|
+
watcher.close();
|
|
2090
|
+
fileWatchers.delete(agentName);
|
|
2091
|
+
fileLastSize.delete(agentName);
|
|
2092
|
+
return;
|
|
2093
|
+
}
|
|
2094
|
+
try {
|
|
2095
|
+
const newStats = fs.statSync(logFile);
|
|
2096
|
+
const lastSize = fileLastSize.get(agentName) || 0;
|
|
2097
|
+
if (newStats.size > lastSize) {
|
|
2098
|
+
// Read only the new content
|
|
2099
|
+
const fd = fs.openSync(logFile, 'r');
|
|
2100
|
+
const buffer = Buffer.alloc(newStats.size - lastSize);
|
|
2101
|
+
fs.readSync(fd, buffer, 0, buffer.length, lastSize);
|
|
2102
|
+
fs.closeSync(fd);
|
|
2103
|
+
const newContent = buffer.toString('utf-8');
|
|
2104
|
+
fileLastSize.set(agentName, newStats.size);
|
|
2105
|
+
// Broadcast to subscribed clients
|
|
2106
|
+
const payload = JSON.stringify({
|
|
2107
|
+
type: 'output',
|
|
2108
|
+
agent: agentName,
|
|
2109
|
+
data: newContent,
|
|
2110
|
+
timestamp: new Date().toISOString(),
|
|
2111
|
+
});
|
|
2112
|
+
for (const client of clients) {
|
|
2113
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
2114
|
+
client.send(payload);
|
|
2115
|
+
}
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
catch (err) {
|
|
2120
|
+
console.error(`[dashboard] Error reading log file updates for ${agentName}:`, err);
|
|
2121
|
+
}
|
|
2122
|
+
});
|
|
2123
|
+
fileWatchers.set(agentName, watcher);
|
|
2124
|
+
}
|
|
2125
|
+
catch (err) {
|
|
2126
|
+
console.error(`[dashboard] Failed to watch log file for ${agentName}:`, err);
|
|
2127
|
+
}
|
|
2128
|
+
};
|
|
2042
2129
|
// Helper to subscribe to an agent (async to handle spawn timing)
|
|
2043
2130
|
const subscribeToAgent = async (agentName) => {
|
|
2044
2131
|
let isSpawned = spawner?.hasWorker(agentName) ?? false;
|
|
@@ -2093,12 +2180,27 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2093
2180
|
}));
|
|
2094
2181
|
}
|
|
2095
2182
|
else {
|
|
2096
|
-
//
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
lines
|
|
2101
|
-
|
|
2183
|
+
// Check if this is an externally-spawned worker with a log file
|
|
2184
|
+
const externalWorker = getExternalWorkerInfo(agentName);
|
|
2185
|
+
if (externalWorker?.logFile && fs.existsSync(externalWorker.logFile)) {
|
|
2186
|
+
// Read logs from the external worker's log file
|
|
2187
|
+
const lines = readLogsFromFile(externalWorker.logFile, 5000);
|
|
2188
|
+
ws.send(JSON.stringify({
|
|
2189
|
+
type: 'history',
|
|
2190
|
+
agent: agentName,
|
|
2191
|
+
lines,
|
|
2192
|
+
}));
|
|
2193
|
+
// Start watching the log file for live updates
|
|
2194
|
+
watchLogFile(agentName, externalWorker.logFile);
|
|
2195
|
+
}
|
|
2196
|
+
else {
|
|
2197
|
+
// For daemon-connected agents without log files, explain that PTY output isn't available
|
|
2198
|
+
ws.send(JSON.stringify({
|
|
2199
|
+
type: 'history',
|
|
2200
|
+
agent: agentName,
|
|
2201
|
+
lines: [`[${agentName} is a daemon-connected agent - PTY output not available. Showing relay messages only.]`],
|
|
2202
|
+
}));
|
|
2203
|
+
}
|
|
2102
2204
|
}
|
|
2103
2205
|
ws.send(JSON.stringify({
|
|
2104
2206
|
type: 'subscribed',
|
|
@@ -2129,6 +2231,16 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2129
2231
|
const agentName = msg.unsubscribe;
|
|
2130
2232
|
clientSubscriptions.delete(agentName);
|
|
2131
2233
|
logSubscriptions.get(agentName)?.delete(ws);
|
|
2234
|
+
// Clean up file watcher if no more subscribers
|
|
2235
|
+
const remainingClients = logSubscriptions.get(agentName);
|
|
2236
|
+
if (!remainingClients || remainingClients.size === 0) {
|
|
2237
|
+
const watcher = fileWatchers.get(agentName);
|
|
2238
|
+
if (watcher) {
|
|
2239
|
+
watcher.close();
|
|
2240
|
+
fileWatchers.delete(agentName);
|
|
2241
|
+
fileLastSize.delete(agentName);
|
|
2242
|
+
}
|
|
2243
|
+
}
|
|
2132
2244
|
console.log(`[dashboard] Client unsubscribed from logs for: ${agentName}`);
|
|
2133
2245
|
ws.send(JSON.stringify({
|
|
2134
2246
|
type: 'unsubscribed',
|
|
@@ -2174,6 +2286,17 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2174
2286
|
// Clean up subscriptions on disconnect
|
|
2175
2287
|
for (const agentName of clientSubscriptions) {
|
|
2176
2288
|
logSubscriptions.get(agentName)?.delete(ws);
|
|
2289
|
+
// Clean up file watchers if no more subscribers
|
|
2290
|
+
const remainingClients = logSubscriptions.get(agentName);
|
|
2291
|
+
if (!remainingClients || remainingClients.size === 0) {
|
|
2292
|
+
const watcher = fileWatchers.get(agentName);
|
|
2293
|
+
if (watcher) {
|
|
2294
|
+
watcher.close();
|
|
2295
|
+
fileWatchers.delete(agentName);
|
|
2296
|
+
fileLastSize.delete(agentName);
|
|
2297
|
+
console.log(`[dashboard] Stopped watching log file for: ${agentName}`);
|
|
2298
|
+
}
|
|
2299
|
+
}
|
|
2177
2300
|
}
|
|
2178
2301
|
const reasonStr = reason?.toString() || 'no reason';
|
|
2179
2302
|
console.log(`[dashboard] Logs WebSocket client disconnected (code: ${code}, reason: ${reasonStr})`);
|