@agent-relay/dashboard-server 2.0.65 → 2.0.66-beta.0
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.d.ts.map +1 -1
- package/dist/server.js +102 -24
- package/dist/server.js.map +1 -1
- package/out/404.html +1 -1
- package/out/_next/static/chunks/118-4c8241b0218335de.js +1 -0
- package/out/_next/static/chunks/{202-6cfbf8339f05e5ef.js → 202-fc0763dd7488e58f.js} +1 -1
- package/out/_next/static/chunks/259-3bbaad41b2550936.js +1 -0
- package/out/_next/static/chunks/285-1cb1c0ed74f31c6c.js +1 -0
- package/out/_next/static/chunks/722-85011b58b9caf88b.js +1 -0
- package/out/_next/static/chunks/994-0ce5f1d759089504.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/page-589620c567f85400.js +1 -0
- package/out/_next/static/chunks/app/{page-ba281b017e148cd6.js → page-5c60a00d938ac40a.js} +1 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-f058bf6696242d7b.js +1 -0
- package/out/_next/static/css/{15362c88976df1b9.css → 71615414d8909a44.css} +1 -1
- package/out/about.html +2 -2
- 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 +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +1 -1
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +1 -1
- package/out/changelog.html +2 -2
- package/out/changelog.txt +1 -1
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +1 -1
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +2 -2
- package/out/contact.html +2 -2
- package/out/contact.txt +1 -1
- package/out/docs.html +2 -2
- package/out/docs.txt +1 -1
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +2 -2
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +1 -1
- package/out/privacy.html +2 -2
- package/out/privacy.txt +1 -1
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +2 -2
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +2 -2
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +2 -2
- package/out/providers.html +1 -1
- package/out/providers.txt +2 -2
- package/out/security.html +2 -2
- package/out/security.txt +1 -1
- package/out/signup.html +2 -2
- package/out/signup.txt +2 -2
- package/out/terms.html +2 -2
- package/out/terms.txt +1 -1
- package/package.json +1 -1
- package/out/_next/static/chunks/118-b821e49d30a9f6af.js +0 -1
- package/out/_next/static/chunks/259-b560f20df53128e5.js +0 -1
- package/out/_next/static/chunks/535-757cbf5de3af1d18.js +0 -1
- package/out/_next/static/chunks/722-6cffbc5120f31e24.js +0 -1
- package/out/_next/static/chunks/994-e927457424324a78.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-7c9abc28789ea7cb.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-c667546c4902f1b0.js +0 -1
- /package/out/_next/static/{XAoBjrJ3N72573Ty4Ja_J → CRgdkwuTcA6Bt0A5Fx1wC}/_buildManifest.js +0 -0
- /package/out/_next/static/{XAoBjrJ3N72573Ty4Ja_J → CRgdkwuTcA6Bt0A5Fx1wC}/_ssgManifest.js +0 -0
package/dist/server.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AA8DA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,kBAAkB,CAAC;AAmYzD,wBAAsB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;AACvH,wBAAsB,cAAc,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC"}
|
package/dist/server.js
CHANGED
|
@@ -5,7 +5,7 @@ import path from 'path';
|
|
|
5
5
|
import fs from 'fs';
|
|
6
6
|
import os from 'os';
|
|
7
7
|
import crypto from 'crypto';
|
|
8
|
-
import { exec } from 'child_process';
|
|
8
|
+
import { exec, execFile } from 'child_process';
|
|
9
9
|
import { fileURLToPath } from 'url';
|
|
10
10
|
import { createStorageAdapter } from '@agent-relay/storage/adapter';
|
|
11
11
|
import { RelayClient } from '@agent-relay/sdk';
|
|
@@ -642,6 +642,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
642
642
|
clearInterval(bridgePingInterval);
|
|
643
643
|
});
|
|
644
644
|
const onlineUsers = new Map();
|
|
645
|
+
// Track cwd per spawned agent (name -> cwd)
|
|
646
|
+
// This is set when /api/spawn is called and included in /api/spawned responses
|
|
647
|
+
const agentCwdMap = new Map();
|
|
645
648
|
// Validation helpers for presence
|
|
646
649
|
const isValidUsername = (username) => {
|
|
647
650
|
if (typeof username !== 'string')
|
|
@@ -1834,7 +1837,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1834
1837
|
// Ignore errors reading processing state - it's optional
|
|
1835
1838
|
}
|
|
1836
1839
|
}
|
|
1837
|
-
// Mark spawned agents with isSpawned flag, team, and
|
|
1840
|
+
// Mark spawned agents with isSpawned flag, team, model, and cwd
|
|
1838
1841
|
if (spawnReader) {
|
|
1839
1842
|
const activeWorkers = spawnReader.getActiveWorkers();
|
|
1840
1843
|
for (const worker of activeWorkers) {
|
|
@@ -1844,6 +1847,12 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1844
1847
|
if (worker.team) {
|
|
1845
1848
|
agent.team = worker.team;
|
|
1846
1849
|
}
|
|
1850
|
+
// Inject cwd from agentCwdMap (set during /api/spawn) or from worker info
|
|
1851
|
+
// (set by SpawnManager for relay-protocol spawns that bypass /api/spawn)
|
|
1852
|
+
const workerCwd = agentCwdMap.get(worker.name) || worker.cwd;
|
|
1853
|
+
if (workerCwd) {
|
|
1854
|
+
agent.cwd = workerCwd;
|
|
1855
|
+
}
|
|
1847
1856
|
// Extract model from spawn command (e.g., "codex --model gpt-5.2-codex" → "gpt-5.2-codex")
|
|
1848
1857
|
if (worker.cli) {
|
|
1849
1858
|
// Support both `--model foo` and `--model=foo`
|
|
@@ -1872,6 +1881,21 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1872
1881
|
// Ignore errors reading workers.json
|
|
1873
1882
|
}
|
|
1874
1883
|
}
|
|
1884
|
+
// Mark relay-protocol spawned agents (spawned by other agents, not via dashboard /api/spawn)
|
|
1885
|
+
// These agents have log files in the team directory but aren't tracked by agentCwdMap
|
|
1886
|
+
if (spawnReader) {
|
|
1887
|
+
for (const [name, agent] of agentsMap) {
|
|
1888
|
+
if (agent.isSpawned)
|
|
1889
|
+
continue;
|
|
1890
|
+
if (onlineUsers.has(name) || name === 'Dashboard')
|
|
1891
|
+
continue;
|
|
1892
|
+
// Check if there's a log file for this agent (indicates it was spawned)
|
|
1893
|
+
const logPath = path.join(teamDir, `${name}.log`);
|
|
1894
|
+
if (fs.existsSync(logPath)) {
|
|
1895
|
+
agent.isSpawned = true;
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
}
|
|
1875
1899
|
// Set team from teams.json for agents that don't have a team yet
|
|
1876
1900
|
// This ensures agents defined in teams.json are associated with their team
|
|
1877
1901
|
// even if they weren't spawned via auto-spawn
|
|
@@ -4167,26 +4191,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4167
4191
|
});
|
|
4168
4192
|
}
|
|
4169
4193
|
}
|
|
4170
|
-
//
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
const data = JSON.parse(fs.readFileSync(agentsPath, 'utf-8'));
|
|
4174
|
-
const registeredAgents = data.agents || [];
|
|
4175
|
-
for (const agent of registeredAgents) {
|
|
4176
|
-
if (!agents.find(a => a.name === agent.name)) {
|
|
4177
|
-
// Check if recently active (within 30 seconds)
|
|
4178
|
-
const lastSeen = agent.lastSeen ? new Date(agent.lastSeen).getTime() : 0;
|
|
4179
|
-
const isActive = Date.now() - lastSeen < 30000;
|
|
4180
|
-
if (isActive) {
|
|
4181
|
-
agents.push({
|
|
4182
|
-
name: agent.name,
|
|
4183
|
-
status: 'active',
|
|
4184
|
-
alertLevel: 'normal',
|
|
4185
|
-
});
|
|
4186
|
-
}
|
|
4187
|
-
}
|
|
4188
|
-
}
|
|
4189
|
-
}
|
|
4194
|
+
// Note: We only show spawned agents with actual PIDs in memory metrics.
|
|
4195
|
+
// Human users and non-process entries from agents.json are excluded since
|
|
4196
|
+
// they don't have memory usage to track.
|
|
4190
4197
|
res.json({
|
|
4191
4198
|
agents,
|
|
4192
4199
|
system: {
|
|
@@ -4664,6 +4671,20 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4664
4671
|
const online = isAgentOnline(name);
|
|
4665
4672
|
res.json({ name, online });
|
|
4666
4673
|
});
|
|
4674
|
+
/**
|
|
4675
|
+
* PUT /api/agents/:name/cwd - Register an agent's working directory
|
|
4676
|
+
* Used by relay-pty-orchestrator after daemon socket spawns (which bypass /api/spawn).
|
|
4677
|
+
*/
|
|
4678
|
+
app.put('/api/agents/:name/cwd', (req, res) => {
|
|
4679
|
+
const { name } = req.params;
|
|
4680
|
+
const { cwd } = req.body || {};
|
|
4681
|
+
if (!cwd || typeof cwd !== 'string') {
|
|
4682
|
+
return res.status(400).json({ error: 'Missing required field: cwd' });
|
|
4683
|
+
}
|
|
4684
|
+
agentCwdMap.set(name, cwd);
|
|
4685
|
+
broadcastData().catch(() => { });
|
|
4686
|
+
res.json({ success: true, name, cwd });
|
|
4687
|
+
});
|
|
4667
4688
|
// ===== Agent Spawn API =====
|
|
4668
4689
|
/**
|
|
4669
4690
|
* POST /api/spawn - Spawn a new agent
|
|
@@ -4683,6 +4704,8 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4683
4704
|
error: 'Missing required field: name',
|
|
4684
4705
|
});
|
|
4685
4706
|
}
|
|
4707
|
+
// Inherit spawner's cwd if no explicit cwd provided (for nested/agent-to-agent spawns)
|
|
4708
|
+
const effectiveCwd = cwd || (spawnerName ? agentCwdMap.get(spawnerName) : undefined);
|
|
4686
4709
|
try {
|
|
4687
4710
|
let result;
|
|
4688
4711
|
if (useExternalSpawnManager) {
|
|
@@ -4702,7 +4725,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4702
4725
|
cli,
|
|
4703
4726
|
task,
|
|
4704
4727
|
team: team || undefined,
|
|
4705
|
-
cwd:
|
|
4728
|
+
cwd: effectiveCwd || undefined,
|
|
4706
4729
|
interactive,
|
|
4707
4730
|
shadowMode,
|
|
4708
4731
|
shadowAgent,
|
|
@@ -4722,7 +4745,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4722
4745
|
task,
|
|
4723
4746
|
team: team || undefined,
|
|
4724
4747
|
spawnerName: spawnerName || undefined,
|
|
4725
|
-
cwd:
|
|
4748
|
+
cwd: effectiveCwd || undefined,
|
|
4726
4749
|
interactive,
|
|
4727
4750
|
shadowMode,
|
|
4728
4751
|
shadowAgent,
|
|
@@ -4735,6 +4758,10 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4735
4758
|
result = await spawner.spawn(request);
|
|
4736
4759
|
}
|
|
4737
4760
|
if (result.success) {
|
|
4761
|
+
// Track cwd for this agent so /api/spawned can return it
|
|
4762
|
+
if (effectiveCwd) {
|
|
4763
|
+
agentCwdMap.set(name, effectiveCwd);
|
|
4764
|
+
}
|
|
4738
4765
|
// Broadcast update to WebSocket clients
|
|
4739
4766
|
broadcastData().catch(() => { });
|
|
4740
4767
|
// Broadcast agent_spawned event to activity feed
|
|
@@ -4758,6 +4785,55 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4758
4785
|
});
|
|
4759
4786
|
}
|
|
4760
4787
|
});
|
|
4788
|
+
/**
|
|
4789
|
+
* POST /api/repos/clone - Clone a repo into the workspace directory
|
|
4790
|
+
* Body: { fullName: "Owner/RepoName" }
|
|
4791
|
+
* Used by cloud API to hot-clone repos added to a running workspace.
|
|
4792
|
+
*/
|
|
4793
|
+
app.post('/api/repos/clone', async (req, res) => {
|
|
4794
|
+
const { fullName } = req.body;
|
|
4795
|
+
if (!fullName || typeof fullName !== 'string' || !fullName.includes('/')) {
|
|
4796
|
+
return res.status(400).json({ success: false, error: 'fullName is required (e.g., "Owner/RepoName")' });
|
|
4797
|
+
}
|
|
4798
|
+
// Validate format: "Owner/RepoName" with safe characters only
|
|
4799
|
+
if (!/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(fullName)) {
|
|
4800
|
+
return res.status(400).json({ success: false, error: 'Invalid repository name format' });
|
|
4801
|
+
}
|
|
4802
|
+
const repoName = fullName.split('/').pop();
|
|
4803
|
+
const workspaceDir = process.env.WORKSPACE_DIR || path.dirname(projectRoot || dataDir);
|
|
4804
|
+
const targetDir = path.join(workspaceDir, repoName);
|
|
4805
|
+
// Idempotent: skip if already cloned
|
|
4806
|
+
if (fs.existsSync(targetDir)) {
|
|
4807
|
+
return res.json({ success: true, message: 'Already cloned', path: targetDir });
|
|
4808
|
+
}
|
|
4809
|
+
const githubToken = process.env.GITHUB_TOKEN;
|
|
4810
|
+
if (!githubToken) {
|
|
4811
|
+
return res.status(500).json({ success: false, error: 'GITHUB_TOKEN not available' });
|
|
4812
|
+
}
|
|
4813
|
+
const cloneUrl = `https://x-access-token:${githubToken}@github.com/${fullName}.git`;
|
|
4814
|
+
try {
|
|
4815
|
+
// Use execFile to avoid shell injection
|
|
4816
|
+
await new Promise((resolve, reject) => {
|
|
4817
|
+
execFile('git', ['clone', cloneUrl, targetDir], { timeout: 120000 }, (error, _stdout, stderr) => {
|
|
4818
|
+
if (error) {
|
|
4819
|
+
reject(new Error(stderr || error.message));
|
|
4820
|
+
}
|
|
4821
|
+
else {
|
|
4822
|
+
resolve();
|
|
4823
|
+
}
|
|
4824
|
+
});
|
|
4825
|
+
});
|
|
4826
|
+
// Mark directory as safe for git
|
|
4827
|
+
execFile('git', ['config', '--global', '--add', 'safe.directory', targetDir], () => { });
|
|
4828
|
+
res.json({ success: true, path: targetDir });
|
|
4829
|
+
}
|
|
4830
|
+
catch (err) {
|
|
4831
|
+
// Sanitize error message to avoid leaking GITHUB_TOKEN embedded in the clone URL
|
|
4832
|
+
const safeMessage = (err.message || 'Clone failed').replace(/https:\/\/[^@]+@/g, 'https://***@');
|
|
4833
|
+
console.error('[api/repos/clone] Clone failed:', safeMessage);
|
|
4834
|
+
res.status(500).json({ success: false, error: safeMessage });
|
|
4835
|
+
}
|
|
4836
|
+
});
|
|
4761
4837
|
/**
|
|
4762
4838
|
* POST /api/spawn/architect - Spawn an Architect agent for bridge mode
|
|
4763
4839
|
* Body: { cli?: string }
|
|
@@ -4908,6 +4984,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
4908
4984
|
spawnedAt: worker.spawnedAt,
|
|
4909
4985
|
task: worker.task,
|
|
4910
4986
|
team: worker.team,
|
|
4987
|
+
cwd: agentCwdMap.get(worker.name) || worker.cwd,
|
|
4911
4988
|
source: 'spawner',
|
|
4912
4989
|
});
|
|
4913
4990
|
}
|
|
@@ -4978,6 +5055,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
4978
5055
|
released = await spawner.release(name);
|
|
4979
5056
|
}
|
|
4980
5057
|
if (released) {
|
|
5058
|
+
agentCwdMap.delete(name);
|
|
4981
5059
|
broadcastData().catch(() => { });
|
|
4982
5060
|
// Broadcast agent_released event to activity feed
|
|
4983
5061
|
broadcastPresence({
|