From c862ca5a116db399b6f7e339139dbff5632a802d Mon Sep 17 00:00:00 2001 From: Pedram Amini Date: Thu, 8 Jan 2026 06:12:13 -0600 Subject: [PATCH] added Maestro Symphony Registration Manifest --- .../main/ipc/handlers/process.test.ts | 41 +++++++++++++++++++ src/main/ipc/handlers/process.ts | 13 +++--- symphony-registry.json | 21 ++++++++++ 3 files changed, 70 insertions(+), 5 deletions(-) create mode 100644 symphony-registry.json diff --git a/src/__tests__/main/ipc/handlers/process.test.ts b/src/__tests__/main/ipc/handlers/process.test.ts index c18c8eda..7cb52757 100644 --- a/src/__tests__/main/ipc/handlers/process.test.ts +++ b/src/__tests__/main/ipc/handlers/process.test.ts @@ -1130,6 +1130,47 @@ describe('process IPC handlers', () => { expect(remoteCommandArg).not.toContain('/opt/homebrew/bin/codex'); }); + it('should use sessionCustomPath for SSH remote when user specifies a custom path', async () => { + // When user sets a custom path for a session, that path should be used on the remote + // This allows users to specify the exact binary location on the remote host + const mockAgent = { + id: 'codex', + name: 'Codex', + binaryName: 'codex', + path: '/opt/homebrew/bin/codex', // Local path + requiresPty: false, + }; + + mockAgentDetector.getAgent.mockResolvedValue(mockAgent); + mockSettingsStore.get.mockImplementation((key, defaultValue) => { + if (key === 'sshRemotes') return [mockSshRemote]; + return defaultValue; + }); + mockProcessManager.spawn.mockReturnValue({ pid: 12345, success: true }); + + const handler = handlers.get('process:spawn'); + await handler!({} as any, { + sessionId: 'session-1', + toolType: 'codex', + cwd: '/home/devuser/project', + command: '/opt/homebrew/bin/codex', + args: ['exec', '--json'], + sessionCustomPath: '/usr/local/bin/codex', // User's custom path for the remote + sessionSshRemoteConfig: { + enabled: true, + remoteId: 'remote-1', + }, + }); + + const spawnCall = mockProcessManager.spawn.mock.calls[0][0]; + expect(spawnCall.command).toBe('ssh'); + + // Should use the custom path, not binaryName or local path + const remoteCommandArg = spawnCall.args[spawnCall.args.length - 1]; + expect(remoteCommandArg).toContain("'/usr/local/bin/codex'"); + expect(remoteCommandArg).not.toContain('/opt/homebrew/bin/codex'); + }); + it('should fall back to config.command when agent.binaryName is not available', async () => { // Edge case: if agent lookup fails or binaryName is undefined, fall back to command mockAgentDetector.getAgent.mockResolvedValue(null); // Agent not found diff --git a/src/main/ipc/handlers/process.ts b/src/main/ipc/handlers/process.ts index 8a4bbf74..90c88e2d 100644 --- a/src/main/ipc/handlers/process.ts +++ b/src/main/ipc/handlers/process.ts @@ -268,11 +268,12 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void // The cwd is the local project path which may not exist on remote // Remote should use remoteWorkingDir from SSH config if set // - // IMPORTANT: For remote execution, use the agent's binaryName (e.g., 'codex', 'claude') - // instead of the locally detected full path (e.g., '/opt/homebrew/bin/codex'). - // The remote shell's PATH will resolve the binary correctly. This fixes the issue - // where Maestro would try to execute a local macOS path on a remote Linux host. - const remoteCommand = agent?.binaryName || config.command; + // Determine the command to run on the remote host: + // 1. If user set a session-specific custom path, use that (they configured it for the remote) + // 2. Otherwise, use the agent's binaryName (e.g., 'codex', 'claude') and let + // the remote shell's PATH resolve it. This avoids using local paths like + // '/opt/homebrew/bin/codex' which don't exist on the remote host. + const remoteCommand = config.sessionCustomPath || agent?.binaryName || config.command; const sshCommand = await buildSshCommand(sshResult.config, { command: remoteCommand, args: sshArgs, @@ -294,6 +295,8 @@ export function registerProcessHandlers(deps: ProcessHandlerDependencies): void source: sshResult.source, localCommand: config.command, remoteCommand: remoteCommand, + customPath: config.sessionCustomPath || null, + hasCustomEnvVars: !!effectiveCustomEnvVars && Object.keys(effectiveCustomEnvVars).length > 0, sshCommand: `${sshCommand.command} ${sshCommand.args.join(' ')}`, }); } diff --git a/symphony-registry.json b/symphony-registry.json new file mode 100644 index 00000000..67258208 --- /dev/null +++ b/symphony-registry.json @@ -0,0 +1,21 @@ +{ + "schemaVersion": "1.0", + "lastUpdated": "2025-01-01T00:00:00Z", + "repositories": [ + { + "slug": "pedramamini/Maestro", + "name": "Maestro", + "description": "Desktop app for managing multiple AI coding assistants with a keyboard-first interface.", + "url": "https://github.com/pedramamini/Maestro", + "category": "developer-tools", + "tags": ["electron", "ai", "productivity", "typescript"], + "maintainer": { + "name": "Pedram Amini", + "url": "https://github.com/pedramamini" + }, + "isActive": true, + "featured": true, + "addedAt": "2025-01-01" + } + ] +}