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.
- package/bin/relay-pty-darwin-arm64 +0 -0
- package/bin/relay-pty-darwin-x64 +0 -0
- package/bin/relay-pty-linux-x64 +0 -0
- package/deploy/workspace/codex.config.toml +5 -0
- package/deploy/workspace/entrypoint.sh +10 -2
- package/dist/dashboard/out/404.html +1 -1
- package/dist/dashboard/out/app/onboarding.html +1 -1
- package/dist/dashboard/out/app/onboarding.txt +1 -1
- package/dist/dashboard/out/app.html +1 -1
- package/dist/dashboard/out/app.txt +1 -1
- package/dist/dashboard/out/cloud/link.html +1 -1
- package/dist/dashboard/out/cloud/link.txt +1 -1
- package/dist/dashboard/out/connect-repos.html +1 -1
- package/dist/dashboard/out/connect-repos.txt +1 -1
- package/dist/dashboard/out/history.html +1 -1
- package/dist/dashboard/out/history.txt +1 -1
- package/dist/dashboard/out/index.html +1 -1
- package/dist/dashboard/out/index.txt +1 -1
- package/dist/dashboard/out/login.html +1 -1
- package/dist/dashboard/out/login.txt +1 -1
- package/dist/dashboard/out/metrics.html +1 -1
- package/dist/dashboard/out/metrics.txt +1 -1
- package/dist/dashboard/out/pricing.html +1 -1
- package/dist/dashboard/out/pricing.txt +1 -1
- package/dist/dashboard/out/providers/setup/claude.html +1 -1
- package/dist/dashboard/out/providers/setup/claude.txt +1 -1
- package/dist/dashboard/out/providers/setup/codex.html +1 -1
- package/dist/dashboard/out/providers/setup/codex.txt +1 -1
- package/dist/dashboard/out/providers/setup/cursor.html +1 -1
- package/dist/dashboard/out/providers/setup/cursor.txt +1 -1
- package/dist/dashboard/out/providers.html +1 -1
- package/dist/dashboard/out/providers.txt +1 -1
- package/dist/dashboard/out/signup.html +1 -1
- package/dist/dashboard/out/signup.txt +1 -1
- package/dist/src/cli/index.js +131 -21
- package/package.json +20 -19
- package/packages/api-types/package.json +1 -1
- package/packages/bridge/dist/index.d.ts +1 -1
- package/packages/bridge/dist/index.js +1 -1
- package/packages/bridge/dist/spawner.d.ts +18 -0
- package/packages/bridge/dist/spawner.js +144 -39
- package/packages/bridge/package.json +8 -7
- package/packages/cloud/package.json +6 -6
- package/packages/config/package.json +2 -2
- package/packages/continuity/package.json +1 -1
- package/packages/daemon/dist/connection.js +5 -1
- package/packages/daemon/dist/relay-ledger.d.ts +3 -1
- package/packages/daemon/dist/relay-ledger.js +8 -2
- package/packages/daemon/dist/router.js +13 -0
- package/packages/daemon/dist/server.d.ts +7 -0
- package/packages/daemon/dist/server.js +338 -4
- package/packages/daemon/package.json +12 -12
- package/packages/dashboard/dist/server.js +29 -5
- package/packages/dashboard/package.json +13 -12
- package/packages/dashboard/ui-dist/404.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.html +1 -1
- package/packages/dashboard/ui-dist/app/onboarding.txt +1 -1
- package/packages/dashboard/ui-dist/app.html +1 -1
- package/packages/dashboard/ui-dist/app.txt +1 -1
- package/packages/dashboard/ui-dist/cloud/link.html +1 -1
- package/packages/dashboard/ui-dist/cloud/link.txt +1 -1
- package/packages/dashboard/ui-dist/connect-repos.html +1 -1
- package/packages/dashboard/ui-dist/connect-repos.txt +1 -1
- package/packages/dashboard/ui-dist/history.html +1 -1
- package/packages/dashboard/ui-dist/history.txt +1 -1
- package/packages/dashboard/ui-dist/index.html +1 -1
- package/packages/dashboard/ui-dist/index.txt +1 -1
- package/packages/dashboard/ui-dist/login.html +1 -1
- package/packages/dashboard/ui-dist/login.txt +1 -1
- package/packages/dashboard/ui-dist/metrics.html +1 -1
- package/packages/dashboard/ui-dist/metrics.txt +1 -1
- package/packages/dashboard/ui-dist/pricing.html +1 -1
- package/packages/dashboard/ui-dist/pricing.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/claude.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/codex.txt +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.html +1 -1
- package/packages/dashboard/ui-dist/providers/setup/cursor.txt +1 -1
- package/packages/dashboard/ui-dist/providers.html +1 -1
- package/packages/dashboard/ui-dist/providers.txt +1 -1
- package/packages/dashboard/ui-dist/signup.html +1 -1
- package/packages/dashboard/ui-dist/signup.txt +1 -1
- package/packages/dashboard-server/dist/server.js +29 -5
- package/packages/dashboard-server/package.json +12 -12
- package/packages/hooks/package.json +4 -4
- package/packages/mcp/README.md +24 -3
- package/packages/mcp/dist/bin.js +13 -5
- package/packages/mcp/dist/client.d.ts +54 -1
- package/packages/mcp/dist/client.js +132 -18
- package/packages/mcp/dist/cloud.d.ts +12 -0
- package/packages/mcp/dist/cloud.js +125 -1
- package/packages/mcp/dist/file-transport.d.ts +97 -0
- package/packages/mcp/dist/file-transport.js +197 -0
- package/packages/mcp/dist/hybrid-client.d.ts +28 -0
- package/packages/mcp/dist/hybrid-client.js +159 -0
- package/packages/mcp/dist/index.d.ts +4 -2
- package/packages/mcp/dist/index.js +6 -2
- package/packages/mcp/dist/install.d.ts +23 -1
- package/packages/mcp/dist/install.js +229 -31
- package/packages/mcp/dist/server.js +7 -1
- package/packages/mcp/dist/simple.d.ts +1 -1
- package/packages/mcp/dist/tools/index.d.ts +1 -0
- package/packages/mcp/dist/tools/index.js +1 -0
- package/packages/mcp/dist/tools/relay-continuity.d.ts +35 -0
- package/packages/mcp/dist/tools/relay-continuity.js +101 -0
- package/packages/mcp/dist/tools/relay-health.d.ts +1 -4
- package/packages/mcp/dist/tools/relay-health.js +7 -15
- package/packages/mcp/dist/tools/relay-logs.js +4 -2
- package/packages/mcp/dist/tools/relay-metrics.d.ts +1 -4
- package/packages/mcp/dist/tools/relay-metrics.js +4 -15
- package/packages/mcp/dist/tools/relay-send.d.ts +2 -2
- package/packages/mcp/package.json +3 -2
- package/packages/memory/package.json +2 -2
- package/packages/policy/package.json +2 -2
- package/packages/protocol/dist/relay-pty-schemas.d.ts +14 -0
- package/packages/protocol/dist/types.d.ts +152 -2
- package/packages/protocol/package.json +1 -1
- package/packages/resiliency/package.json +1 -1
- package/packages/sdk/dist/client.js +7 -0
- package/packages/sdk/package.json +2 -2
- package/packages/spawner/package.json +1 -1
- package/packages/state/package.json +1 -1
- package/packages/storage/package.json +2 -2
- package/packages/telemetry/package.json +1 -1
- package/packages/trajectory/package.json +2 -2
- package/packages/user-directory/package.json +2 -2
- package/packages/utils/dist/logger.js +3 -1
- package/packages/utils/package.json +1 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.d.ts +28 -1
- package/packages/wrapper/dist/relay-pty-orchestrator.js +358 -43
- package/packages/wrapper/package.json +6 -6
- package/scripts/demos/README.md +79 -0
- package/scripts/demos/server-capacity.sh +69 -0
- package/scripts/demos/sprint-planning.sh +73 -0
- /package/dist/dashboard/out/_next/static/{h1U3qU5XIfQSy46M_SDsz → RgEj_9Y-mWbLaxggzni-X}/_buildManifest.js +0 -0
- /package/dist/dashboard/out/_next/static/{h1U3qU5XIfQSy46M_SDsz → RgEj_9Y-mWbLaxggzni-X}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{4WryIM4xHT22BbJ46YITr → RgEj_9Y-mWbLaxggzni-X}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{4WryIM4xHT22BbJ46YITr → RgEj_9Y-mWbLaxggzni-X}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{dS0EgrS-iG-_pkUVhBypz → UkLmDJOkaPWU2PaNQnkx5}/_buildManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{dS0EgrS-iG-_pkUVhBypz → UkLmDJOkaPWU2PaNQnkx5}/_ssgManifest.js +0 -0
- /package/packages/dashboard/ui-dist/_next/static/{h1U3qU5XIfQSy46M_SDsz → bv9xidgU2pXi7xxPoCAK-}/_buildManifest.js +0 -0
- /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
|
|
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-
|
|
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
|
|
78
|
-
const
|
|
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
|
|
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
|
|
97
|
-
if (settings.
|
|
98
|
-
settings.
|
|
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(
|
|
145
|
+
log.debug(`Setting ${config.enableAllKey}: true`);
|
|
101
146
|
}
|
|
102
147
|
// Ensure permissions.allow includes agent-relay MCP
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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 || '
|
|
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.
|
|
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
|
-
|
|
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} (
|
|
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
|
-
|
|
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.
|
|
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.
|
|
26
|
-
"@agent-relay/config": "2.0.
|
|
27
|
-
"@agent-relay/utils": "2.0.
|
|
28
|
-
"@agent-relay/policy": "2.0.
|
|
29
|
-
"@agent-relay/user-directory": "2.0.
|
|
30
|
-
"@agent-relay/wrapper": "2.0.
|
|
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.
|
|
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.
|
|
42
|
-
"@agent-relay/config": "2.0.
|
|
43
|
-
"@agent-relay/resiliency": "2.0.
|
|
44
|
-
"@agent-relay/storage": "2.0.
|
|
45
|
-
"@agent-relay/protocol": "2.0.
|
|
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.
|
|
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.
|
|
86
|
+
"@agent-relay/protocol": "2.0.14",
|
|
87
87
|
"zod": "^3.23.8",
|
|
88
88
|
"zod-to-json-schema": "^3.23.1"
|
|
89
89
|
},
|
|
@@ -222,7 +222,11 @@ export class Connection {
|
|
|
222
222
|
}
|
|
223
223
|
}
|
|
224
224
|
handleSend(envelope) {
|
|
225
|
-
|
|
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
|
|
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
|
|
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.
|
|
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
|
*/
|