fix: emit the resolved file path in terminal file events (#30282)

When a model calls display_file, write_file or replace_file_content with a relative path, Open Terminal resolves it against the session working directory and returns the absolute path, but the event sent to the browser carried the raw argument instead. The file panel matches that string against the file browser root, a relative path never matches, so the preview never opens, the panel jumps to the root and the session working directory is rewritten to the root. With the root turned off (OPEN_TERMINAL_FILE_BROWSER_ROOT=filesystem) there is nothing to clamp to and the relative string is sent to the terminal as the new working directory, moving it silently. The tool call itself succeeds either way, so the failure only shows up as a panel that will not open the file the model just wrote.

Both events now carry the path from the tool result and fall back to the argument when the result cannot be read, which is what build_terminal_file_tool_result already does for the chat file attachment. The same one-line rule is applied to the direct tool server path in the frontend, where the browser runs the tool itself and the write_file branch beside it was already correct.

Checked against a live Open Terminal: relative arguments now emit the absolute path, absolute ones are unchanged, non-existent files and inline displays still emit nothing, unreadable or error results still fall back to the argument, and run_command is untouched.

Related to #30051
This commit is contained in:
Classic298 2026-09-21 06:31:14 +02:00 • committed by GitHub
parent 17e7e8f5e9
commit 8e4cc946ce
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 15 additions and 10 deletions

View file

@ -1266,6 +1266,14 @@ async def process_tool_result(
return tool_result, tool_result_files, tool_result_embeds
def parse_terminal_tool_result(tool_result: Any) -> dict:
try:
result = JSONCodec.loads(tool_result)
except (JSONCodec.JSONDecodeError, TypeError):
return {}
return result if isinstance(result, dict) else {}
async def terminal_event_handler(
tool_function_name: str,
tool_function_params: dict,
@ -1284,17 +1292,13 @@ async def terminal_event_handler(
if tool_function_name == 'display_file':
if tool_function_params.get('inline') is True:
return
path = tool_function_params.get('path', '')
result = parse_terminal_tool_result(tool_result)
# Open Terminal resolves the argument against the session cwd, so prefer its resolved path
path = result.get('path') or tool_function_params.get('path', '')
if not path:
return
# Only emit if the file actually exists
parsed = tool_result
if isinstance(parsed, str):
try:
parsed = JSONCodec.loads(parsed)
except (JSONCodec.JSONDecodeError, TypeError):
pass
if isinstance(parsed, dict) and parsed.get('exists') is False:
if result.get('exists') is False:
return
page = tool_function_params.get('page')
@ -1308,7 +1312,8 @@ async def terminal_event_handler(
}
)
elif tool_function_name in ('write_file', 'replace_file_content'):
path = tool_function_params.get('path', '')
result = parse_terminal_tool_result(tool_result)
path = result.get('path') or tool_function_params.get('path', '')
if not path:
return
await event_emitter(

View file

@ -537,7 +537,7 @@
if (data?.name === 'display_file' && params?.path && !inlineDisplayFile) {
if (result?.exists !== false) {
displayFileHandler(
params.path,
result?.path ?? params.path,
{ showControls, showFileNavPath },
{ page: params?.page }
);