fix(tools): atomic file_edit writes + bound the patch subprocess
This commit is contained in:
+17
-4
@@ -24,7 +24,6 @@ import java.nio.file.Files
|
||||
import java.nio.file.InvalidPathException
|
||||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import java.nio.file.StandardOpenOption
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
class FileEditTool(
|
||||
@@ -242,7 +241,9 @@ class FileEditTool(
|
||||
|
||||
private fun append(path: Path, request: ToolRequest): ToolResult {
|
||||
val content = request.parameters["content"] as String
|
||||
Files.writeString(path, content, StandardOpenOption.APPEND)
|
||||
// Read-modify-atomic-swap instead of an in-place APPEND: a crash mid-append leaves the file
|
||||
// whole (old content), never truncated. Same crash-safety FileWriteTool gets.
|
||||
AtomicFileWriter.write(path, (Files.readString(path) + content).toByteArray(Charsets.UTF_8))
|
||||
return ToolResult.Success(
|
||||
invocationId = request.invocationId,
|
||||
output = "Content appended to ${path.toAbsolutePath()}",
|
||||
@@ -258,7 +259,7 @@ class FileEditTool(
|
||||
return when (occurrences) {
|
||||
1 -> {
|
||||
val newContent = currentContent.replace(target, replacement)
|
||||
Files.writeString(path, newContent)
|
||||
AtomicFileWriter.write(path, newContent.toByteArray(Charsets.UTF_8))
|
||||
ToolResult.Success(
|
||||
invocationId = request.invocationId,
|
||||
output = "Target replaced in $pathString",
|
||||
@@ -338,7 +339,18 @@ class FileEditTool(
|
||||
return try {
|
||||
process.outputStream.use { it.write(patchContent.toByteArray()) }
|
||||
val output = process.inputStream.bufferedReader().use { it.readText() }
|
||||
val exitCode = process.waitFor()
|
||||
if (!process.waitFor(PATCH_TIMEOUT_SECONDS, java.util.concurrent.TimeUnit.SECONDS)) {
|
||||
// A hung `patch` (e.g. prompting for a reject-file name it never gets) would block the
|
||||
// stage forever. Kill it and let the model fall back to replace/file_write.
|
||||
process.destroyForcibly()
|
||||
return ToolResult.Failure(
|
||||
invocationId = request.invocationId,
|
||||
reason = "Patch timed out after ${PATCH_TIMEOUT_SECONDS}s. " +
|
||||
"Consider using operation 'replace' (exact string swap) or file_write instead.",
|
||||
recoverable = true,
|
||||
)
|
||||
}
|
||||
val exitCode = process.exitValue()
|
||||
|
||||
if (exitCode == 0) {
|
||||
ToolResult.Success(
|
||||
@@ -389,5 +401,6 @@ class FileEditTool(
|
||||
const val MIN_ANCHOR = 3
|
||||
const val MAX_CMP = 400
|
||||
const val MIN_SIMILARITY = 0.5
|
||||
const val PATCH_TIMEOUT_SECONDS = 30L
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user