agent-relay 2.0.12 → 2.0.14

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 (143) hide show
  1. package/bin/relay-pty-darwin-arm64 +0 -0
  2. package/bin/relay-pty-darwin-x64 +0 -0
  3. package/bin/relay-pty-linux-x64 +0 -0
  4. package/deploy/workspace/codex.config.toml +5 -0
  5. package/deploy/workspace/entrypoint.sh +10 -2
  6. package/dist/dashboard/out/404.html +1 -1
  7. package/dist/dashboard/out/app/onboarding.html +1 -1
  8. package/dist/dashboard/out/app/onboarding.txt +1 -1
  9. package/dist/dashboard/out/app.html +1 -1
  10. package/dist/dashboard/out/app.txt +1 -1
  11. package/dist/dashboard/out/cloud/link.html +1 -1
  12. package/dist/dashboard/out/cloud/link.txt +1 -1
  13. package/dist/dashboard/out/connect-repos.html +1 -1
  14. package/dist/dashboard/out/connect-repos.txt +1 -1
  15. package/dist/dashboard/out/history.html +1 -1
  16. package/dist/dashboard/out/history.txt +1 -1
  17. package/dist/dashboard/out/index.html +1 -1
  18. package/dist/dashboard/out/index.txt +1 -1
  19. package/dist/dashboard/out/login.html +1 -1
  20. package/dist/dashboard/out/login.txt +1 -1
  21. package/dist/dashboard/out/metrics.html +1 -1
  22. package/dist/dashboard/out/metrics.txt +1 -1
  23. package/dist/dashboard/out/pricing.html +1 -1
  24. package/dist/dashboard/out/pricing.txt +1 -1
  25. package/dist/dashboard/out/providers/setup/claude.html +1 -1
  26. package/dist/dashboard/out/providers/setup/claude.txt +1 -1
  27. package/dist/dashboard/out/providers/setup/codex.html +1 -1
  28. package/dist/dashboard/out/providers/setup/codex.txt +1 -1
  29. package/dist/dashboard/out/providers/setup/cursor.html +1 -1
  30. package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
  31. package/dist/dashboard/out/providers.html +1 -1
  32. package/dist/dashboard/out/providers.txt +1 -1
  33. package/dist/dashboard/out/signup.html +1 -1
  34. package/dist/dashboard/out/signup.txt +1 -1
  35. package/dist/src/cli/index.js +131 -21
  36. package/package.json +20 -19
  37. package/packages/api-types/package.json +1 -1
  38. package/packages/bridge/dist/index.d.ts +1 -1
  39. package/packages/bridge/dist/index.js +1 -1
  40. package/packages/bridge/dist/spawner.d.ts +18 -0
  41. package/packages/bridge/dist/spawner.js +144 -39
  42. package/packages/bridge/package.json +8 -7
  43. package/packages/cloud/package.json +6 -6
  44. package/packages/config/package.json +2 -2
  45. package/packages/continuity/package.json +1 -1
  46. package/packages/daemon/dist/connection.js +5 -1
  47. package/packages/daemon/dist/relay-ledger.d.ts +3 -1
  48. package/packages/daemon/dist/relay-ledger.js +8 -2
  49. package/packages/daemon/dist/router.js +13 -0
  50. package/packages/daemon/dist/server.d.ts +7 -0
  51. package/packages/daemon/dist/server.js +338 -4
  52. package/packages/daemon/package.json +12 -12
  53. package/packages/dashboard/dist/server.js +29 -5
  54. package/packages/dashboard/package.json +13 -12
  55. package/packages/dashboard/ui-dist/404.html +1 -1
  56. package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
  57. package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
  58. package/packages/dashboard/ui-dist/app.html +1 -1
  59. package/packages/dashboard/ui-dist/app.txt +1 -1
  60. package/packages/dashboard/ui-dist/cloud/link.html +1 -1
  61. package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
  62. package/packages/dashboard/ui-dist/connect-repos.html +1 -1
  63. package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
  64. package/packages/dashboard/ui-dist/history.html +1 -1
  65. package/packages/dashboard/ui-dist/history.txt +1 -1
  66. package/packages/dashboard/ui-dist/index.html +1 -1
  67. package/packages/dashboard/ui-dist/index.txt +1 -1
  68. package/packages/dashboard/ui-dist/login.html +1 -1
  69. package/packages/dashboard/ui-dist/login.txt +1 -1
  70. package/packages/dashboard/ui-dist/metrics.html +1 -1
  71. package/packages/dashboard/ui-dist/metrics.txt +1 -1
  72. package/packages/dashboard/ui-dist/pricing.html +1 -1
  73. package/packages/dashboard/ui-dist/pricing.txt +1 -1
  74. package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
  75. package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
  76. package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
  77. package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
  78. package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
  79. package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
  80. package/packages/dashboard/ui-dist/providers.html +1 -1
  81. package/packages/dashboard/ui-dist/providers.txt +1 -1
  82. package/packages/dashboard/ui-dist/signup.html +1 -1
  83. package/packages/dashboard/ui-dist/signup.txt +1 -1
  84. package/packages/dashboard-server/dist/server.js +29 -5
  85. package/packages/dashboard-server/package.json +12 -12
  86. package/packages/hooks/package.json +4 -4
  87. package/packages/mcp/README.md +24 -3
  88. package/packages/mcp/dist/bin.js +13 -5
  89. package/packages/mcp/dist/client.d.ts +54 -1
  90. package/packages/mcp/dist/client.js +132 -18
  91. package/packages/mcp/dist/cloud.d.ts +12 -0
  92. package/packages/mcp/dist/cloud.js +125 -1
  93. package/packages/mcp/dist/file-transport.d.ts +97 -0
  94. package/packages/mcp/dist/file-transport.js +197 -0
  95. package/packages/mcp/dist/hybrid-client.d.ts +28 -0
  96. package/packages/mcp/dist/hybrid-client.js +159 -0
  97. package/packages/mcp/dist/index.d.ts +4 -2
  98. package/packages/mcp/dist/index.js +6 -2
  99. package/packages/mcp/dist/install.d.ts +23 -1
  100. package/packages/mcp/dist/install.js +229 -31
  101. package/packages/mcp/dist/server.js +7 -1
  102. package/packages/mcp/dist/simple.d.ts +1 -1
  103. package/packages/mcp/dist/tools/index.d.ts +1 -0
  104. package/packages/mcp/dist/tools/index.js +1 -0
  105. package/packages/mcp/dist/tools/relay-continuity.d.ts +35 -0
  106. package/packages/mcp/dist/tools/relay-continuity.js +101 -0
  107. package/packages/mcp/dist/tools/relay-health.d.ts +1 -4
  108. package/packages/mcp/dist/tools/relay-health.js +7 -15
  109. package/packages/mcp/dist/tools/relay-logs.js +4 -2
  110. package/packages/mcp/dist/tools/relay-metrics.d.ts +1 -4
  111. package/packages/mcp/dist/tools/relay-metrics.js +4 -15
  112. package/packages/mcp/dist/tools/relay-send.d.ts +2 -2
  113. package/packages/mcp/package.json +3 -2
  114. package/packages/memory/package.json +2 -2
  115. package/packages/policy/package.json +2 -2
  116. package/packages/protocol/dist/relay-pty-schemas.d.ts +14 -0
  117. package/packages/protocol/dist/types.d.ts +152 -2
  118. package/packages/protocol/package.json +1 -1
  119. package/packages/resiliency/package.json +1 -1
  120. package/packages/sdk/dist/client.js +7 -0
  121. package/packages/sdk/package.json +2 -2
  122. package/packages/spawner/package.json +1 -1
  123. package/packages/state/package.json +1 -1
  124. package/packages/storage/package.json +2 -2
  125. package/packages/telemetry/package.json +1 -1
  126. package/packages/trajectory/package.json +2 -2
  127. package/packages/user-directory/package.json +2 -2
  128. package/packages/utils/dist/logger.js +3 -1
  129. package/packages/utils/package.json +1 -1
  130. package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +28 -1
  131. package/packages/wrapper/dist/relay-pty-orchestrator.js +358 -43
  132. package/packages/wrapper/package.json +6 -6
  133. package/scripts/demos/README.md +79 -0
  134. package/scripts/demos/server-capacity.sh +69 -0
  135. package/scripts/demos/sprint-planning.sh +73 -0
  136. /package/dist/dashboard/out/_next/static/{h1U3qU5XIfQSy46M_SDsz → RgEj_9Y-mWbLaxggzni-X}/_buildManifest.js +0 -0
  137. /package/dist/dashboard/out/_next/static/{h1U3qU5XIfQSy46M_SDsz → RgEj_9Y-mWbLaxggzni-X}/_ssgManifest.js +0 -0
  138. /package/packages/dashboard/ui-dist/_next/static/{4WryIM4xHT22BbJ46YITr → RgEj_9Y-mWbLaxggzni-X}/_buildManifest.js +0 -0
  139. /package/packages/dashboard/ui-dist/_next/static/{4WryIM4xHT22BbJ46YITr → RgEj_9Y-mWbLaxggzni-X}/_ssgManifest.js +0 -0
  140. /package/packages/dashboard/ui-dist/_next/static/{dS0EgrS-iG-_pkUVhBypz → UkLmDJOkaPWU2PaNQnkx5}/_buildManifest.js +0 -0
  141. /package/packages/dashboard/ui-dist/_next/static/{dS0EgrS-iG-_pkUVhBypz → UkLmDJOkaPWU2PaNQnkx5}/_ssgManifest.js +0 -0
  142. /package/packages/dashboard/ui-dist/_next/static/{h1U3qU5XIfQSy46M_SDsz → bv9xidgU2pXi7xxPoCAK-}/_buildManifest.js +0 -0
  143. /package/packages/dashboard/ui-dist/_next/static/{h1U3qU5XIfQSy46M_SDsz → bv9xidgU2pXi7xxPoCAK-}/_ssgManifest.js +0 -0
@@ -23,6 +23,7 @@ import { AgentPolicyService } from '@agent-relay/policy';
23
23
  import { buildClaudeArgs, findAgentConfig } from '@agent-relay/config/agent-config';
24
24
  import { composeForAgent } from '@agent-relay/wrapper';
25
25
  import { getUserDirectoryService } from '@agent-relay/user-directory';
26
+ import { installMcpConfig } from '@agent-relay/mcp';
26
27
  // Logger instance for spawner (uses daemon log system instead of console)
27
28
  const log = createLogger('spawner');
28
29
  /**
@@ -63,23 +64,67 @@ function extractGhTokenFromHosts(content) {
63
64
  return null;
64
65
  }
65
66
  /**
66
- * Ensure MCP permissions are pre-configured for Claude Code.
67
+ * Ensure MCP permissions are pre-configured for the given CLI type.
67
68
  * This prevents MCP approval prompts from blocking agent initialization.
68
69
  *
69
- * Creates/updates .claude/settings.local.json with:
70
+ * For Claude Code: Creates/updates .claude/settings.local.json with:
70
71
  * - enableAllProjectMcpServers: true (auto-approve project MCP servers)
71
- * - permissions.allow: ["mcp__agent-relay"] (pre-approve agent-relay MCP tools)
72
+ * - permissions.allow: ["mcp__agent-relay__*"] (pre-approve all agent-relay MCP tools)
73
+ *
74
+ * For Cursor: Creates/updates .cursor/settings.json with MCP permissions
75
+ * For Gemini: Creates/updates .gemini/settings.json with MCP permissions
76
+ * For Windsurf: Creates/updates .windsurf/settings.json with MCP permissions
77
+ * Other CLIs: May use CLI flags instead of config-based permissions
72
78
  *
73
79
  * @param projectRoot - The project root directory
80
+ * @param cliType - The CLI type (claude, codex, gemini, cursor, etc.)
74
81
  * @param debug - Whether to log debug information
75
82
  */
76
- function ensureMcpPermissions(projectRoot, debug = false) {
77
- const settingsDir = path.join(projectRoot, '.claude');
78
- const settingsPath = path.join(settingsDir, 'settings.local.json');
83
+ export function ensureMcpPermissions(projectRoot, cliType, debug = false) {
84
+ const home = process.env.HOME || '';
85
+ const configMap = {
86
+ claude: {
87
+ // Use global settings for Claude
88
+ // enableAllProjectMcpServers enables project-local .mcp.json files
89
+ settingsDir: path.join(home, '.claude'),
90
+ settingsFile: 'settings.local.json',
91
+ permissionKey: 'permissions.allow',
92
+ enableAllKey: 'enableAllProjectMcpServers',
93
+ },
94
+ cursor: {
95
+ settingsDir: path.join(projectRoot, '.cursor'),
96
+ settingsFile: 'settings.json',
97
+ permissionKey: 'permissions.allow',
98
+ },
99
+ gemini: {
100
+ settingsDir: path.join(projectRoot, '.gemini'),
101
+ settingsFile: 'settings.json',
102
+ permissionKey: 'permissions.allow',
103
+ },
104
+ windsurf: {
105
+ settingsDir: path.join(projectRoot, '.windsurf'),
106
+ settingsFile: 'settings.json',
107
+ permissionKey: 'permissions.allow',
108
+ },
109
+ // Codex uses TOML config and --dangerously-bypass-approvals-and-sandbox flag
110
+ // OpenCode and Droid may not need config-based permissions
111
+ };
112
+ // Normalize CLI type
113
+ const normalizedCli = cliType.toLowerCase().replace(/^(claude|codex|gemini|cursor|agent|windsurf).*/, '$1');
114
+ // Map 'agent' (Cursor CLI) to 'cursor'
115
+ const effectiveCli = normalizedCli === 'agent' ? 'cursor' : normalizedCli;
116
+ const config = configMap[effectiveCli];
117
+ if (!config) {
118
+ // CLI doesn't use config-based MCP permissions (uses CLI flags instead)
119
+ if (debug)
120
+ log.debug(`CLI ${cliType} uses flag-based permissions, skipping config setup`);
121
+ return;
122
+ }
123
+ const settingsPath = path.join(config.settingsDir, config.settingsFile);
79
124
  try {
80
- // Ensure .claude directory exists
81
- if (!fs.existsSync(settingsDir)) {
82
- fs.mkdirSync(settingsDir, { recursive: true });
125
+ // Ensure settings directory exists
126
+ if (!fs.existsSync(config.settingsDir)) {
127
+ fs.mkdirSync(config.settingsDir, { recursive: true });
83
128
  }
84
129
  // Read existing settings or start fresh
85
130
  let settings = {};
@@ -93,28 +138,39 @@ function ensureMcpPermissions(projectRoot, debug = false) {
93
138
  settings = {};
94
139
  }
95
140
  }
96
- // Set enableAllProjectMcpServers to auto-approve MCP servers in .mcp.json
97
- if (settings.enableAllProjectMcpServers !== true) {
98
- settings.enableAllProjectMcpServers = true;
141
+ // Set enableAllProjectMcpServers if supported (Claude-specific)
142
+ if (config.enableAllKey && settings[config.enableAllKey] !== true) {
143
+ settings[config.enableAllKey] = true;
99
144
  if (debug)
100
- log.debug('Setting enableAllProjectMcpServers: true');
145
+ log.debug(`Setting ${config.enableAllKey}: true`);
101
146
  }
102
147
  // Ensure permissions.allow includes agent-relay MCP
103
- // Format: "mcp__serverName" approves all tools from that server
104
- if (!settings.permissions || typeof settings.permissions !== 'object') {
105
- settings.permissions = {};
106
- }
107
- const permissions = settings.permissions;
108
- if (!Array.isArray(permissions.allow)) {
109
- permissions.allow = [];
110
- }
111
- const allowList = permissions.allow;
112
- // Add agent-relay MCP permission if not already present
113
- const agentRelayPermission = 'mcp__agent-relay';
114
- if (!allowList.includes(agentRelayPermission)) {
115
- allowList.push(agentRelayPermission);
116
- if (debug)
117
- log.debug(`Added MCP permission: ${agentRelayPermission}`);
148
+ if (config.permissionKey) {
149
+ // Parse nested key (e.g., 'permissions.allow')
150
+ const keyParts = config.permissionKey.split('.');
151
+ let current = settings;
152
+ // Navigate/create nested structure
153
+ for (let i = 0; i < keyParts.length - 1; i++) {
154
+ const key = keyParts[i];
155
+ if (!current[key] || typeof current[key] !== 'object') {
156
+ current[key] = {};
157
+ }
158
+ current = current[key];
159
+ }
160
+ // Ensure allow list exists
161
+ const finalKey = keyParts[keyParts.length - 1];
162
+ if (!Array.isArray(current[finalKey])) {
163
+ current[finalKey] = [];
164
+ }
165
+ const allowList = current[finalKey];
166
+ // Add agent-relay MCP permission if not already present
167
+ // Format: mcp__<serverName>__* approves all tools from that server
168
+ const agentRelayPermission = 'mcp__agent-relay__*';
169
+ if (!allowList.includes(agentRelayPermission)) {
170
+ allowList.push(agentRelayPermission);
171
+ if (debug)
172
+ log.debug(`Added MCP permission: ${agentRelayPermission}`);
173
+ }
118
174
  }
119
175
  // Write updated settings
120
176
  fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
@@ -124,6 +180,7 @@ function ensureMcpPermissions(projectRoot, debug = false) {
124
180
  catch (err) {
125
181
  // Log but don't fail - this is a best-effort optimization
126
182
  log.warn('Failed to pre-configure MCP permissions', {
183
+ cli: cliType,
127
184
  error: err instanceof Error ? err.message : String(err),
128
185
  });
129
186
  }
@@ -544,14 +601,12 @@ export class AgentSpawner {
544
601
  // Command wasn't resolved - it might not exist
545
602
  log.warn(`Could not resolve path for '${commandName}', spawn may fail`);
546
603
  }
604
+ // Pre-configure MCP permissions for all supported CLIs
605
+ // This creates/updates CLI-specific settings files with agent-relay permissions
606
+ ensureMcpPermissions(this.projectRoot, commandName, debug);
547
607
  // Add --dangerously-skip-permissions for Claude agents
548
608
  const isClaudeCli = commandName.startsWith('claude');
549
609
  if (isClaudeCli) {
550
- // Pre-configure MCP permissions to avoid approval prompts blocking initialization
551
- // This creates/updates .claude/settings.local.json with:
552
- // - enableAllProjectMcpServers: true
553
- // - permissions.allow: ["mcp__agent-relay"]
554
- ensureMcpPermissions(this.projectRoot, debug);
555
610
  if (!args.includes('--dangerously-skip-permissions')) {
556
611
  args.push('--dangerously-skip-permissions');
557
612
  }
@@ -573,7 +628,7 @@ export class AgentSpawner {
573
628
  ? mapModelToCli(modelFromProfile)
574
629
  : mapModelToCli(); // defaults to claude:sonnet
575
630
  // Extract effective model name for logging
576
- const effectiveModel = modelFromProfile || 'sonnet';
631
+ const effectiveModel = modelFromProfile || 'opus';
577
632
  const configuredArgs = buildClaudeArgs(name, args, this.projectRoot);
578
633
  // Replace args with configured version (includes --model and --agent if found)
579
634
  args.length = 0;
@@ -593,17 +648,44 @@ export class AgentSpawner {
593
648
  if (isGeminiCli && !args.includes('--yolo')) {
594
649
  args.push('--yolo');
595
650
  }
651
+ // Auto-install MCP config if not present (project-local)
652
+ // Uses .mcp.json in the project root - doesn't modify global settings
653
+ const projectMcpConfigPath = path.join(this.projectRoot, '.mcp.json');
654
+ const mcpSocketPath = path.join(this.projectRoot, '.agent-relay', 'relay.sock');
655
+ const hasMcpConfig = fs.existsSync(projectMcpConfigPath);
656
+ if (!hasMcpConfig) {
657
+ try {
658
+ const result = installMcpConfig(projectMcpConfigPath, {
659
+ configKey: 'mcpServers',
660
+ // Set RELAY_SOCKET so MCP server finds daemon regardless of CWD
661
+ env: { RELAY_SOCKET: mcpSocketPath },
662
+ });
663
+ if (result.success) {
664
+ if (debug)
665
+ log.debug(`Auto-installed MCP config at ${projectMcpConfigPath}`);
666
+ }
667
+ else {
668
+ log.warn(`Failed to auto-install MCP config: ${result.error}`);
669
+ }
670
+ }
671
+ catch (err) {
672
+ log.warn('Failed to auto-install MCP config', {
673
+ error: err instanceof Error ? err.message : String(err),
674
+ });
675
+ }
676
+ }
596
677
  // Check if MCP tools are available
597
678
  // Must verify BOTH conditions (matching inbox hook behavior from commit 18bab59):
598
- // 1. .mcp.json config exists in project
679
+ // 1. MCP config exists (user or project scope)
599
680
  // 2. Relay daemon socket is accessible (daemon must be running)
600
681
  // Without both, MCP context would be shown but tools wouldn't work
601
- const mcpConfigPath = path.join(this.projectRoot, '.mcp.json');
602
682
  // Use the actual socket path from config (project-local .agent-relay/relay.sock)
603
683
  // or fall back to environment variable
604
684
  const relaySocket = this.socketPath || process.env.RELAY_SOCKET || path.join(this.projectRoot, '.agent-relay', 'relay.sock');
605
685
  let hasMcp = false;
606
- if (fs.existsSync(mcpConfigPath)) {
686
+ // Check either user-scope or project-scope MCP config
687
+ // hasMcpConfig was already computed above
688
+ if (hasMcpConfig) {
607
689
  try {
608
690
  hasMcp = fs.statSync(relaySocket).isSocket();
609
691
  }
@@ -613,7 +695,7 @@ export class AgentSpawner {
613
695
  }
614
696
  }
615
697
  if (debug && hasMcp)
616
- log.debug(`MCP tools available for ${name} (found ${mcpConfigPath} and socket ${relaySocket})`);
698
+ log.debug(`MCP tools available for ${name} (MCP config found, socket ${relaySocket})`);
617
699
  // Inject relay protocol instructions via CLI-specific system prompt
618
700
  let relayInstructions = getRelayInstructions(name, { hasMcp, includeWorkflowConventions });
619
701
  // Compose role-specific prompts if agent has a role defined in .claude/agents/
@@ -655,7 +737,28 @@ export class AgentSpawner {
655
737
  // Note: Spawned agents CAN spawn sub-workers intentionally - the parser is strict enough
656
738
  // to avoid accidental spawns from documentation text (requires line start, PascalCase, known CLI)
657
739
  // Use request.cwd if specified, otherwise use projectRoot
658
- const agentCwd = request.cwd || this.projectRoot;
740
+ // Validate cwd to prevent path traversal attacks
741
+ let agentCwd;
742
+ if (request.cwd && typeof request.cwd === 'string') {
743
+ // Resolve cwd relative to project root and ensure it stays within that root
744
+ const resolvedCwd = path.resolve(this.projectRoot, request.cwd);
745
+ const normalizedProjectRoot = path.resolve(this.projectRoot);
746
+ const projectRootWithSep = normalizedProjectRoot.endsWith(path.sep)
747
+ ? normalizedProjectRoot
748
+ : normalizedProjectRoot + path.sep;
749
+ // Ensure the resolved cwd is within the project root to prevent traversal
750
+ if (resolvedCwd !== normalizedProjectRoot && !resolvedCwd.startsWith(projectRootWithSep)) {
751
+ return {
752
+ success: false,
753
+ name,
754
+ error: `Invalid cwd: "${request.cwd}" must be within the project root`,
755
+ };
756
+ }
757
+ agentCwd = resolvedCwd;
758
+ }
759
+ else {
760
+ agentCwd = this.projectRoot;
761
+ }
659
762
  // Log whether nested spawning will be enabled for this agent
660
763
  log.info(`Spawning ${name}: dashboardPort=${this.dashboardPort || 'none'} (${this.dashboardPort ? 'nested spawns enabled' : 'nested spawns disabled'})`);
661
764
  let userEnv;
@@ -765,6 +868,8 @@ export class AgentSpawner {
765
868
  // Pass socket path for MCP server discovery
766
869
  // This allows the MCP server (started by Claude Code) to connect to the daemon
767
870
  ...(relaySocket ? { RELAY_SOCKET: relaySocket } : {}),
871
+ // Pass agent name so MCP server knows its identity
872
+ RELAY_AGENT_NAME: name,
768
873
  },
769
874
  streamLogs: true,
770
875
  shadowOf: request.shadowOf,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/bridge",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "Multi-project bridge client utilities for Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -22,12 +22,13 @@
22
22
  "test:watch": "vitest"
23
23
  },
24
24
  "dependencies": {
25
- "@agent-relay/protocol": "2.0.12",
26
- "@agent-relay/config": "2.0.12",
27
- "@agent-relay/utils": "2.0.12",
28
- "@agent-relay/policy": "2.0.12",
29
- "@agent-relay/user-directory": "2.0.12",
30
- "@agent-relay/wrapper": "2.0.12"
25
+ "@agent-relay/protocol": "2.0.14",
26
+ "@agent-relay/config": "2.0.14",
27
+ "@agent-relay/utils": "2.0.14",
28
+ "@agent-relay/policy": "2.0.14",
29
+ "@agent-relay/user-directory": "2.0.14",
30
+ "@agent-relay/wrapper": "2.0.14",
31
+ "@agent-relay/mcp": "2.0.14"
31
32
  },
32
33
  "devDependencies": {
33
34
  "@types/node": "^22.19.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/cloud",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "Cloud API server and services for Agent Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -38,11 +38,11 @@
38
38
  "test:watch": "vitest"
39
39
  },
40
40
  "dependencies": {
41
- "@agent-relay/wrapper": "2.0.12",
42
- "@agent-relay/config": "2.0.12",
43
- "@agent-relay/resiliency": "2.0.12",
44
- "@agent-relay/storage": "2.0.12",
45
- "@agent-relay/protocol": "2.0.12"
41
+ "@agent-relay/wrapper": "2.0.14",
42
+ "@agent-relay/config": "2.0.14",
43
+ "@agent-relay/resiliency": "2.0.14",
44
+ "@agent-relay/storage": "2.0.14",
45
+ "@agent-relay/protocol": "2.0.14"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/node": "^22.19.3",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/config",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "Shared configuration schemas and loaders for Agent Relay",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -83,7 +83,7 @@
83
83
  "test:watch": "vitest"
84
84
  },
85
85
  "dependencies": {
86
- "@agent-relay/protocol": "2.0.12",
86
+ "@agent-relay/protocol": "2.0.14",
87
87
  "zod": "^3.23.8",
88
88
  "zod-to-json-schema": "^3.23.1"
89
89
  },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agent-relay/continuity",
3
- "version": "2.0.12",
3
+ "version": "2.0.14",
4
4
  "description": "Session continuity manager for Relay (ledgers, handoffs, resume)",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -222,7 +222,11 @@ export class Connection {
222
222
  }
223
223
  }
224
224
  handleSend(envelope) {
225
- if (this._state !== 'ACTIVE') {
225
+ // Allow MCP-style messages (with envelope.from set) even when not in ACTIVE state.
226
+ // MCP tools use short-lived connections without HELLO handshake.
227
+ // The router will use envelope.from as the sender name for these messages.
228
+ const hasMcpSender = !!envelope.from && !!envelope.to;
229
+ if (this._state !== 'ACTIVE' && !hasMcpSender) {
226
230
  this.sendError('BAD_REQUEST', 'Not in ACTIVE state', false);
227
231
  return;
228
232
  }
@@ -86,6 +86,7 @@ export declare class RelayLedger {
86
86
  private stmtMarkArchived;
87
87
  private stmtGetPending;
88
88
  private stmtGetByPath;
89
+ private stmtIsActivePath;
89
90
  private stmtGetById;
90
91
  private stmtResetProcessing;
91
92
  private stmtCleanupArchived;
@@ -142,7 +143,8 @@ export declare class RelayLedger {
142
143
  */
143
144
  registerFile(sourcePath: string, agentName: string, messageType: string, fileSize: number, contentHash?: string, fileMtimeNs?: number, fileInode?: number, symlinkPath?: string): string;
144
145
  /**
145
- * Check if a file is already registered
146
+ * Check if a file is actively being processed (pending or processing).
147
+ * Returns false for archived/delivered/failed files so new files at the same path can be registered.
146
148
  */
147
149
  isFileRegistered(sourcePath: string): boolean;
148
150
  /**
@@ -39,6 +39,7 @@ export class RelayLedger {
39
39
  stmtMarkArchived;
40
40
  stmtGetPending;
41
41
  stmtGetByPath;
42
+ stmtIsActivePath;
42
43
  stmtGetById;
43
44
  stmtResetProcessing;
44
45
  stmtCleanupArchived;
@@ -139,6 +140,10 @@ export class RelayLedger {
139
140
  `);
140
141
  this.stmtGetByPath = this.db.prepare(`
141
142
  SELECT * FROM relay_files WHERE source_path = ?
143
+ `);
144
+ // Only check for pending/processing files (not archived/delivered/failed)
145
+ this.stmtIsActivePath = this.db.prepare(`
146
+ SELECT 1 FROM relay_files WHERE source_path = ? AND status IN ('pending', 'processing')
142
147
  `);
143
148
  this.stmtGetById = this.db.prepare(`
144
149
  SELECT * FROM relay_files WHERE file_id = ?
@@ -186,10 +191,11 @@ export class RelayLedger {
186
191
  return fileId;
187
192
  }
188
193
  /**
189
- * Check if a file is already registered
194
+ * Check if a file is actively being processed (pending or processing).
195
+ * Returns false for archived/delivered/failed files so new files at the same path can be registered.
190
196
  */
191
197
  isFileRegistered(sourcePath) {
192
- const row = this.stmtGetByPath.get(sourcePath);
198
+ const row = this.stmtIsActivePath.get(sourcePath);
193
199
  return row !== undefined;
194
200
  }
195
201
  /**
@@ -175,6 +175,19 @@ export class Router {
175
175
  oldConnectionId: existing.id,
176
176
  newConnectionId: connection.id,
177
177
  });
178
+ // Send fatal error before closing to prevent reconnection loop
179
+ const errorEnvelope = {
180
+ v: PROTOCOL_VERSION,
181
+ type: 'ERROR',
182
+ id: generateId(),
183
+ ts: Date.now(),
184
+ payload: {
185
+ code: 'DUPLICATE_CONNECTION',
186
+ message: `Another agent with name "${connection.agentName}" connected. This connection will be closed.`,
187
+ fatal: true,
188
+ },
189
+ };
190
+ existing.send(errorEnvelope);
178
191
  existing.close();
179
192
  this.connections.delete(existing.id);
180
193
  }
@@ -44,6 +44,8 @@ export declare class Daemon {
44
44
  private consensus?;
45
45
  private cloudSyncDebounceTimer?;
46
46
  private spawnManager?;
47
+ private shuttingDown;
48
+ private relayWatchdog?;
47
49
  /** Telemetry tracking */
48
50
  private startTime?;
49
51
  private agentSpawnCount;
@@ -86,6 +88,11 @@ export declare class Daemon {
86
88
  * Start the daemon.
87
89
  */
88
90
  start(): Promise<void>;
91
+ /**
92
+ * Initialize RelayWatchdog for MCP and file-based agents.
93
+ * Uses the ledger-based watchdog for durable file processing.
94
+ */
95
+ private initRelayWatchdog;
89
96
  /**
90
97
  * Initialize cloud sync service for cross-machine agent communication.
91
98
  */