Edit an existing documentation file (str_replace, insert, or undo).
(
arguments: Dict[str, Any],
store: SessionStore,
)
| 111 | |
| 112 | |
| 113 | async def handle_edit_doc_file( |
| 114 | arguments: Dict[str, Any], |
| 115 | store: SessionStore, |
| 116 | ) -> str: |
| 117 | """Edit an existing documentation file (str_replace, insert, or undo).""" |
| 118 | session_id = arguments["session_id"] |
| 119 | session = store.get(session_id) |
| 120 | if session is None: |
| 121 | return json.dumps({"error": f"Session {session_id} not found or expired."}) |
| 122 | |
| 123 | filename = arguments["filename"] |
| 124 | doc_path = _safe_doc_path(session, filename) |
| 125 | if doc_path is None: |
| 126 | return json.dumps({"error": "Filename escapes output directory."}) |
| 127 | |
| 128 | command = arguments["command"] |
| 129 | |
| 130 | if command == "undo": |
| 131 | # Undo via registry history |
| 132 | history = session.registry.get("file_history", {}) |
| 133 | if isinstance(history, str): |
| 134 | history = json.loads(history) |
| 135 | path_history = history.get(str(doc_path), []) |
| 136 | if not path_history: |
| 137 | return json.dumps({"error": f"No edit history found for {filename}."}) |
| 138 | old_content = path_history.pop() |
| 139 | history[str(doc_path)] = path_history |
| 140 | session.registry["file_history"] = history |
| 141 | await asyncio.to_thread(doc_path.write_text, old_content, "utf-8") |
| 142 | |
| 143 | # Validate Mermaid after undo |
| 144 | mermaid_result = await _validate_mermaid(str(doc_path), filename) |
| 145 | return json.dumps({ |
| 146 | "status": "undone", |
| 147 | "filename": filename, |
| 148 | "mermaid_validation": mermaid_result, |
| 149 | }, ensure_ascii=False) |
| 150 | |
| 151 | if not await asyncio.to_thread(doc_path.exists): |
| 152 | return json.dumps({"error": f"File not found: {filename}. Use write_doc_file to create it."}) |
| 153 | |
| 154 | current_content = await asyncio.to_thread(doc_path.read_text, "utf-8") |
| 155 | |
| 156 | if command == "str_replace": |
| 157 | old_str = arguments.get("old_str") |
| 158 | new_str = arguments.get("new_str", "") |
| 159 | if old_str is None: |
| 160 | return json.dumps({"error": "old_str is required for str_replace."}) |
| 161 | |
| 162 | occurrences = current_content.count(old_str) |
| 163 | if occurrences == 0: |
| 164 | return json.dumps({"error": f"old_str not found in {filename}."}) |
| 165 | if occurrences > 1: |
| 166 | return json.dumps({"error": f"old_str appears {occurrences} times in {filename}. Make it unique."}) |
| 167 | |
| 168 | new_content = current_content.replace(old_str, new_str, 1) |
| 169 | # Save history only for edits that actually happen, so undo never |
| 170 | # pops a no-op entry left behind by a failed/rejected command. |