@agent-relay/dashboard-server 2.0.46 → 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 +147 -7
- 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/{kt1pBkOoE9ZJk69H9H3_X → HwEoE2BFhgZdESAQLY9dG}/_buildManifest.js +0 -0
- /package/out/_next/static/{kt1pBkOoE9ZJk69H9H3_X → HwEoE2BFhgZdESAQLY9dG}/_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;
|
|
@@ -548,6 +549,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
548
549
|
});
|
|
549
550
|
// Track log subscriptions: agentName -> Set of WebSocket clients
|
|
550
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();
|
|
551
555
|
// Track alive status for ping/pong keepalive on main dashboard connections
|
|
552
556
|
// This prevents TCP/proxy timeouts from killing idle workspace connections
|
|
553
557
|
const mainClientAlive = new WeakMap();
|
|
@@ -1742,6 +1746,23 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1742
1746
|
}
|
|
1743
1747
|
}
|
|
1744
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
|
+
}
|
|
1745
1766
|
// Set team from teams.json for agents that don't have a team yet
|
|
1746
1767
|
// This ensures agents defined in teams.json are associated with their team
|
|
1747
1768
|
// even if they weren't spawned via auto-spawn
|
|
@@ -2038,6 +2059,89 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2038
2059
|
return false;
|
|
2039
2060
|
}
|
|
2040
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
|
+
};
|
|
2041
2145
|
// Helper to subscribe to an agent (async to handle spawn timing)
|
|
2042
2146
|
const subscribeToAgent = async (agentName) => {
|
|
2043
2147
|
let isSpawned = spawner?.hasWorker(agentName) ?? false;
|
|
@@ -2092,12 +2196,27 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2092
2196
|
}));
|
|
2093
2197
|
}
|
|
2094
2198
|
else {
|
|
2095
|
-
//
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
lines
|
|
2100
|
-
|
|
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
|
+
}
|
|
2101
2220
|
}
|
|
2102
2221
|
ws.send(JSON.stringify({
|
|
2103
2222
|
type: 'subscribed',
|
|
@@ -2128,6 +2247,16 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2128
2247
|
const agentName = msg.unsubscribe;
|
|
2129
2248
|
clientSubscriptions.delete(agentName);
|
|
2130
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
|
+
}
|
|
2131
2260
|
console.log(`[dashboard] Client unsubscribed from logs for: ${agentName}`);
|
|
2132
2261
|
ws.send(JSON.stringify({
|
|
2133
2262
|
type: 'unsubscribed',
|
|
@@ -2173,6 +2302,17 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
2173
2302
|
// Clean up subscriptions on disconnect
|
|
2174
2303
|
for (const agentName of clientSubscriptions) {
|
|
2175
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
|
+
}
|
|
2176
2316
|
}
|
|
2177
2317
|
const reasonStr = reason?.toString() || 'no reason';
|
|
2178
2318
|
console.log(`[dashboard] Logs WebSocket client disconnected (code: ${code}, reason: ${reasonStr})`);
|