@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.
Files changed (76) hide show
  1. package/dist/server.d.ts.map +1 -1
  2. package/dist/server.js +102 -24
  3. package/dist/server.js.map +1 -1
  4. package/out/404.html +1 -1
  5. package/out/_next/static/chunks/118-4c8241b0218335de.js +1 -0
  6. package/out/_next/static/chunks/{202-6cfbf8339f05e5ef.js → 202-fc0763dd7488e58f.js} +1 -1
  7. package/out/_next/static/chunks/259-3bbaad41b2550936.js +1 -0
  8. package/out/_next/static/chunks/285-1cb1c0ed74f31c6c.js +1 -0
  9. package/out/_next/static/chunks/722-85011b58b9caf88b.js +1 -0
  10. package/out/_next/static/chunks/994-0ce5f1d759089504.js +1 -0
  11. package/out/_next/static/chunks/app/app/[[...slug]]/page-589620c567f85400.js +1 -0
  12. package/out/_next/static/chunks/app/{page-ba281b017e148cd6.js → page-5c60a00d938ac40a.js} +1 -1
  13. package/out/_next/static/chunks/app/providers/setup/[provider]/page-f058bf6696242d7b.js +1 -0
  14. package/out/_next/static/css/{15362c88976df1b9.css → 71615414d8909a44.css} +1 -1
  15. package/out/about.html +2 -2
  16. package/out/about.txt +1 -1
  17. package/out/app/onboarding.html +1 -1
  18. package/out/app/onboarding.txt +1 -1
  19. package/out/app.html +1 -1
  20. package/out/app.txt +2 -2
  21. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
  22. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  23. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  24. package/out/blog/let-them-cook-multi-agent-orchestration.txt +1 -1
  25. package/out/blog.html +2 -2
  26. package/out/blog.txt +1 -1
  27. package/out/careers.html +2 -2
  28. package/out/careers.txt +1 -1
  29. package/out/changelog.html +2 -2
  30. package/out/changelog.txt +1 -1
  31. package/out/cloud/link.html +1 -1
  32. package/out/cloud/link.txt +2 -2
  33. package/out/complete-profile.html +2 -2
  34. package/out/complete-profile.txt +1 -1
  35. package/out/connect-repos.html +1 -1
  36. package/out/connect-repos.txt +2 -2
  37. package/out/contact.html +2 -2
  38. package/out/contact.txt +1 -1
  39. package/out/docs.html +2 -2
  40. package/out/docs.txt +1 -1
  41. package/out/history.html +1 -1
  42. package/out/history.txt +2 -2
  43. package/out/index.html +1 -1
  44. package/out/index.txt +2 -2
  45. package/out/login.html +2 -2
  46. package/out/login.txt +2 -2
  47. package/out/metrics.html +1 -1
  48. package/out/metrics.txt +2 -2
  49. package/out/pricing.html +2 -2
  50. package/out/pricing.txt +1 -1
  51. package/out/privacy.html +2 -2
  52. package/out/privacy.txt +1 -1
  53. package/out/providers/setup/claude.html +1 -1
  54. package/out/providers/setup/claude.txt +2 -2
  55. package/out/providers/setup/codex.html +1 -1
  56. package/out/providers/setup/codex.txt +2 -2
  57. package/out/providers/setup/cursor.html +1 -1
  58. package/out/providers/setup/cursor.txt +2 -2
  59. package/out/providers.html +1 -1
  60. package/out/providers.txt +2 -2
  61. package/out/security.html +2 -2
  62. package/out/security.txt +1 -1
  63. package/out/signup.html +2 -2
  64. package/out/signup.txt +2 -2
  65. package/out/terms.html +2 -2
  66. package/out/terms.txt +1 -1
  67. package/package.json +1 -1
  68. package/out/_next/static/chunks/118-b821e49d30a9f6af.js +0 -1
  69. package/out/_next/static/chunks/259-b560f20df53128e5.js +0 -1
  70. package/out/_next/static/chunks/535-757cbf5de3af1d18.js +0 -1
  71. package/out/_next/static/chunks/722-6cffbc5120f31e24.js +0 -1
  72. package/out/_next/static/chunks/994-e927457424324a78.js +0 -1
  73. package/out/_next/static/chunks/app/app/[[...slug]]/page-7c9abc28789ea7cb.js +0 -1
  74. package/out/_next/static/chunks/app/providers/setup/[provider]/page-c667546c4902f1b0.js +0 -1
  75. /package/out/_next/static/{XAoBjrJ3N72573Ty4Ja_J → CRgdkwuTcA6Bt0A5Fx1wC}/_buildManifest.js +0 -0
  76. /package/out/_next/static/{XAoBjrJ3N72573Ty4Ja_J → CRgdkwuTcA6Bt0A5Fx1wC}/_ssgManifest.js +0 -0
@@ -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;AAkYzD,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"}
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 model
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
- // Also check agents.json for registered agents that may not be spawned
4171
- const agentsPath = path.join(teamDir, 'agents.json');
4172
- if (fs.existsSync(agentsPath)) {
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: cwd || undefined,
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: cwd || undefined,
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({