agent-relay 2.1.0 → 2.1.2

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 (66) hide show
  1. package/dist/index.cjs +193 -63
  2. package/dist/src/cli/index.d.ts +11 -1
  3. package/dist/src/cli/index.d.ts.map +1 -1
  4. package/dist/src/cli/index.js +112 -110
  5. package/dist/src/cli/index.js.map +1 -1
  6. package/package.json +18 -18
  7. package/packages/api-types/package.json +1 -1
  8. package/packages/benchmark/package.json +4 -4
  9. package/packages/bridge/package.json +8 -8
  10. package/packages/cli-tester/package.json +1 -1
  11. package/packages/config/package.json +2 -2
  12. package/packages/continuity/package.json +2 -2
  13. package/packages/daemon/dist/connection.d.ts +5 -0
  14. package/packages/daemon/dist/connection.d.ts.map +1 -1
  15. package/packages/daemon/dist/connection.js +19 -1
  16. package/packages/daemon/dist/connection.js.map +1 -1
  17. package/packages/daemon/dist/server.js +2 -2
  18. package/packages/daemon/dist/server.js.map +1 -1
  19. package/packages/daemon/package.json +12 -12
  20. package/packages/daemon/src/connection.ts +22 -1
  21. package/packages/daemon/src/router.test.ts +32 -0
  22. package/packages/daemon/src/server.ts +2 -2
  23. package/packages/hooks/package.json +4 -4
  24. package/packages/mcp/package.json +3 -3
  25. package/packages/memory/package.json +2 -2
  26. package/packages/policy/package.json +2 -2
  27. package/packages/protocol/dist/types.d.ts +5 -0
  28. package/packages/protocol/dist/types.d.ts.map +1 -1
  29. package/packages/protocol/package.json +1 -1
  30. package/packages/protocol/src/types.ts +5 -0
  31. package/packages/resiliency/package.json +1 -1
  32. package/packages/sdk/dist/client.d.ts +6 -0
  33. package/packages/sdk/dist/client.d.ts.map +1 -1
  34. package/packages/sdk/dist/client.js +1 -0
  35. package/packages/sdk/dist/client.js.map +1 -1
  36. package/packages/sdk/package.json +2 -2
  37. package/packages/sdk/src/client.ts +7 -0
  38. package/packages/spawner/package.json +1 -1
  39. package/packages/state/package.json +1 -1
  40. package/packages/storage/package.json +2 -2
  41. package/packages/telemetry/package.json +1 -1
  42. package/packages/trajectory/package.json +2 -2
  43. package/packages/user-directory/package.json +2 -2
  44. package/packages/utils/dist/cjs/relay-pty-path.js +111 -55
  45. package/packages/utils/dist/relay-pty-path.d.ts +17 -12
  46. package/packages/utils/dist/relay-pty-path.d.ts.map +1 -1
  47. package/packages/utils/dist/relay-pty-path.js +144 -94
  48. package/packages/utils/dist/relay-pty-path.js.map +1 -1
  49. package/packages/utils/package.json +2 -2
  50. package/packages/utils/src/relay-pty-path.test.ts +373 -0
  51. package/packages/utils/src/relay-pty-path.ts +182 -91
  52. package/packages/wrapper/dist/base-wrapper.d.ts +5 -0
  53. package/packages/wrapper/dist/base-wrapper.d.ts.map +1 -1
  54. package/packages/wrapper/dist/base-wrapper.js +14 -1
  55. package/packages/wrapper/dist/base-wrapper.js.map +1 -1
  56. package/packages/wrapper/dist/shared.d.ts +36 -0
  57. package/packages/wrapper/dist/shared.d.ts.map +1 -1
  58. package/packages/wrapper/dist/shared.js +123 -2
  59. package/packages/wrapper/dist/shared.js.map +1 -1
  60. package/packages/wrapper/dist/tmux-wrapper.js +1 -1
  61. package/packages/wrapper/dist/tmux-wrapper.js.map +1 -1
  62. package/packages/wrapper/package.json +6 -6
  63. package/packages/wrapper/src/base-wrapper.ts +15 -0
  64. package/packages/wrapper/src/shared.test.ts +156 -11
  65. package/packages/wrapper/src/shared.ts +154 -2
  66. package/packages/wrapper/src/tmux-wrapper.ts +1 -1
@@ -29,59 +29,9 @@ import readline from 'node:readline';
29
29
  import { promisify } from 'node:util';
30
30
  import { exec, spawn as spawnProcess } from 'node:child_process';
31
31
  import { fileURLToPath } from 'node:url';
32
- const RELAY_DASHBOARD_REPO = 'https://github.com/AgentWorkforce/relay-dashboard';
33
- /**
34
- * Prompt user to choose how to handle missing dashboard package.
35
- * Returns: 'npx' | 'install' | 'skip'
36
- */
37
- async function promptDashboardInstall() {
38
- const rl = readline.createInterface({
39
- input: process.stdin,
40
- output: process.stdout,
41
- });
42
- console.log(`
43
- The web dashboard requires @agent-relay/dashboard-server package.
44
-
45
- How would you like to proceed?
46
- 1. Start with npx (recommended - auto-installs temporarily)
47
- 2. View installation instructions
48
- 3. Skip and continue without dashboard
49
- `);
50
- return new Promise((resolve) => {
51
- rl.question('Choose [1/2/3]: ', (answer) => {
52
- rl.close();
53
- const choice = answer.trim();
54
- if (choice === '1') {
55
- resolve('npx');
56
- }
57
- else if (choice === '2') {
58
- resolve('install');
59
- }
60
- else {
61
- resolve('skip');
62
- }
63
- });
64
- });
65
- }
66
- /**
67
- * Show instructions for installing the external dashboard package.
68
- */
69
- function showDashboardInstallInstructions() {
70
- console.log(`
71
- To install the dashboard, run:
72
-
73
- npm install @agent-relay/dashboard-server @agent-relay/dashboard
74
-
75
- Then restart with:
76
-
77
- agent-relay up --dashboard
78
-
79
- For more options, see: ${RELAY_DASHBOARD_REPO}
80
- `);
81
- }
82
32
  /**
83
33
  * Start dashboard via npx (downloads and runs if not installed).
84
- * Returns the spawned child process and port.
34
+ * Returns the spawned child process, port, and a promise that resolves when ready.
85
35
  */
86
36
  function startDashboardViaNpx(options) {
87
37
  console.log('Starting dashboard via npx (this may take a moment on first run)...');
@@ -100,10 +50,21 @@ function startDashboardViaNpx(options) {
100
50
  // Pass any additional env vars needed
101
51
  },
102
52
  });
53
+ // Promise that resolves when dashboard is ready (or after timeout)
54
+ let resolveReady;
55
+ const ready = new Promise((resolve) => {
56
+ resolveReady = resolve;
57
+ // Fallback timeout in case we miss the ready signal
58
+ setTimeout(resolve, 30000);
59
+ });
103
60
  // Forward dashboard output with prefix
104
61
  dashboardProcess.stdout?.on('data', (data) => {
105
62
  const lines = data.toString().split('\n').filter(Boolean);
106
63
  for (const line of lines) {
64
+ // Detect when dashboard is ready (listening message)
65
+ if (line.includes('Dashboard:') || line.includes('listening') || line.includes('ready')) {
66
+ resolveReady();
67
+ }
107
68
  // Don't duplicate the "Dashboard:" line
108
69
  if (!line.includes('Dashboard:')) {
109
70
  console.log(`[dashboard] ${line}`);
@@ -118,13 +79,56 @@ function startDashboardViaNpx(options) {
118
79
  });
119
80
  dashboardProcess.on('error', (err) => {
120
81
  console.error('Failed to start dashboard via npx:', err.message);
82
+ resolveReady(); // Resolve to not block forever
121
83
  });
122
84
  dashboardProcess.on('exit', (code) => {
85
+ resolveReady(); // Resolve on exit to not block forever
123
86
  if (code !== 0 && code !== null) {
124
87
  console.error(`Dashboard process exited with code ${code}`);
125
88
  }
126
89
  });
127
- return { process: dashboardProcess, port: options.port };
90
+ return { process: dashboardProcess, port: options.port, ready };
91
+ }
92
+ /**
93
+ * Install agent-relay-snippet to markdown files using prpm.
94
+ * Installs to CLAUDE.md, GEMINI.md, and AGENTS.md.
95
+ * prpm handles idempotency - won't duplicate if already installed.
96
+ */
97
+ export async function installRelaySnippets(options) {
98
+ const execAsync = promisify(exec);
99
+ const installed = [];
100
+ const targets = [
101
+ { location: 'CLAUDE.md', name: 'CLAUDE.md' },
102
+ { location: 'GEMINI.md', name: 'GEMINI.md' },
103
+ { location: undefined, name: 'AGENTS.md' }, // Default location
104
+ ];
105
+ for (const target of targets) {
106
+ try {
107
+ const args = ['npx', 'prpm', 'install', '@agent-relay/agent-relay-snippet'];
108
+ if (target.location) {
109
+ args.push('--location', target.location);
110
+ }
111
+ await execAsync(args.join(' '), { timeout: 60000 });
112
+ installed.push(target.name);
113
+ if (!options?.silent) {
114
+ console.log(` ✓ Installed to ${target.name}`);
115
+ }
116
+ }
117
+ catch (err) {
118
+ // prpm exits with error if already installed or other issues
119
+ // Check if it's an "already exists" situation by looking at stderr
120
+ if (err.stderr?.includes('already') || err.stdout?.includes('already')) {
121
+ if (!options?.silent) {
122
+ console.log(` ✓ ${target.name} (already installed)`);
123
+ }
124
+ installed.push(target.name);
125
+ }
126
+ else if (!options?.silent) {
127
+ console.error(` ⚠ Failed to install to ${target.name}: ${err.message}`);
128
+ }
129
+ }
130
+ }
131
+ return { success: installed.length > 0, installed };
128
132
  }
129
133
  dotenvConfig();
130
134
  const DEFAULT_DASHBOARD_PORT = process.env.AGENT_RELAY_DASHBOARD_PORT || '3888';
@@ -465,6 +469,36 @@ program
465
469
  const socketPath = paths.socketPath;
466
470
  const dbPath = paths.dbPath;
467
471
  const pidFilePath = pidFilePathForSocket(socketPath);
472
+ // Auto-install relay protocol snippets if not already present
473
+ // Check if the snippet marker exists in any of the target files
474
+ const snippetTargets = ['CLAUDE.md', 'GEMINI.md', 'AGENTS.md'];
475
+ const snippetMarker = '<!-- prpm:snippet:start @agent-relay/agent-relay-snippet';
476
+ const hasSnippetInstalled = snippetTargets.some(file => {
477
+ const filePath = path.join(paths.projectRoot, file);
478
+ if (!fs.existsSync(filePath))
479
+ return false;
480
+ try {
481
+ const content = fs.readFileSync(filePath, 'utf-8');
482
+ return content.includes(snippetMarker);
483
+ }
484
+ catch {
485
+ return false;
486
+ }
487
+ });
488
+ if (!hasSnippetInstalled) {
489
+ console.log('Installing relay protocol snippets...');
490
+ try {
491
+ const result = await installRelaySnippets({ silent: false });
492
+ if (result.success) {
493
+ console.log(`Installed snippets to: ${result.installed.join(', ')}`);
494
+ }
495
+ }
496
+ catch (err) {
497
+ // Non-fatal - continue even if snippet install fails
498
+ console.log(`Note: Could not auto-install snippets: ${err.message}`);
499
+ }
500
+ console.log('');
501
+ }
468
502
  // Set up log file to avoid console output polluting TUI terminals
469
503
  // Only set if not already configured via environment
470
504
  if (!process.env.AGENT_RELAY_LOG_FILE) {
@@ -578,60 +612,27 @@ program
578
612
  if (err.code === 'ERR_MODULE_NOT_FOUND' || err.code === 'MODULE_NOT_FOUND') {
579
613
  // Dashboard package not installed
580
614
  if (dashboardRequested) {
581
- // User explicitly asked for dashboard but it's not installed
582
- // Only prompt interactively if stdin is a TTY (not in Docker/CI)
583
- if (process.stdin.isTTY) {
584
- console.log('');
585
- const action = await promptDashboardInstall();
586
- if (action === 'npx') {
587
- // Start dashboard via npx
588
- const { process: dashboardProcess, port: npxPort } = startDashboardViaNpx({
589
- port,
590
- dataDir: paths.dataDir,
591
- teamDir: paths.teamDir,
592
- projectRoot: paths.projectRoot,
593
- });
594
- dashboardPort = npxPort;
595
- // Clean up dashboard process on exit
596
- const cleanupDashboard = () => {
597
- if (dashboardProcess && !dashboardProcess.killed) {
598
- dashboardProcess.kill('SIGTERM');
599
- }
600
- };
601
- process.on('SIGINT', cleanupDashboard);
602
- process.on('SIGTERM', cleanupDashboard);
603
- process.on('exit', cleanupDashboard);
604
- // Wait a moment for dashboard to start
605
- await new Promise(resolve => setTimeout(resolve, 2000));
606
- console.log(`Dashboard: http://localhost:${dashboardPort}`);
615
+ // User explicitly asked for dashboard but it's not installed - start via npx
616
+ console.log('Dashboard package not installed. Starting via npx...');
617
+ const { process: dashboardProcess, port: npxPort, ready } = startDashboardViaNpx({
618
+ port,
619
+ dataDir: paths.dataDir,
620
+ teamDir: paths.teamDir,
621
+ projectRoot: paths.projectRoot,
622
+ });
623
+ dashboardPort = npxPort;
624
+ // Clean up dashboard process on exit
625
+ const cleanupDashboard = () => {
626
+ if (dashboardProcess && !dashboardProcess.killed) {
627
+ dashboardProcess.kill('SIGTERM');
607
628
  }
608
- else if (action === 'install') {
609
- showDashboardInstallInstructions();
610
- }
611
- }
612
- else {
613
- // Non-interactive: try npx automatically
614
- console.log('Dashboard package not installed. Starting via npx...');
615
- const { process: dashboardProcess, port: npxPort } = startDashboardViaNpx({
616
- port,
617
- dataDir: paths.dataDir,
618
- teamDir: paths.teamDir,
619
- projectRoot: paths.projectRoot,
620
- });
621
- dashboardPort = npxPort;
622
- // Clean up dashboard process on exit
623
- const cleanupDashboard = () => {
624
- if (dashboardProcess && !dashboardProcess.killed) {
625
- dashboardProcess.kill('SIGTERM');
626
- }
627
- };
628
- process.on('SIGINT', cleanupDashboard);
629
- process.on('SIGTERM', cleanupDashboard);
630
- process.on('exit', cleanupDashboard);
631
- // Wait a moment for dashboard to start
632
- await new Promise(resolve => setTimeout(resolve, 3000));
633
- console.log(`Dashboard: http://localhost:${dashboardPort}`);
634
- }
629
+ };
630
+ process.on('SIGINT', cleanupDashboard);
631
+ process.on('SIGTERM', cleanupDashboard);
632
+ process.on('exit', cleanupDashboard);
633
+ // Wait for dashboard to be ready
634
+ await ready;
635
+ console.log(`Dashboard: http://localhost:${dashboardPort}`);
635
636
  }
636
637
  // Silent if user didn't explicitly request dashboard
637
638
  }
@@ -3155,10 +3156,11 @@ async function runInit(options) {
3155
3156
  console.log(' ○ Daemon is not running');
3156
3157
  }
3157
3158
  console.log('');
3158
- // Step 3: Install MCP for editors
3159
+ // Step 1: Install MCP for editors (only if RELAY_MCP_AUTO_INSTALL=1)
3159
3160
  let mcpInstalled = false;
3160
- if (!options.skipMcp) {
3161
- console.log(' ┌─ Step 1: MCP Server for AI Editors ─────────────────────┐');
3161
+ const mcpAutoInstallEnabled = process.env.RELAY_MCP_AUTO_INSTALL === '1';
3162
+ if (!options.skipMcp && mcpAutoInstallEnabled) {
3163
+ console.log(' ┌─ MCP Server for AI Editors ───────────────────────────────┐');
3162
3164
  console.log(' │ │');
3163
3165
  console.log(' │ MCP (Model Context Protocol) gives AI editors native │');
3164
3166
  console.log(' │ tools for agent communication: │');
@@ -3193,9 +3195,9 @@ async function runInit(options) {
3193
3195
  console.log('');
3194
3196
  }
3195
3197
  }
3196
- // Step 4: Start daemon
3198
+ // Start daemon
3197
3199
  if (!daemonRunning && !options.skipDaemon) {
3198
- console.log(' ┌─ Step 2: Start the Relay Daemon ─────────────────────────┐');
3200
+ console.log(' ┌─ Start the Relay Daemon ──────────────────────────────────┐');
3199
3201
  console.log(' │ │');
3200
3202
  console.log(' │ The daemon manages agent connections and message │');
3201
3203
  console.log(' │ routing. It runs in the background. │');