Read file refactor + Fix include path errors (#633)
* refactor: read_file to read_prompt_file * fix: ignore absolute paths in includes
The Master committed
Jul 31, 2025 at 16:21 UTC
3eb6d8ff61d4c09eb4a706fe6d8d70479e83c37a
7 files changed
+44
-9
agent.py
+1
-1
@@ -506,7 +506,7 @@ class Agent:
506
): # if agent has custom folder, use it and use default as backup
507
prompt_dir = files.get_abs_path("agents", self.config.profile, "prompts")
508
backup_dir.append(files.get_abs_path("prompts"))
509
- prompt = files.read_file(
509
+ prompt = files.read_prompt_file(
510
files.get_abs_path(prompt_dir, file), _backup_dirs=backup_dir, **kwargs
511
)
512
prompt = files.remove_code_fences(prompt)
prompts/agent.system.tool.call_sub.py
+1
-1
@@ -13,7 +13,7 @@ class CallSubordinate(VariablesPlugin):
13
agent_subdirs = files.get_subdirectories("agents", exclude=["_example"])
14
for agent_subdir in agent_subdirs:
15
try:
16
- context = files.read_file(
16
+ context = files.read_prompt_file(
17
files.get_abs_path("agents", agent_subdir, "_context.md")
18
)
19
profiles.append({"name": agent_subdir, "context": context})
prompts/agent.system.tools.py
+1
-1
@@ -22,7 +22,7 @@ class CallSubordinate(VariablesPlugin):
22
tools = []
23
for prompt_file in prompt_files:
24
try:
25
- tool = files.read_file(prompt_file)
25
+ tool = files.read_prompt_file(prompt_file)
26
tools.append(tool)
27
except Exception as e:
28
PrintStyle().error(f"Error loading tool '{prompt_file}': {e}")
python/extensions/system_prompt/_20_behaviour_prompt.py
+1
-1
@@ -16,7 +16,7 @@ def get_custom_rules_file(agent: Agent):
16
def read_rules(agent: Agent):
17
rules_file = get_custom_rules_file(agent)
18
if files.exists(rules_file):
19
- rules = files.read_file(rules_file)
19
+ rules = files.read_prompt_file(rules_file)
20
return agent.read_prompt("agent.system.behaviour.md", rules=rules)
21
else:
22
rules = agent.read_prompt("agent.system.behaviour_default.md")
python/helpers/files.py
+36
-3
@@ -73,7 +73,17 @@ from python.helpers.strings import sanitize_string
73
74
75
def parse_file(_relative_path, _backup_dirs=None, _encoding="utf-8", **kwargs):
76
- content = read_file(_relative_path, _backup_dirs, _encoding)
76
+ if _backup_dirs is None:
77
+ _backup_dirs = []
78
+
79
+ # Try to get the absolute path for the file from the original directory or backup directories
80
+ absolute_path = find_file_in_dirs(_relative_path, _backup_dirs)
81
+
82
+ # Read the file content
83
+ with open(absolute_path, "r", encoding=_encoding) as f:
84
+ # content = remove_code_fences(f.read())
85
+ content = f.read()
86
+
87
is_json = is_full_json_template(content)
88
content = remove_code_fences(content)
89
variables = load_plugin_variables(_relative_path, _backup_dirs) or {} # type: ignore
@@ -85,10 +95,15 @@ def parse_file(_relative_path, _backup_dirs=None, _encoding="utf-8", **kwargs):
95
return obj
96
else:
97
content = replace_placeholders_text(content, **variables)
98
+ # Process include statements
99
+ content = process_includes(
100
+ # here we use kwargs, the plugin variables are not inherited
101
+ content, os.path.dirname(_relative_path), _backup_dirs, **kwargs
102
+ )
103
return content
104
105
91
-def read_file(_relative_path, _backup_dirs=None, _encoding="utf-8", **kwargs):
106
+def read_prompt_file(_relative_path, _backup_dirs=None, _encoding="utf-8", **kwargs):
107
if _backup_dirs is None:
108
_backup_dirs = []
109
@@ -115,6 +130,18 @@ def read_file(_relative_path, _backup_dirs=None, _encoding="utf-8", **kwargs):
130
return content
131
132
133
+def read_file(_relative_path, _backup_dirs=None, _encoding="utf-8"):
134
+ if _backup_dirs is None:
135
+ _backup_dirs = []
136
+
137
+ # Try to get the absolute path for the file from the original directory or backup directories
138
+ absolute_path = find_file_in_dirs(_relative_path, _backup_dirs)
139
+
140
+ # Read the file content
141
+ with open(absolute_path, "r", encoding=_encoding) as f:
142
+ return f.read()
143
+
144
+
145
def read_file_bin(_relative_path, _backup_dirs=None):
146
# init backup dirs
147
if _backup_dirs is None:
@@ -194,13 +221,16 @@ def process_includes(_content, _base_path, _backup_dirs, **kwargs):
221
222
def replace_include(match):
223
include_path = match.group(1)
224
+ # if the path is absolute, do not process it
225
+ if os.path.isabs(include_path):
226
+ return match.group(0)
227
# First attempt to resolve the include relative to the base path
228
full_include_path = find_file_in_dirs(
229
os.path.join(_base_path, include_path), _backup_dirs
230
)
231
232
# Recursively read the included file content, keeping the original base path
203
- included_content = read_file(full_include_path, _backup_dirs, **kwargs)
233
+ included_content = read_prompt_file(full_include_path, _backup_dirs, **kwargs)
234
return included_content
235
236
# Replace all includes with the file content
@@ -219,6 +249,9 @@ def find_file_in_dirs(file_path, backup_dirs):
249
250
# Loop through the backup directories
251
for backup_dir in backup_dirs:
252
+ # backup path should be os.path.join(backup_dir, file_path) but that can lead to problems
253
+ # with absolute paths in file_path. So we use basename.
254
+ # This means that we don't support include paths like "subdir/something.md" from backups
255
backup_path = os.path.join(backup_dir, os.path.basename(file_path))
256
if os.path.isfile(get_abs_path(backup_path)):
257
return get_abs_path(backup_path)
python/tools/behaviour_adjustment.py
+1
-1
@@ -58,7 +58,7 @@ def get_custom_rules_file(agent: Agent):
58
def read_rules(agent: Agent):
59
rules_file = get_custom_rules_file(agent)
60
if files.exists(rules_file):
61
- rules = files.read_file(rules_file)
61
+ rules = files.read_prompt_file(rules_file)
62
return agent.read_prompt("agent.system.behaviour.md", rules=rules)
63
else:
64
rules = agent.read_prompt("agent.system.behaviour_default.md")
run_ui.py
+3
-1
@@ -157,8 +157,10 @@ async def serve_index():
157
"version": "unknown",
158
"commit_time": "unknown",
159
}
160
- return files.read_file(
160
+ return files.read_prompt_file(
161
"./webui/index.html",
162
+ _backup_dirs=[],
163
+ _encoding="utf-8",
164
version_no=gitinfo["version"],
165
version_time=gitinfo["commit_time"],
166
)