fix(approval): resolve preview paths against workspace root, not server CWD (#57)
computeToolPreview/readFileIfExists resolved relative file_write/file_edit paths against the daemon CWD, so the diff shown to the operator for approval read the wrong file (or nothing) when server CWD != workspace_root. Thread the bound workspaceRoot (effectives.policy.workspaceRoot) through and resolve relative paths against it, same as the tools do.
This commit is contained in:
+22
-8
@@ -1275,7 +1275,9 @@ abstract class SessionOrchestrator(
|
||||
mode = approvalMode,
|
||||
)
|
||||
val requestId = ApprovalRequestId(UUID.randomUUID().toString())
|
||||
val toolPreview = computeToolPreview(toolCall.function.name, parameters)
|
||||
val toolPreview = computeToolPreview(
|
||||
toolCall.function.name, parameters, effectives.policy?.workspaceRoot,
|
||||
)
|
||||
val domainRequest = DomainApprovalRequest(
|
||||
id = requestId,
|
||||
tier = tier,
|
||||
@@ -3382,10 +3384,14 @@ abstract class SessionOrchestrator(
|
||||
* This function performs blocking I/O (file reads) and must be called from a suspend context
|
||||
* that will dispatch it on [Dispatchers.IO].
|
||||
*/
|
||||
private suspend fun computeToolPreview(toolName: String, parameters: Map<String, Any>): String? {
|
||||
private suspend fun computeToolPreview(
|
||||
toolName: String,
|
||||
parameters: Map<String, Any>,
|
||||
workspaceRoot: java.nio.file.Path?,
|
||||
): String? {
|
||||
if (toolName == "shell") return shellCommandPreview(parameters)
|
||||
if (toolName == "task_decompose") return renderDecomposePreview(parameters)
|
||||
if (toolName == "file_edit") return computeFileEditPreview(parameters)
|
||||
if (toolName == "file_edit") return computeFileEditPreview(parameters, workspaceRoot)
|
||||
if (toolName != "file_write") return null
|
||||
val path = parameters["path"] as? String ?: return null
|
||||
// file_write no longer carries an `operation` param (delete was split into file_delete), so the
|
||||
@@ -3393,13 +3399,18 @@ private suspend fun computeToolPreview(toolName: String, parameters: Map<String,
|
||||
// silently bailed to the raw-JSON args fallback for every file_write.
|
||||
val proposedContent = parameters["content"] as? String ?: return null
|
||||
|
||||
val existingContent = readFileIfExists(path)
|
||||
val existingContent = readFileIfExists(path, workspaceRoot)
|
||||
return buildDiffString(path, existingContent, proposedContent)
|
||||
}
|
||||
|
||||
private suspend fun readFileIfExists(path: String): String? = withContext(Dispatchers.IO) {
|
||||
private suspend fun readFileIfExists(path: String, workspaceRoot: java.nio.file.Path?): String? =
|
||||
withContext(Dispatchers.IO) {
|
||||
runCatching {
|
||||
val filePath = java.nio.file.Paths.get(path)
|
||||
// Resolve relative paths against the session's workspace root, same as the tools do —
|
||||
// resolving against the daemon CWD showed the operator the wrong file (or nothing) when
|
||||
// server CWD ≠ workspace_root.
|
||||
val raw = java.nio.file.Paths.get(path)
|
||||
val filePath = if (raw.isAbsolute || workspaceRoot == null) raw else workspaceRoot.resolve(raw)
|
||||
if (java.nio.file.Files.exists(filePath)) {
|
||||
java.nio.file.Files.readString(filePath)
|
||||
} else null
|
||||
@@ -3412,10 +3423,13 @@ private suspend fun readFileIfExists(path: String): String? = withContext(Dispat
|
||||
* to the raw-JSON-args fallback — simulating `patch -p1` application here isn't worth the risk of
|
||||
* the preview silently disagreeing with what the tool actually does.
|
||||
*/
|
||||
private suspend fun computeFileEditPreview(parameters: Map<String, Any>): String? {
|
||||
private suspend fun computeFileEditPreview(
|
||||
parameters: Map<String, Any>,
|
||||
workspaceRoot: java.nio.file.Path?,
|
||||
): String? {
|
||||
val path = parameters["path"] as? String ?: return null
|
||||
val operation = parameters["operation"] as? String ?: return null
|
||||
val existingContent = readFileIfExists(path) ?: return null
|
||||
val existingContent = readFileIfExists(path, workspaceRoot) ?: return null
|
||||
|
||||
val proposedContent = when (operation) {
|
||||
"append" -> existingContent + (parameters["content"] as? String ?: return null)
|
||||
|
||||
Reference in New Issue
Block a user