Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Process Tool #430

Copy link
Copy link
@phodal

Description

@phodal
Issue body actions

IntelliJ IDEA Process Management Tool Integration

Overview

This issue proposes a design for integrating process management capabilities into IntelliJ IDEA plugins, inspired by Cursor's tool schema approach. The goal is to provide a clean, extensible API for managing external processes within the IntelliJ ecosystem.

Design

Core Architecture

The design follows a tool-based architecture where each process management operation is encapsulated as a tool with well-defined inputs and outputs. This approach provides:

  • Consistency: All tools follow the same interface pattern
  • Extensibility: Easy to add new process management capabilities
  • Integration: Seamless integration with IntelliJ's existing process management APIs
  • Type Safety: Strongly typed inputs and outputs using Protocol Buffers

Key Components

  1. Process Tool Registry: Central registry for all process management tools
  2. Tool Execution Engine: Handles tool invocation and lifecycle management
  3. Process State Manager: Tracks and manages process states
  4. IntelliJ Integration Layer: Bridges tool operations with IntelliJ APIs

Process Lifecycle

Tool Request → Tool Registry → Tool Execution → IntelliJ API → Process State Update → Response

Schema API

Base Tool Interface

interface ProcessTool {
    val name: String
    val description: String
    val inputSchema: Descriptor
    val outputSchema: Descriptor
    
    suspend fun execute(
        project: Project,
        requestId: String,
        input: Struct,
        context: ToolContext
    ): ToolResponse
}

Process Management Tools

1. Launch Process Tool Schema

message LaunchProcessRequest {
    string command = 1;
    string working_directory = 2;
    map<string, string> environment = 3;
    bool wait_for_completion = 4;
    int32 timeout_seconds = 5;
    bool show_in_terminal = 6;
}

message LaunchProcessResponse {
    string process_id = 1;
    int32 exit_code = 2;
    string stdout = 3;
    string stderr = 4;
    bool timed_out = 5;
    ProcessStatus status = 6;
}

enum ProcessStatus {
    RUNNING = 0;
    COMPLETED = 1;
    FAILED = 2;
    TIMED_OUT = 3;
}

2. List Processes Tool Schema

message ListProcessesRequest {
    bool include_terminated = 1;
    int32 max_results = 2;
}

message ListProcessesResponse {
    repeated ProcessInfo processes = 1;
}

message ProcessInfo {
    string process_id = 1;
    string command = 2;
    string working_directory = 3;
    ProcessStatus status = 4;
    int32 exit_code = 5;
    int64 start_time = 6;
    int64 end_time = 7;
}

3. Kill Process Tool Schema

message KillProcessRequest {
    string process_id = 1;
    bool force = 2;
}

message KillProcessResponse {
    bool success = 1;
    string error_message = 2;
}

4. Read Process Output Tool Schema

message ReadProcessOutputRequest {
    string process_id = 1;
    bool include_stdout = 2;
    bool include_stderr = 3;
    int32 max_bytes = 4;
}

message ReadProcessOutputResponse {
    string stdout = 1;
    string stderr = 2;
    bool has_more = 3;
}

5. Write Process Input Tool Schema

message WriteProcessInputRequest {
    string process_id = 1;
    string input_data = 2;
    bool append_newline = 3;
}

message WriteProcessInputResponse {
    bool success = 1;
    string error_message = 2;
}

Tool Context

data class ToolContext(
    val project: Project,
    val sessionId: String,
    val userId: String?,
    val metadata: Map<String, String>
)

Example Code

Tool Implementation

class LaunchProcessTool : ProcessTool {
    override val name = "launch_process"
    override val description = "Launch a new process with specified command and options"
    override val inputSchema = LaunchProcessRequest.getDescriptor()
    override val outputSchema = LaunchProcessResponse.getDescriptor()

    override suspend fun execute(
        project: Project,
        requestId: String,
        input: Struct,
        context: ToolContext
    ): ToolResponse = withContext(Dispatchers.IO) {
        try {
            val request = ProtoUtils.convertStruct(input, LaunchProcessRequest::newBuilder).build()
            
            // Create IntelliJ run configuration
            val runManager = RunManager.getInstance(project)
            val configuration = runManager.createConfiguration(
                "process_$requestId", 
                ShConfigurationType::class.java
            )
            
            val shConfig = configuration.configuration as ShRunConfiguration
            shConfig.scriptText = request.command
            shConfig.scriptWorkingDirectory = request.workingDirectory
            
            // Set environment variables
            request.environmentMap.forEach { (key, value) ->
                shConfig.env.put(key, value)
            }
            
            // Create execution environment
            val executor = DefaultRunExecutor.getRunExecutorInstance()
            val envBuilder = ExecutionEnvironmentBuilder.createOrNull(executor, shConfig)
                ?: throw IllegalStateException("Failed to create execution environment")
            
            val executionEnv = envBuilder.build(object : ProgramRunner.Callback {
                override fun processStarted(descriptor: RunContentDescriptor?) {
                    // Handle process start
                }
                
                override fun processNotStarted(error: Throwable?) {
                    // Handle process start failure
                }
            })
            
            // Execute process
            val descriptor = executionEnv.runner.execute(executionEnv)
            val processHandler = descriptor.processHandler
            
            // Create process info
            val processInfo = ProcessInfo(
                processId = descriptor.executionId.toString(),
                command = request.command,
                workingDirectory = request.workingDirectory,
                status = ProcessStatus.RUNNING,
                startTime = System.currentTimeMillis()
            )
            
            // Store process info
            ProcessStateManager.getInstance(project).registerProcess(processInfo)
            
            // Wait for completion if requested
            if (request.waitForCompletion) {
                val completed = processHandler.waitFor(request.timeoutSeconds * 1000)
                val exitCode = processHandler.exitCode
                
                val response = LaunchProcessResponse.newBuilder()
                    .setProcessId(processInfo.processId)
                    .setStatus(if (completed) ProcessStatus.COMPLETED else ProcessStatus.TIMED_OUT)
                    .setTimedOut(!completed)
                
                if (exitCode != null) {
                    response.exitCode = exitCode
                }
                
                return@withContext ToolResponse.success(response.build())
            }
            
            ToolResponse.success(
                LaunchProcessResponse.newBuilder()
                    .setProcessId(processInfo.processId)
                    .setStatus(ProcessStatus.RUNNING)
                    .build()
            )
            
        } catch (e: Exception) {
            ToolResponse.error("Failed to launch process: ${e.message}")
        }
    }
}

Tool Registry

class ProcessToolRegistry {
    private val tools = mutableMapOf<String, ProcessTool>()
    
    fun registerTool(tool: ProcessTool) {
        tools[tool.name] = tool
    }
    
    fun getTool(name: String): ProcessTool? = tools[name]
    
    fun getAllTools(): List<ProcessTool> = tools.values.toList()
    
    companion object {
        @JvmStatic
        fun getInstance(project: Project): ProcessToolRegistry {
            return project.getService(ProcessToolRegistry::class.java)
        }
    }
}

Process State Manager

class ProcessStateManager {
    private val processes = ConcurrentHashMap<String, ProcessInfo>()
    private val processHandlers = ConcurrentHashMap<String, ProcessHandler>()
    
    fun registerProcess(processInfo: ProcessInfo) {
        processes[processInfo.processId] = processInfo
    }
    
    fun updateProcessStatus(processId: String, status: ProcessStatus, exitCode: Int? = null) {
        processes[processId]?.let { info ->
            processes[processId] = info.copy(
                status = status,
                exitCode = exitCode,
                endTime = if (status in listOf(ProcessStatus.COMPLETED, ProcessStatus.FAILED)) 
                    System.currentTimeMillis() else null
            )
        }
    }
    
    fun getProcess(processId: String): ProcessInfo? = processes[processId]
    
    fun getAllProcesses(): List<ProcessInfo> = processes.values.toList()
    
    fun removeProcess(processId: String) {
        processes.remove(processId)
        processHandlers.remove(processId)
    }
    
    companion object {
        @JvmStatic
        fun getInstance(project: Project): ProcessStateManager {
            return project.getService(ProcessStateManager::class.java)
        }
    }
}

Tool Execution Engine

class ToolExecutionEngine(private val project: Project) {
    private val toolRegistry = ProcessToolRegistry.getInstance(project)
    
    suspend fun executeTool(
        toolName: String,
        requestId: String,
        input: Struct,
        context: ToolContext
    ): ToolResponse {
        val tool = toolRegistry.getTool(toolName)
            ?: return ToolResponse.error("Tool '$toolName' not found")
        
        return try {
            tool.execute(project, requestId, input, context)
        } catch (e: Exception) {
            ToolResponse.error("Tool execution failed: ${e.message}")
        }
    }
    
    fun getAvailableTools(): List<ToolInfo> {
        return toolRegistry.getAllTools().map { tool ->
            ToolInfo(
                name = tool.name,
                description = tool.description,
                inputSchema = tool.inputSchema,
                outputSchema = tool.outputSchema
            )
        }
    }
}

data class ToolInfo(
    val name: String,
    val description: String,
    val inputSchema: Descriptor,
    val outputSchema: Descriptor
)

Plugin Integration

class ProcessManagementPlugin : ApplicationPlugin {
    override fun init() {
        // Register default tools
        val toolRegistry = ProcessToolRegistry.getInstance()
        toolRegistry.registerTool(LaunchProcessTool())
        toolRegistry.registerTool(ListProcessesTool())
        toolRegistry.registerTool(KillProcessTool())
        toolRegistry.registerTool(ReadProcessOutputTool())
        toolRegistry.registerTool(WriteProcessInputTool())
    }
}

Usage Example

// Example: Launch a process
val engine = ToolExecutionEngine(project)
val context = ToolContext(project, "session_123", "user_456", emptyMap())

val input = Struct.newBuilder()
    .putFields("command", Value.newBuilder().setStringValue("ls -la").build())
    .putFields("working_directory", Value.newBuilder().setStringValue("/tmp").build())
    .putFields("wait_for_completion", Value.newBuilder().setBoolValue(true).build())
    .build()

val response = engine.executeTool("launch_process", "req_789", input, context)

// Example: List all processes
val listInput = Struct.newBuilder().build()
val listResponse = engine.executeTool("list_processes", "req_790", listInput, context)

Benefits

  1. Clean Architecture: Clear separation of concerns with tool-based design
  2. IntelliJ Integration: Leverages existing IntelliJ process management APIs
  3. Type Safety: Strongly typed inputs/outputs using Protocol Buffers
  4. Extensibility: Easy to add new process management capabilities
  5. Consistency: All tools follow the same interface pattern
  6. Error Handling: Comprehensive error handling and reporting
  7. State Management: Centralized process state tracking

Implementation Notes

  • All tools should be implemented as Kotlin coroutines for non-blocking operations
  • Process state should be persisted across IDE sessions
  • Tools should handle IntelliJ-specific process lifecycle events
  • Consider implementing process output streaming for long-running processes
  • Add support for process groups and dependencies
  • Implement process resource monitoring and cleanup

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.