feat(runner): add execution policy abstract for local running

This commit is contained in:
2026-03-13 14:45:50 +08:00
parent 8463eb9dae
commit 97bb897407
4 changed files with 137 additions and 6 deletions

View File

@@ -7,14 +7,14 @@ import io.modelcontextprotocol.client.transport.customizer.McpSyncHttpClientRequ
import io.modelcontextprotocol.common.McpTransportContext; import io.modelcontextprotocol.common.McpTransportContext;
import io.modelcontextprotocol.json.McpJsonMapper; import io.modelcontextprotocol.json.McpJsonMapper;
import io.modelcontextprotocol.spec.McpClientTransport; import io.modelcontextprotocol.spec.McpClientTransport;
import work.slhaf.partner.core.action.runner.policy.RunnerExecutionPolicy; import work.slhaf.partner.core.action.runner.policy.ExecutionPolicyRegistry;
import java.net.URI; import java.net.URI;
import java.net.http.HttpRequest; import java.net.http.HttpRequest;
public class McpTransportFactory { public class McpTransportFactory {
public McpClientTransport create(McpTransportConfig config, RunnerExecutionPolicy policy) { public McpClientTransport create(McpTransportConfig config, ExecutionPolicyRegistry policy) {
return switch (config) { return switch (config) {
case McpTransportConfig.Stdio stdio -> { case McpTransportConfig.Stdio stdio -> {
ServerParameters serverParameters = ServerParameters.builder(stdio.command()) ServerParameters serverParameters = ServerParameters.builder(stdio.command())

View File

@@ -0,0 +1,20 @@
package work.slhaf.partner.core.action.runner.policy
object DirectPolicyProvider : PolicyProvider(
policyName = "direct"
) {
override fun prepare(
policy: ExecutionPolicy,
commands: List<String>
): WrappedLaunchSpec {
val (command, args) = splitCommands(commands)
return WrappedLaunchSpec(
command = command,
args = args,
workingDirectory = policy.workingDirectory,
environment = resolveEnvironment(policy)
)
}
}

View File

@@ -0,0 +1,115 @@
package work.slhaf.partner.core.action.runner.policy
import java.util.concurrent.ConcurrentHashMap
import java.util.concurrent.CopyOnWriteArraySet
object ExecutionPolicyRegistry {
private const val DEFAULT_PROVIDER = "direct"
private val policyProviders = ConcurrentHashMap<String, PolicyProvider>().apply {
put(DEFAULT_PROVIDER, DirectPolicyProvider)
}
private val listeners = CopyOnWriteArraySet<RunnerExecutionPolicyListener>()
@Volatile
private var currentPolicy = ExecutionPolicy(
provider = "direct",
mode = ExecutionPolicy.Mode.DIRECT,
net = ExecutionPolicy.Network.ENABLE,
inheritEnv = true,
env = emptyMap(),
workingDirectory = null,
readOnlyPaths = emptySet(),
writablePaths = emptySet(),
)
fun prepare(commands: List<String>): WrappedLaunchSpec {
val policy = currentPolicy
val provider = policyProviders[policy.provider]
?: policyProviders[DEFAULT_PROVIDER]
?: error("Default provider '${DEFAULT_PROVIDER}' is not registered")
return provider.prepare(policy, commands)
}
fun updatePolicy(policy: ExecutionPolicy) {
currentPolicy = policy
listeners.forEach { it.onPolicyChanged(policy) }
}
fun addListener(listener: RunnerExecutionPolicyListener) {
listeners += listener
}
fun removeListener(listener: RunnerExecutionPolicyListener) {
listeners -= listener
}
fun registerPolicyProvider(policyProvider: PolicyProvider) {
val name = policyProvider.policyName
if (policyProviders.containsKey(name)) {
return
}
policyProviders[name] = policyProvider
}
}
data class ExecutionPolicy(
val mode: Mode,
val provider: String,
val net: Network,
val inheritEnv: Boolean,
val env: Map<String, String>,
val workingDirectory: String?,
val readOnlyPaths: Set<String>,
val writablePaths: Set<String>,
) {
enum class Mode {
DIRECT,
SANDBOX
}
enum class Network {
DISABLE,
ENABLE
}
}
data class WrappedLaunchSpec(
val command: String,
val args: List<String>,
val workingDirectory: String? = null,
val environment: Map<String, String> = emptyMap()
)
abstract class PolicyProvider(
val policyName: String
) {
abstract fun prepare(
policy: ExecutionPolicy,
commands: List<String>
): WrappedLaunchSpec
protected fun resolveEnvironment(policy: ExecutionPolicy): Map<String, String> {
val result = LinkedHashMap<String, String>()
if (policy.inheritEnv) {
result.putAll(System.getenv())
}
result.putAll(policy.env)
return result
}
protected fun splitCommands(commands: List<String>): Pair<String, List<String>> {
require(commands.isNotEmpty()) { "commands must not be empty" }
return commands.first() to commands.drop(1)
}
}
interface RunnerExecutionPolicyListener {
fun onPolicyChanged(policy: ExecutionPolicy)
}

View File

@@ -1,4 +0,0 @@
package work.slhaf.partner.core.action.runner.policy;
public interface RunnerExecutionPolicy {
}