1- """Write tool -- create or overwrite files in the execution environment."""
1+ """Write tool -- create or overwrite files in the execution environment.
2+
3+ When path_restriction is set, writes within the restriction proceed
4+ without approval. Writes outside the restriction suspend for user approval.
5+ Set approval to 'always' to require approval for every write, or 'none'
6+ to skip approval entirely (overrides path restriction).
7+ """
28
39from __future__ import annotations
410
11+ import os
512from collections .abc import Awaitable , Callable
613from typing import Any , Literal
714
1017from ...core .context import WorkflowContext
1118from ...tools .tool import Tool
1219from ..environment import ExecutionEnvironment
20+ from .path_approval import PathRestrictionConfig , is_path_allowed , require_path_approval
1321
1422
1523class WriteInput (BaseModel ):
@@ -19,22 +27,43 @@ class WriteInput(BaseModel):
1927 content : str = Field (description = "Content to write to the file" )
2028
2129
30+ class WriteToolConfig (BaseModel ):
31+ """Configuration for the write tool."""
32+
33+ approval : Literal ["always" , "none" ] | None = None
34+ path_config : PathRestrictionConfig | None = None
35+
36+
2237def create_write_tool (
2338 get_env : Callable [[], Awaitable [ExecutionEnvironment ]],
24- approval : Literal [ "always" , "none" ] | None = None ,
39+ config : WriteToolConfig | None = None ,
2540) -> Tool :
2641 """Create the write tool for writing file contents.
2742
2843 Args:
2944 get_env: Async callable that returns the shared ExecutionEnvironment.
30- approval : Optional approval mode ('always' requires user approval before write) .
45+ config : Optional configuration with approval mode and/or path restriction .
3146
3247 Returns:
3348 A Tool instance for write.
3449 """
3550
3651 async def handler (ctx : WorkflowContext , input : WriteInput ) -> dict [str , Any ]:
3752 env = await get_env ()
53+
54+ # Path-restricted approval: approve if outside cwd, skip if inside
55+ if (
56+ not (config and config .approval )
57+ and config
58+ and config .path_config
59+ and config .path_config .path_restriction
60+ ):
61+ resolved = os .path .abspath (os .path .join (env .get_cwd (), input .path ))
62+ if not is_path_allowed (resolved , config .path_config .path_restriction ):
63+ await require_path_approval (
64+ ctx , "write" , resolved , config .path_config .path_restriction
65+ )
66+
3867 await env .write_file (input .path , input .content )
3968 return {"success" : True , "path" : input .path }
4069
@@ -52,7 +81,7 @@ async def wrapped_func(ctx: WorkflowContext, payload: dict[str, Any] | None):
5281 ),
5382 parameters = WriteInput .model_json_schema (),
5483 func = wrapped_func ,
55- approval = approval ,
84+ approval = "always" if config and config . approval == "always" else None ,
5685 )
5786 tool ._input_schema_class = WriteInput
5887 return tool
0 commit comments