@agent-relay/dashboard-server 2.0.66-beta.1 → 2.0.67
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/mocks/fixtures.d.ts +66 -2
- package/dist/mocks/fixtures.d.ts.map +1 -1
- package/dist/mocks/fixtures.js +82 -2
- package/dist/mocks/fixtures.js.map +1 -1
- package/dist/mocks/routes.d.ts.map +1 -1
- package/dist/mocks/routes.js +311 -77
- package/dist/mocks/routes.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +108 -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-b560f20df53128e5.js +1 -0
- package/out/_next/static/chunks/285-1efcdae8bd9b3272.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/page-394875a22b5ba7ce.js +1 -0
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-f058bf6696242d7b.js +1 -0
- package/out/_next/static/css/{fd373f99378195fc.css → 4c58d9cf493aa626.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 +10 -10
- package/out/_next/static/chunks/118-b821e49d30a9f6af.js +0 -1
- package/out/_next/static/chunks/259-141fb39611979082.js +0 -1
- package/out/_next/static/chunks/535-cecb00f34c2ed9ba.js +0 -1
- package/out/_next/static/chunks/722-af84568996237c02.js +0 -1
- package/out/_next/static/chunks/994-e927457424324a78.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-bba8842697e6192c.js +0 -1
- package/out/_next/static/chunks/app/providers/page-59e92abba4f8d895.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-c667546c4902f1b0.js +0 -1
- /package/out/_next/static/{g8CQjGCOpPwFcSToizDmH → UFF2wGBwSkKsayPv0zus_}/_buildManifest.js +0 -0
- /package/out/_next/static/{g8CQjGCOpPwFcSToizDmH → UFF2wGBwSkKsayPv0zus_}/_ssgManifest.js +0 -0
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`
|
|
@@ -1855,6 +1864,14 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1855
1864
|
}
|
|
1856
1865
|
}
|
|
1857
1866
|
}
|
|
1867
|
+
// Inject cwd from agentCwdMap for agents not in spawner's active workers
|
|
1868
|
+
// (e.g., agents that connected before spawner tracked them, or after restarts)
|
|
1869
|
+
for (const [name, cwd] of agentCwdMap) {
|
|
1870
|
+
const agent = agentsMap.get(name);
|
|
1871
|
+
if (agent && !agent.cwd) {
|
|
1872
|
+
agent.cwd = cwd;
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1858
1875
|
// Also check workers.json for externally-spawned workers (e.g., from agentswarm)
|
|
1859
1876
|
// These workers have log files but weren't spawned by the dashboard's spawner
|
|
1860
1877
|
const workersJsonPath = path.join(teamDir, 'workers.json');
|
|
@@ -1872,6 +1889,21 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
1872
1889
|
// Ignore errors reading workers.json
|
|
1873
1890
|
}
|
|
1874
1891
|
}
|
|
1892
|
+
// Mark relay-protocol spawned agents (spawned by other agents, not via dashboard /api/spawn)
|
|
1893
|
+
// These agents have log files in the team directory but aren't tracked by agentCwdMap
|
|
1894
|
+
if (spawnReader) {
|
|
1895
|
+
for (const [name, agent] of agentsMap) {
|
|
1896
|
+
if (agent.isSpawned)
|
|
1897
|
+
continue;
|
|
1898
|
+
if (onlineUsers.has(name) || name === 'Dashboard')
|
|
1899
|
+
continue;
|
|
1900
|
+
// Check if there's a log file for this agent (indicates it was spawned)
|
|
1901
|
+
const logPath = path.join(teamDir, `${name}.log`);
|
|
1902
|
+
if (fs.existsSync(logPath)) {
|
|
1903
|
+
agent.isSpawned = true;
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
}
|
|
1875
1907
|
// Set team from teams.json for agents that don't have a team yet
|
|
1876
1908
|
// This ensures agents defined in teams.json are associated with their team
|
|
1877
1909
|
// even if they weren't spawned via auto-spawn
|
|
@@ -4167,26 +4199,9 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4167
4199
|
});
|
|
4168
4200
|
}
|
|
4169
4201
|
}
|
|
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
|
-
}
|
|
4202
|
+
// Note: We only show spawned agents with actual PIDs in memory metrics.
|
|
4203
|
+
// Human users and non-process entries from agents.json are excluded since
|
|
4204
|
+
// they don't have memory usage to track.
|
|
4190
4205
|
res.json({
|
|
4191
4206
|
agents,
|
|
4192
4207
|
system: {
|
|
@@ -4664,6 +4679,20 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4664
4679
|
const online = isAgentOnline(name);
|
|
4665
4680
|
res.json({ name, online });
|
|
4666
4681
|
});
|
|
4682
|
+
/**
|
|
4683
|
+
* PUT /api/agents/:name/cwd - Register an agent's working directory
|
|
4684
|
+
* Used by relay-pty-orchestrator after daemon socket spawns (which bypass /api/spawn).
|
|
4685
|
+
*/
|
|
4686
|
+
app.put('/api/agents/:name/cwd', (req, res) => {
|
|
4687
|
+
const { name } = req.params;
|
|
4688
|
+
const { cwd } = req.body || {};
|
|
4689
|
+
if (!cwd || typeof cwd !== 'string') {
|
|
4690
|
+
return res.status(400).json({ error: 'Missing required field: cwd' });
|
|
4691
|
+
}
|
|
4692
|
+
agentCwdMap.set(name, cwd);
|
|
4693
|
+
broadcastData().catch(() => { });
|
|
4694
|
+
res.json({ success: true, name, cwd });
|
|
4695
|
+
});
|
|
4667
4696
|
// ===== Agent Spawn API =====
|
|
4668
4697
|
/**
|
|
4669
4698
|
* POST /api/spawn - Spawn a new agent
|
|
@@ -4683,6 +4712,8 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4683
4712
|
error: 'Missing required field: name',
|
|
4684
4713
|
});
|
|
4685
4714
|
}
|
|
4715
|
+
// Inherit spawner's cwd if no explicit cwd provided (for nested/agent-to-agent spawns)
|
|
4716
|
+
const effectiveCwd = cwd || (spawnerName ? agentCwdMap.get(spawnerName) : undefined);
|
|
4686
4717
|
try {
|
|
4687
4718
|
let result;
|
|
4688
4719
|
if (useExternalSpawnManager) {
|
|
@@ -4702,7 +4733,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4702
4733
|
cli,
|
|
4703
4734
|
task,
|
|
4704
4735
|
team: team || undefined,
|
|
4705
|
-
cwd:
|
|
4736
|
+
cwd: effectiveCwd || undefined,
|
|
4706
4737
|
interactive,
|
|
4707
4738
|
shadowMode,
|
|
4708
4739
|
shadowAgent,
|
|
@@ -4722,7 +4753,7 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4722
4753
|
task,
|
|
4723
4754
|
team: team || undefined,
|
|
4724
4755
|
spawnerName: spawnerName || undefined,
|
|
4725
|
-
cwd:
|
|
4756
|
+
cwd: effectiveCwd || undefined,
|
|
4726
4757
|
interactive,
|
|
4727
4758
|
shadowMode,
|
|
4728
4759
|
shadowAgent,
|
|
@@ -4735,6 +4766,10 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4735
4766
|
result = await spawner.spawn(request);
|
|
4736
4767
|
}
|
|
4737
4768
|
if (result.success) {
|
|
4769
|
+
// Track cwd for this agent so /api/spawned can return it
|
|
4770
|
+
if (effectiveCwd) {
|
|
4771
|
+
agentCwdMap.set(name, effectiveCwd);
|
|
4772
|
+
}
|
|
4738
4773
|
// Broadcast update to WebSocket clients
|
|
4739
4774
|
broadcastData().catch(() => { });
|
|
4740
4775
|
// Broadcast agent_spawned event to activity feed
|
|
@@ -4758,6 +4793,53 @@ export async function startDashboard(portOrOptions, dataDirArg, teamDirArg, dbPa
|
|
|
4758
4793
|
});
|
|
4759
4794
|
}
|
|
4760
4795
|
});
|
|
4796
|
+
/**
|
|
4797
|
+
* POST /api/repos/clone - Clone a repo into the workspace directory
|
|
4798
|
+
* Body: { fullName: "Owner/RepoName" }
|
|
4799
|
+
* Used by cloud API to hot-clone repos added to a running workspace.
|
|
4800
|
+
*/
|
|
4801
|
+
app.post('/api/repos/clone', async (req, res) => {
|
|
4802
|
+
const { fullName } = req.body;
|
|
4803
|
+
if (!fullName || typeof fullName !== 'string' || !fullName.includes('/')) {
|
|
4804
|
+
return res.status(400).json({ success: false, error: 'fullName is required (e.g., "Owner/RepoName")' });
|
|
4805
|
+
}
|
|
4806
|
+
// Validate format: "Owner/RepoName" with safe characters only
|
|
4807
|
+
if (!/^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/.test(fullName)) {
|
|
4808
|
+
return res.status(400).json({ success: false, error: 'Invalid repository name format' });
|
|
4809
|
+
}
|
|
4810
|
+
const repoName = fullName.split('/').pop();
|
|
4811
|
+
const workspaceDir = process.env.WORKSPACE_DIR || path.dirname(projectRoot || dataDir);
|
|
4812
|
+
const targetDir = path.join(workspaceDir, repoName);
|
|
4813
|
+
// Idempotent: skip if already cloned
|
|
4814
|
+
if (fs.existsSync(targetDir)) {
|
|
4815
|
+
return res.json({ success: true, message: 'Already cloned', path: targetDir });
|
|
4816
|
+
}
|
|
4817
|
+
// Use plain HTTPS URL - git credential helper handles authentication.
|
|
4818
|
+
// The credential helper (git-credential-relay) fetches per-repo tokens from
|
|
4819
|
+
// the cloud API, which correctly resolves installation tokens for private repos.
|
|
4820
|
+
const cloneUrl = `https://github.com/${fullName}.git`;
|
|
4821
|
+
try {
|
|
4822
|
+
// Use execFile to avoid shell injection
|
|
4823
|
+
await new Promise((resolve, reject) => {
|
|
4824
|
+
execFile('git', ['clone', cloneUrl, targetDir], { timeout: 120000 }, (error, _stdout, stderr) => {
|
|
4825
|
+
if (error) {
|
|
4826
|
+
reject(new Error(stderr || error.message));
|
|
4827
|
+
}
|
|
4828
|
+
else {
|
|
4829
|
+
resolve();
|
|
4830
|
+
}
|
|
4831
|
+
});
|
|
4832
|
+
});
|
|
4833
|
+
// Mark directory as safe for git
|
|
4834
|
+
execFile('git', ['config', '--global', '--add', 'safe.directory', targetDir], () => { });
|
|
4835
|
+
res.json({ success: true, path: targetDir });
|
|
4836
|
+
}
|
|
4837
|
+
catch (err) {
|
|
4838
|
+
const safeMessage = (err.message || 'Clone failed');
|
|
4839
|
+
console.error('[api/repos/clone] Clone failed:', safeMessage);
|
|
4840
|
+
res.status(500).json({ success: false, error: safeMessage });
|
|
4841
|
+
}
|
|
4842
|
+
});
|
|
4761
4843
|
/**
|
|
4762
4844
|
* POST /api/spawn/architect - Spawn an Architect agent for bridge mode
|
|
4763
4845
|
* Body: { cli?: string }
|
|
@@ -4908,6 +4990,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
4908
4990
|
spawnedAt: worker.spawnedAt,
|
|
4909
4991
|
task: worker.task,
|
|
4910
4992
|
team: worker.team,
|
|
4993
|
+
cwd: agentCwdMap.get(worker.name) || worker.cwd,
|
|
4911
4994
|
source: 'spawner',
|
|
4912
4995
|
});
|
|
4913
4996
|
}
|
|
@@ -4978,6 +5061,7 @@ Start by greeting the project leads and asking for status updates.`;
|
|
|
4978
5061
|
released = await spawner.release(name);
|
|
4979
5062
|
}
|
|
4980
5063
|
if (released) {
|
|
5064
|
+
agentCwdMap.delete(name);
|
|
4981
5065
|
broadcastData().catch(() => { });
|
|
4982
5066
|
// Broadcast agent_released event to activity feed
|
|
4983
5067
|
broadcastPresence({
|