From cf856742b10f901091cad9625b47930ae9c9f6d8 Mon Sep 17 00:00:00 2001 From: kami Date: Sun, 12 Jul 2026 12:37:04 +0400 Subject: [PATCH] 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. --- .../orchestration/SessionOrchestrator.kt | 42 ++++++++++++------- 1 file changed, 28 insertions(+), 14 deletions(-) diff --git a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt index b5d516a2..e78ead73 100644 --- a/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt +++ b/core/kernel/src/main/kotlin/com/correx/core/kernel/orchestration/SessionOrchestrator.kt @@ -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? { +private suspend fun computeToolPreview( + toolName: String, + parameters: Map, + 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,18 +3399,23 @@ private suspend fun computeToolPreview(toolName: String, parameters: Map): String? { +private suspend fun computeFileEditPreview( + parameters: Map, + 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)