mirror of
https://github.com/bytedance/deer-flow.git
synced 2026-09-25 05:56:18 +00:00
fix(mcp): insert bare-filename rewrites literally (#5522)
_rewrite_unique_bare_filenames handed the correlated /mnt/user-data virtual path to Pattern.subn as a replacement template. That path is built from the file's relative path, and a backslash is an ordinary character in a POSIX filename, so a file written literally as "screenshots\q3.png" -- the shape a model produces by passing a Windows-style path to a stdio server on a POSIX host -- turned \q into an unknown template escape. Pattern.subn compiles the template eagerly, so re.error escaped _convert_call_tool_result and failed the whole tool call even though the server had already written the file, and the agent never saw the path. When the backslash does start a known escape (\r, \t, \b ...), the bare-filename pass substituted that byte into the returned text instead, so "screenshots\raw.png" came back as a path with a raw CR in the middle of it. Insert the correlated path through a callable replacement, matching what _rewrite_local_paths_in_text already does, so it is never parsed as a template.
This commit is contained in:
parent
d811143b52
commit
796ca28f55
@ -277,7 +277,10 @@ def _rewrite_unique_bare_filenames(
|
|||||||
# Do not rewrite inside longer paths/words. A final sentence period is
|
# Do not rewrite inside longer paths/words. A final sentence period is
|
||||||
# allowed, but ".bak" or another path segment is not.
|
# allowed, but ".bak" or another path segment is not.
|
||||||
pattern = re.compile(rf"(?<![\w./-]){re.escape(name)}(?!(?:[\w/-]|\.[\w]))")
|
pattern = re.compile(rf"(?<![\w./-]){re.escape(name)}(?!(?:[\w/-]|\.[\w]))")
|
||||||
rewritten_text, count = pattern.subn(unique[name], rewritten)
|
# A callable replacement, not a template: the virtual path is built from
|
||||||
|
# the real file's relative path, where a backslash is an ordinary
|
||||||
|
# character, so it must never be read as a regex escape.
|
||||||
|
rewritten_text, count = pattern.subn(lambda _match: unique[name], rewritten)
|
||||||
if count:
|
if count:
|
||||||
logger.debug("MCP bare filename rewrite: %s -> %s", name, unique[name])
|
logger.debug("MCP bare filename rewrite: %s -> %s", name, unique[name])
|
||||||
rewritten = rewritten_text
|
rewritten = rewritten_text
|
||||||
|
|||||||
@ -426,6 +426,48 @@ class TestRewriteLocalPathsInText:
|
|||||||
assert result == text
|
assert result == text
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.skipif(os.name == "nt", reason="a literal backslash in a filename is POSIX-only")
|
||||||
|
class TestRewriteUniqueBareFilenames:
|
||||||
|
"""The correlated virtual path must be inserted verbatim, never as a template.
|
||||||
|
|
||||||
|
The replacement is built from the real file's relative path, where a
|
||||||
|
backslash is an ordinary character, so handing it to ``re.sub`` as a
|
||||||
|
template reads it as a regex escape instead.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def test_backslash_in_replacement_is_inserted_literally(self, paths: Paths):
|
||||||
|
workspace = paths.sandbox_work_dir("t1", user_id="u1")
|
||||||
|
src = _workspace_file(paths, r"screenshots\raw.png")
|
||||||
|
text = r"Saved as screenshots\raw.png"
|
||||||
|
|
||||||
|
with _patch_paths(paths):
|
||||||
|
result = mcp_tools._rewrite_unique_bare_filenames(
|
||||||
|
text,
|
||||||
|
changed_files=[src],
|
||||||
|
thread_id="t1",
|
||||||
|
user_id="u1",
|
||||||
|
source_base_dir=workspace,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == f"Saved as {VIRTUAL_PATH_PREFIX}/workspace/screenshots\\raw.png"
|
||||||
|
|
||||||
|
def test_unknown_regex_escape_in_replacement_does_not_raise(self, paths: Paths):
|
||||||
|
workspace = paths.sandbox_work_dir("t1", user_id="u1")
|
||||||
|
src = _workspace_file(paths, r"screenshots\q3.png")
|
||||||
|
text = r"Saved as screenshots\q3.png"
|
||||||
|
|
||||||
|
with _patch_paths(paths):
|
||||||
|
result = mcp_tools._rewrite_unique_bare_filenames(
|
||||||
|
text,
|
||||||
|
changed_files=[src],
|
||||||
|
thread_id="t1",
|
||||||
|
user_id="u1",
|
||||||
|
source_base_dir=workspace,
|
||||||
|
)
|
||||||
|
|
||||||
|
assert result == f"Saved as {VIRTUAL_PATH_PREFIX}/workspace/screenshots\\q3.png"
|
||||||
|
|
||||||
|
|
||||||
class TestWorkspaceSnapshots:
|
class TestWorkspaceSnapshots:
|
||||||
def test_changed_workspace_files_detects_created_and_modified_files(self, paths: Paths):
|
def test_changed_workspace_files_detects_created_and_modified_files(self, paths: Paths):
|
||||||
import time
|
import time
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user