agent-relay 2.1.1 → 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 (59) hide show
  1. package/dist/index.cjs +97 -3
  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/package.json +2 -2
  45. package/packages/wrapper/dist/base-wrapper.d.ts +5 -0
  46. package/packages/wrapper/dist/base-wrapper.d.ts.map +1 -1
  47. package/packages/wrapper/dist/base-wrapper.js +14 -1
  48. package/packages/wrapper/dist/base-wrapper.js.map +1 -1
  49. package/packages/wrapper/dist/shared.d.ts +36 -0
  50. package/packages/wrapper/dist/shared.d.ts.map +1 -1
  51. package/packages/wrapper/dist/shared.js +123 -2
  52. package/packages/wrapper/dist/shared.js.map +1 -1
  53. package/packages/wrapper/dist/tmux-wrapper.js +1 -1
  54. package/packages/wrapper/dist/tmux-wrapper.js.map +1 -1
  55. package/packages/wrapper/package.json +6 -6
  56. package/packages/wrapper/src/base-wrapper.ts +15 -0
  57. package/packages/wrapper/src/shared.test.ts +156 -11
  58. package/packages/wrapper/src/shared.ts +154 -2
  59. package/packages/wrapper/src/tmux-wrapper.ts +1 -1
package/dist/index.cjs CHANGED
@@ -35497,6 +35497,7 @@ __export(index_exports, {
35497
35497
  PROTOCOL_VERSION: () => PROTOCOL_VERSION,
35498
35498
  PROVIDER_AUTH_PATTERNS: () => PROVIDER_AUTH_PATTERNS,
35499
35499
  PostgresDLQAdapter: () => PostgresDLQAdapter,
35500
+ RESERVED_AGENT_NAMES: () => RESERVED_AGENT_NAMES,
35500
35501
  RelayClient: () => RelayClient,
35501
35502
  RelayEvent: () => RelayEvent,
35502
35503
  RelayPtyOrchestrator: () => RelayPtyOrchestrator,
@@ -37229,13 +37230,81 @@ function stripAnsi2(str) {
37229
37230
  function sleep(ms) {
37230
37231
  return new Promise((resolve5) => setTimeout(resolve5, ms));
37231
37232
  }
37233
+ var AUTO_SUGGEST_PATTERNS = {
37234
+ // Dim text styling - commonly used for ghost text
37235
+ dim: /\x1B\[2m/,
37236
+ // Bright black (dark gray) - common for suggestions
37237
+ brightBlack: /\x1B\[90m/,
37238
+ // 256-color grays (8 is dark gray, 240-250 are grays)
37239
+ gray256: /\x1B\[38;5;(?:8|24[0-9]|250)m/,
37240
+ // Cursor save (CSI s or ESC 7)
37241
+ cursorSave: /\x1B\[s|\x1B7/,
37242
+ // Cursor restore (CSI u or ESC 8)
37243
+ cursorRestore: /\x1B\[u|\x1B8/,
37244
+ // Italic text - sometimes used for suggestions
37245
+ italic: /\x1B\[3m/
37246
+ };
37247
+ function detectAutoSuggest(output) {
37248
+ const patterns = [];
37249
+ let confidence = 0;
37250
+ if (AUTO_SUGGEST_PATTERNS.dim.test(output)) {
37251
+ patterns.push("dim");
37252
+ confidence += 0.4;
37253
+ }
37254
+ if (AUTO_SUGGEST_PATTERNS.brightBlack.test(output)) {
37255
+ patterns.push("brightBlack");
37256
+ confidence += 0.4;
37257
+ }
37258
+ if (AUTO_SUGGEST_PATTERNS.gray256.test(output)) {
37259
+ patterns.push("gray256");
37260
+ confidence += 0.3;
37261
+ }
37262
+ if (AUTO_SUGGEST_PATTERNS.italic.test(output)) {
37263
+ patterns.push("italic");
37264
+ confidence += 0.2;
37265
+ }
37266
+ const hasCursorSave = AUTO_SUGGEST_PATTERNS.cursorSave.test(output);
37267
+ const hasCursorRestore = AUTO_SUGGEST_PATTERNS.cursorRestore.test(output);
37268
+ if (hasCursorSave && hasCursorRestore) {
37269
+ patterns.push("cursorSaveRestore");
37270
+ confidence += 0.5;
37271
+ } else if (hasCursorSave || hasCursorRestore) {
37272
+ patterns.push(hasCursorSave ? "cursorSave" : "cursorRestore");
37273
+ confidence += 0.2;
37274
+ }
37275
+ confidence = Math.min(confidence, 1);
37276
+ const stripped = stripAnsi2(output);
37277
+ if (patterns.length === 0) {
37278
+ return { isAutoSuggest: false, confidence: 0, patterns, strippedContent: stripped };
37279
+ }
37280
+ const lines = stripped.split("\n").filter((l) => l.trim().length > 0);
37281
+ if (lines.length > 2) {
37282
+ confidence *= 0.5;
37283
+ }
37284
+ const isAutoSuggest = confidence >= 0.4;
37285
+ return { isAutoSuggest, confidence, patterns, strippedContent: stripped };
37286
+ }
37287
+ function shouldIgnoreForIdleDetection(output) {
37288
+ if (!output || output.length === 0) {
37289
+ return true;
37290
+ }
37291
+ const result = detectAutoSuggest(output);
37292
+ if (result.isAutoSuggest) {
37293
+ return true;
37294
+ }
37295
+ const stripped = stripAnsi2(output).trim();
37296
+ if (stripped.length === 0) {
37297
+ return true;
37298
+ }
37299
+ return false;
37300
+ }
37232
37301
  function buildInjectionString(msg) {
37233
37302
  const sanitizedBody = stripAnsi2(msg.body || "").replace(/[\r\n]+/g, " ").trim();
37234
37303
  if (sanitizedBody.startsWith("Relay message from ")) {
37235
37304
  return sanitizedBody;
37236
37305
  }
37237
37306
  const shortId = msg.messageId.substring(0, 8);
37238
- const displayFrom = msg.from === "_DashboardUI" && typeof msg.data?.senderName === "string" ? msg.data.senderName : msg.from;
37307
+ const displayFrom = msg.from === "Dashboard" && typeof msg.data?.senderName === "string" ? msg.data.senderName : msg.from;
37239
37308
  const threadHint = msg.thread ? ` [thread:${msg.thread}]` : "";
37240
37309
  const importanceHint = msg.importance !== void 0 && msg.importance > 75 ? " [!!]" : msg.importance !== void 0 && msg.importance > 50 ? " [!]" : "";
37241
37310
  const channelHint = msg.originalTo === "*" ? " [#general] (reply to #general, not sender)" : msg.originalTo?.startsWith("#") ? ` [${msg.originalTo}] (reply to ${msg.originalTo}, not sender)` : "";
@@ -42483,8 +42552,17 @@ var BaseWrapper = class extends import_node_events2.EventEmitter {
42483
42552
  /**
42484
42553
  * Feed output to the idle and stuck detectors.
42485
42554
  * Call this whenever new output is received from the agent.
42555
+ *
42556
+ * Note: Auto-suggestions (ghost text) are filtered out to prevent
42557
+ * false idle resets. Claude Code and other CLIs show suggestions
42558
+ * in gray/dim text with cursor save/restore, which should not
42559
+ * be treated as "real" output for idle detection.
42486
42560
  */
42487
42561
  feedIdleDetectorOutput(output) {
42562
+ if (shouldIgnoreForIdleDetection(output)) {
42563
+ this.stuckDetector.onOutput(output);
42564
+ return;
42565
+ }
42488
42566
  this.idleDetector.onOutput(output);
42489
42567
  this.stuckDetector.onOutput(output);
42490
42568
  }
@@ -52803,6 +52881,16 @@ function generateEventSchemas() {
52803
52881
  }
52804
52882
 
52805
52883
  // packages/daemon/dist/connection.js
52884
+ var RESERVED_AGENT_NAMES = /* @__PURE__ */ new Set([
52885
+ "Dashboard",
52886
+ // Dashboard system client
52887
+ "cli",
52888
+ // CLI tool
52889
+ "system",
52890
+ // System messages
52891
+ "_router"
52892
+ // Internal router target
52893
+ ]);
52806
52894
  var DEFAULT_CONFIG10 = {
52807
52895
  ...DEFAULT_CONNECTION_CONFIG
52808
52896
  };
@@ -52951,7 +53039,12 @@ var Connection = class {
52951
53039
  this.sendError("BAD_REQUEST", "Unexpected HELLO", false);
52952
53040
  return;
52953
53041
  }
52954
- this._agentName = envelope.payload.agent;
53042
+ const agentName = envelope.payload.agent;
53043
+ if (RESERVED_AGENT_NAMES.has(agentName) && !envelope.payload._isSystemComponent) {
53044
+ this.sendError("BAD_REQUEST", `Agent name "${agentName}" is reserved for system use`, true);
53045
+ return;
53046
+ }
53047
+ this._agentName = agentName;
52955
53048
  this._entityType = envelope.payload.entityType;
52956
53049
  this._cli = envelope.payload.cli;
52957
53050
  this._program = envelope.payload.program;
@@ -72681,7 +72774,7 @@ var Daemon = class _Daemon {
72681
72774
  isInternalAgent(name) {
72682
72775
  if (name.startsWith("__"))
72683
72776
  return true;
72684
- return name === "Dashboard" || name === "_DashboardUI" || name === "cli";
72777
+ return name === "Dashboard" || name === "cli";
72685
72778
  }
72686
72779
  /**
72687
72780
  * Stop the daemon.
@@ -78770,6 +78863,7 @@ init_dist();
78770
78863
  PROTOCOL_VERSION,
78771
78864
  PROVIDER_AUTH_PATTERNS,
78772
78865
  PostgresDLQAdapter,
78866
+ RESERVED_AGENT_NAMES,
78773
78867
  RelayClient,
78774
78868
  RelayEvent,
78775
78869
  RelayPtyOrchestrator,
@@ -12,5 +12,15 @@
12
12
  * relay agents - List connected agents
13
13
  * relay who - Show currently active agents
14
14
  */
15
- export {};
15
+ /**
16
+ * Install agent-relay-snippet to markdown files using prpm.
17
+ * Installs to CLAUDE.md, GEMINI.md, and AGENTS.md.
18
+ * prpm handles idempotency - won't duplicate if already installed.
19
+ */
20
+ export declare function installRelaySnippets(options?: {
21
+ silent?: boolean;
22
+ }): Promise<{
23
+ success: boolean;
24
+ installed: string[];
25
+ }>;
16
26
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/cli/index.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;GAYG;AAuGH;;;;GAIG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,OAAO,CAAC;IAAC,SAAS,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CAqC7H"}
@@ -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. │');