refactor: retire sandbox dual-write; file tools write in-place into workspace
This commit is contained in:
@@ -44,7 +44,6 @@ import com.correx.core.toolintent.rules.ExecInterpreterRule
|
|||||||
import com.correx.core.toolintent.rules.NetworkHostRule
|
import com.correx.core.toolintent.rules.NetworkHostRule
|
||||||
import com.correx.core.toolintent.rules.PathContainmentRule
|
import com.correx.core.toolintent.rules.PathContainmentRule
|
||||||
import com.correx.infrastructure.InfrastructureModule
|
import com.correx.infrastructure.InfrastructureModule
|
||||||
import com.correx.infrastructure.artifactscas.DefaultMaterializingArtifactWriter
|
|
||||||
import com.correx.infrastructure.inference.DefaultProviderRegistry
|
import com.correx.infrastructure.inference.DefaultProviderRegistry
|
||||||
import com.correx.infrastructure.inference.FirstAvailableRoutingStrategy
|
import com.correx.infrastructure.inference.FirstAvailableRoutingStrategy
|
||||||
import com.correx.infrastructure.inference.llama.cpp.LlamaCppInferenceProvider
|
import com.correx.infrastructure.inference.llama.cpp.LlamaCppInferenceProvider
|
||||||
@@ -92,10 +91,13 @@ fun main() {
|
|||||||
?: toolsConfig.workingDir.takeIf { it.isNotEmpty() }?.let { Path.of(it) }
|
?: toolsConfig.workingDir.takeIf { it.isNotEmpty() }?.let { Path.of(it) }
|
||||||
?: Path.of("").toAbsolutePath()
|
?: Path.of("").toAbsolutePath()
|
||||||
val shellAllowedExecutables = toolsConfig.shellAllowedExecutables.toSet()
|
val shellAllowedExecutables = toolsConfig.shellAllowedExecutables.toSet()
|
||||||
|
val workspaceRoot = System.getenv("CORREX_WORKSPACE_ROOT")
|
||||||
|
?.let { Path.of(it) }
|
||||||
|
?: toolsConfig.workspaceRoot.takeIf { it.isNotEmpty() }?.let { Path.of(it) }
|
||||||
|
?: workingDir
|
||||||
val toolRegistry = InfrastructureModule.createToolRegistry(
|
val toolRegistry = InfrastructureModule.createToolRegistry(
|
||||||
buildToolConfig(
|
buildToolConfig(
|
||||||
artifactStore,
|
workspaceRoot,
|
||||||
sandboxRoot,
|
|
||||||
workingDir,
|
workingDir,
|
||||||
shellAllowedExecutables,
|
shellAllowedExecutables,
|
||||||
toolsConfig,
|
toolsConfig,
|
||||||
@@ -110,10 +112,6 @@ fun main() {
|
|||||||
|
|
||||||
logToolInfo(sandboxRoot, workingDir, shellAllowedExecutables, toolRegistry)
|
logToolInfo(sandboxRoot, workingDir, shellAllowedExecutables, toolRegistry)
|
||||||
|
|
||||||
val workspaceRoot = System.getenv("CORREX_WORKSPACE_ROOT")
|
|
||||||
?.let { Path.of(it) }
|
|
||||||
?: toolsConfig.workspaceRoot.takeIf { it.isNotEmpty() }?.let { Path.of(it) }
|
|
||||||
?: workingDir
|
|
||||||
val privilegedLocations = toolsConfig.privilegedLocations.map { Path.of(it) }
|
val privilegedLocations = toolsConfig.privilegedLocations.map { Path.of(it) }
|
||||||
val workspacePolicy = WorkspacePolicy(workspaceRoot, privilegedLocations)
|
val workspacePolicy = WorkspacePolicy(workspaceRoot, privilegedLocations)
|
||||||
val toolCallAssessor = ToolCallAssessor(
|
val toolCallAssessor = ToolCallAssessor(
|
||||||
@@ -297,36 +295,39 @@ private fun buildRepositories(
|
|||||||
approvalRepository = InfrastructureModule.createApprovalRepository(eventStore),
|
approvalRepository = InfrastructureModule.createApprovalRepository(eventStore),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// File tools write IN PLACE into the workspace (cwd / allow-listed dirs). The old
|
||||||
|
// sandboxRoot dual-write is retired; reversibility is event-sourced (FileWrittenEvent +
|
||||||
|
// CAS pre/post images, captured by SandboxedToolExecutor). sandboxRoot survives only as
|
||||||
|
// the executor's scratch workDir, never as a write destination.
|
||||||
private fun buildToolConfig(
|
private fun buildToolConfig(
|
||||||
artifactStore: ArtifactStore,
|
workspaceRoot: Path,
|
||||||
sandboxRoot: Path,
|
|
||||||
workingDir: Path,
|
workingDir: Path,
|
||||||
shellAllowedExecutables: Set<String>,
|
shellAllowedExecutables: Set<String>,
|
||||||
toolsConfig: com.correx.core.config.ToolsConfig,
|
toolsConfig: com.correx.core.config.ToolsConfig,
|
||||||
): ToolConfig = ToolConfig(
|
): ToolConfig {
|
||||||
shell = ShellConfig(
|
val allowed = setOf(workspaceRoot, workingDir)
|
||||||
enabled = toolsConfig.shellEnabled,
|
return ToolConfig(
|
||||||
allowedExecutables = shellAllowedExecutables,
|
shell = ShellConfig(
|
||||||
workingDir = workingDir,
|
enabled = toolsConfig.shellEnabled,
|
||||||
),
|
allowedExecutables = shellAllowedExecutables,
|
||||||
fileRead = FileReadConfig(
|
workingDir = workingDir,
|
||||||
enabled = toolsConfig.fileReadEnabled,
|
),
|
||||||
allowedPaths = setOf(sandboxRoot, workingDir),
|
fileRead = FileReadConfig(
|
||||||
),
|
enabled = toolsConfig.fileReadEnabled,
|
||||||
fileWrite = FileWriteConfig(
|
allowedPaths = allowed,
|
||||||
allowedPaths = setOf(sandboxRoot, workingDir),
|
),
|
||||||
enabled = toolsConfig.fileWriteEnabled,
|
fileWrite = FileWriteConfig(
|
||||||
artifactStore = artifactStore,
|
allowedPaths = allowed,
|
||||||
materializingWriter = DefaultMaterializingArtifactWriter(),
|
enabled = toolsConfig.fileWriteEnabled,
|
||||||
sandboxRoot = sandboxRoot,
|
workingDir = workingDir,
|
||||||
workingDir = workingDir,
|
),
|
||||||
),
|
fileEdit = FileEditConfig(
|
||||||
fileEdit = FileEditConfig(
|
enabled = toolsConfig.fileEditEnabled,
|
||||||
enabled = toolsConfig.fileEditEnabled,
|
allowedPaths = allowed,
|
||||||
allowedPaths = setOf(sandboxRoot, workingDir),
|
workingDir = workingDir,
|
||||||
workingDir = workingDir,
|
),
|
||||||
),
|
)
|
||||||
)
|
}
|
||||||
|
|
||||||
private fun DefaultProviderRegistry.asServerRegistry(): ProviderRegistry = object : ProviderRegistry {
|
private fun DefaultProviderRegistry.asServerRegistry(): ProviderRegistry = object : ProviderRegistry {
|
||||||
override fun listAll() = this@asServerRegistry.listAll()
|
override fun listAll() = this@asServerRegistry.listAll()
|
||||||
|
|||||||
-36
@@ -1,11 +1,6 @@
|
|||||||
package com.correx.infrastructure.tools.filesystem
|
package com.correx.infrastructure.tools.filesystem
|
||||||
|
|
||||||
import com.correx.core.approvals.Tier
|
import com.correx.core.approvals.Tier
|
||||||
import com.correx.core.artifacts.MaterializationResult
|
|
||||||
import com.correx.core.artifacts.MaterializingArtifactWriter
|
|
||||||
import com.correx.core.artifacts.kind.FileWrittenArtifact
|
|
||||||
import com.correx.core.artifacts.kind.FileWrittenPayload
|
|
||||||
import com.correx.core.artifactstore.ArtifactStore
|
|
||||||
import com.correx.core.events.events.ToolRequest
|
import com.correx.core.events.events.ToolRequest
|
||||||
import com.correx.core.events.types.ToolInvocationId
|
import com.correx.core.events.types.ToolInvocationId
|
||||||
import com.correx.core.tools.contract.FileAffectingTool
|
import com.correx.core.tools.contract.FileAffectingTool
|
||||||
@@ -17,15 +12,12 @@ import com.correx.core.tools.contract.ValidationResult
|
|||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.json.Json
|
|
||||||
import kotlinx.serialization.json.JsonObject
|
import kotlinx.serialization.json.JsonObject
|
||||||
import kotlinx.serialization.json.JsonPrimitive
|
import kotlinx.serialization.json.JsonPrimitive
|
||||||
import kotlinx.serialization.json.buildJsonArray
|
import kotlinx.serialization.json.buildJsonArray
|
||||||
import kotlinx.serialization.json.buildJsonObject
|
import kotlinx.serialization.json.buildJsonObject
|
||||||
import kotlinx.serialization.json.put
|
import kotlinx.serialization.json.put
|
||||||
import kotlinx.serialization.json.putJsonObject
|
import kotlinx.serialization.json.putJsonObject
|
||||||
import org.slf4j.Logger
|
|
||||||
import org.slf4j.LoggerFactory
|
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.nio.file.Files
|
import java.nio.file.Files
|
||||||
import java.nio.file.InvalidPathException
|
import java.nio.file.InvalidPathException
|
||||||
@@ -34,16 +26,9 @@ import java.nio.file.Paths
|
|||||||
|
|
||||||
class FileWriteTool(
|
class FileWriteTool(
|
||||||
allowedPaths: Set<Path> = emptySet(),
|
allowedPaths: Set<Path> = emptySet(),
|
||||||
private val artifactStore: ArtifactStore? = null,
|
|
||||||
private val materializingWriter: MaterializingArtifactWriter? = null,
|
|
||||||
private val sandboxRoot: Path? = null,
|
|
||||||
private val workingDir: Path? = null,
|
private val workingDir: Path? = null,
|
||||||
) : Tool, FileAffectingTool, ToolExecutor {
|
) : Tool, FileAffectingTool, ToolExecutor {
|
||||||
|
|
||||||
private companion object {
|
|
||||||
val log: Logger = LoggerFactory.getLogger(FileWriteTool::class.java)
|
|
||||||
}
|
|
||||||
|
|
||||||
private val normalizedAllowedPaths: Set<Path> = allowedPaths.map { it.normalize().toAbsolutePath() }.toSet()
|
private val normalizedAllowedPaths: Set<Path> = allowedPaths.map { it.normalize().toAbsolutePath() }.toSet()
|
||||||
|
|
||||||
override val name: String = "file_write"
|
override val name: String = "file_write"
|
||||||
@@ -174,7 +159,6 @@ class FileWriteTool(
|
|||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
AtomicFileWriter.write(path, content.toByteArray(Charsets.UTF_8))
|
AtomicFileWriter.write(path, content.toByteArray(Charsets.UTF_8))
|
||||||
}
|
}
|
||||||
storeArtifact(pathString, content, request.parameters["mode"] as? String ?: "0644")
|
|
||||||
ToolResult.Success(
|
ToolResult.Success(
|
||||||
invocationId = request.invocationId,
|
invocationId = request.invocationId,
|
||||||
output = "File written successfully to $pathString",
|
output = "File written successfully to $pathString",
|
||||||
@@ -206,26 +190,6 @@ class FileWriteTool(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Suppress("ReturnCount")
|
|
||||||
private suspend fun storeArtifact(pathString: String, content: String, mode: String) {
|
|
||||||
val writer = materializingWriter ?: return
|
|
||||||
val store = artifactStore ?: return
|
|
||||||
val root = sandboxRoot ?: return
|
|
||||||
val payload = FileWrittenPayload(path = pathString, content = content, mode = mode)
|
|
||||||
when (val result = writer.materialize(payload, root)) {
|
|
||||||
is MaterializationResult.Success -> {
|
|
||||||
val canonicalBytes = Json.encodeToString(
|
|
||||||
FileWrittenArtifact.serializer(),
|
|
||||||
result.artifact,
|
|
||||||
).toByteArray(Charsets.UTF_8)
|
|
||||||
store.put(canonicalBytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
is MaterializationResult.Failure ->
|
|
||||||
log.warn("[FileWriteTool] artifact materialization failed for {}: {}", pathString, result.reason)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleExecutionException(
|
private fun handleExecutionException(
|
||||||
e: Throwable,
|
e: Throwable,
|
||||||
invocationId: ToolInvocationId,
|
invocationId: ToolInvocationId,
|
||||||
|
|||||||
@@ -1,7 +1,5 @@
|
|||||||
package com.correx.infrastructure.tools
|
package com.correx.infrastructure.tools
|
||||||
|
|
||||||
import com.correx.core.artifacts.MaterializingArtifactWriter
|
|
||||||
import com.correx.core.artifactstore.ArtifactStore
|
|
||||||
import com.correx.core.tools.contract.Tool
|
import com.correx.core.tools.contract.Tool
|
||||||
import com.correx.infrastructure.tools.filesystem.FileEditTool
|
import com.correx.infrastructure.tools.filesystem.FileEditTool
|
||||||
import com.correx.infrastructure.tools.filesystem.FileReadTool
|
import com.correx.infrastructure.tools.filesystem.FileReadTool
|
||||||
@@ -20,9 +18,6 @@ data class FileReadConfig(val enabled: Boolean = false, val allowedPaths: Set<Pa
|
|||||||
data class FileWriteConfig(
|
data class FileWriteConfig(
|
||||||
val enabled: Boolean = false,
|
val enabled: Boolean = false,
|
||||||
val allowedPaths: Set<Path> = emptySet(),
|
val allowedPaths: Set<Path> = emptySet(),
|
||||||
val artifactStore: ArtifactStore? = null,
|
|
||||||
val materializingWriter: MaterializingArtifactWriter? = null,
|
|
||||||
val sandboxRoot: Path? = null,
|
|
||||||
val workingDir: Path? = null,
|
val workingDir: Path? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -61,9 +56,6 @@ fun ToolConfig.buildTools(): List<Tool> = buildList {
|
|||||||
add(
|
add(
|
||||||
FileWriteTool(
|
FileWriteTool(
|
||||||
allowedPaths = fileWrite.allowedPaths,
|
allowedPaths = fileWrite.allowedPaths,
|
||||||
artifactStore = fileWrite.artifactStore,
|
|
||||||
materializingWriter = fileWrite.materializingWriter,
|
|
||||||
sandboxRoot = fileWrite.sandboxRoot,
|
|
||||||
workingDir = fileWrite.workingDir,
|
workingDir = fileWrite.workingDir,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user