generated from JetBrains/intellij-platform-plugin-template
-
Notifications
You must be signed in to change notification settings - Fork 463
Closed
Description
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
- Process Tool Registry: Central registry for all process management tools
- Tool Execution Engine: Handles tool invocation and lifecycle management
- Process State Manager: Tracks and manages process states
- 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
- Clean Architecture: Clear separation of concerns with tool-based design
- IntelliJ Integration: Leverages existing IntelliJ process management APIs
- Type Safety: Strongly typed inputs/outputs using Protocol Buffers
- Extensibility: Easy to add new process management capabilities
- Consistency: All tools follow the same interface pattern
- Error Handling: Comprehensive error handling and reporting
- 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
Labels
No labels