diff --git a/nanobot/agent/loop.py b/nanobot/agent/loop.py index 1620cb0..d29c13f 100644 --- a/nanobot/agent/loop.py +++ b/nanobot/agent/loop.py @@ -206,7 +206,7 @@ class AgentLoop: "type": "function", "function": { "name": tc.name, - "arguments": json.dumps(tc.arguments) + "arguments": json.dumps(tc.arguments, ensure_ascii=False) } } for tc in response.tool_calls diff --git a/nanobot/agent/subagent.py b/nanobot/agent/subagent.py index 7d48cc4..767bc68 100644 --- a/nanobot/agent/subagent.py +++ b/nanobot/agent/subagent.py @@ -146,7 +146,7 @@ class SubagentManager: "type": "function", "function": { "name": tc.name, - "arguments": json.dumps(tc.arguments), + "arguments": json.dumps(tc.arguments, ensure_ascii=False), }, } for tc in response.tool_calls @@ -159,7 +159,7 @@ class SubagentManager: # Execute tools for tool_call in response.tool_calls: - args_str = json.dumps(tool_call.arguments) + args_str = json.dumps(tool_call.arguments, ensure_ascii=False) logger.debug("Subagent [{}] executing: {} with arguments: {}", task_id, tool_call.name, args_str) result = await tools.execute(tool_call.name, tool_call.arguments) messages.append({ diff --git a/nanobot/agent/tools/web.py b/nanobot/agent/tools/web.py index 9de1d3c..90cdda8 100644 --- a/nanobot/agent/tools/web.py +++ b/nanobot/agent/tools/web.py @@ -116,7 +116,7 @@ class WebFetchTool(Tool): # Validate URL before fetching is_valid, error_msg = _validate_url(url) if not is_valid: - return json.dumps({"error": f"URL validation failed: {error_msg}", "url": url}) + return json.dumps({"error": f"URL validation failed: {error_msg}", "url": url}, ensure_ascii=False) try: async with httpx.AsyncClient( @@ -131,7 +131,7 @@ class WebFetchTool(Tool): # JSON if "application/json" in ctype: - text, extractor = json.dumps(r.json(), indent=2), "json" + text, extractor = json.dumps(r.json(), indent=2, ensure_ascii=False), "json" # HTML elif "text/html" in ctype or r.text[:256].lower().startswith((" str: """Convert HTML to markdown.""" diff --git a/nanobot/channels/dingtalk.py b/nanobot/channels/dingtalk.py index f6dca30..b7263b3 100644 --- a/nanobot/channels/dingtalk.py +++ b/nanobot/channels/dingtalk.py @@ -208,7 +208,7 @@ class DingTalkChannel(BaseChannel): "msgParam": json.dumps({ "text": msg.content, "title": "Nanobot Reply", - }), + }, ensure_ascii=False), } if not self._http: diff --git a/nanobot/channels/feishu.py b/nanobot/channels/feishu.py index c17bf1a..a8ca1fa 100644 --- a/nanobot/channels/feishu.py +++ b/nanobot/channels/feishu.py @@ -390,7 +390,7 @@ class FeishuChannel(BaseChannel): if key: await loop.run_in_executor( None, self._send_message_sync, - receive_id_type, msg.chat_id, "image", json.dumps({"image_key": key}), + receive_id_type, msg.chat_id, "image", json.dumps({"image_key": key}, ensure_ascii=False), ) else: key = await loop.run_in_executor(None, self._upload_file_sync, file_path) @@ -398,7 +398,7 @@ class FeishuChannel(BaseChannel): media_type = "audio" if ext in self._AUDIO_EXTS else "file" await loop.run_in_executor( None, self._send_message_sync, - receive_id_type, msg.chat_id, media_type, json.dumps({"file_key": key}), + receive_id_type, msg.chat_id, media_type, json.dumps({"file_key": key}, ensure_ascii=False), ) if msg.content and msg.content.strip(): diff --git a/nanobot/channels/whatsapp.py b/nanobot/channels/whatsapp.py index f3e14d9..f5fb521 100644 --- a/nanobot/channels/whatsapp.py +++ b/nanobot/channels/whatsapp.py @@ -87,7 +87,7 @@ class WhatsAppChannel(BaseChannel): "to": msg.chat_id, "text": msg.content } - await self._ws.send(json.dumps(payload)) + await self._ws.send(json.dumps(payload, ensure_ascii=False)) except Exception as e: logger.error("Error sending WhatsApp message: {}", e) diff --git a/nanobot/cli/commands.py b/nanobot/cli/commands.py index 2f4ba7b..d879d58 100644 --- a/nanobot/cli/commands.py +++ b/nanobot/cli/commands.py @@ -243,7 +243,7 @@ Information about the user goes here. for filename, content in templates.items(): file_path = workspace / filename if not file_path.exists(): - file_path.write_text(content) + file_path.write_text(content, encoding="utf-8") console.print(f" [dim]Created {filename}[/dim]") # Create memory directory and MEMORY.md @@ -266,12 +266,12 @@ This file stores important information that should persist across sessions. ## Important Notes (Things to remember) -""") +""", encoding="utf-8") console.print(" [dim]Created memory/MEMORY.md[/dim]") history_file = memory_dir / "HISTORY.md" if not history_file.exists(): - history_file.write_text("") + history_file.write_text("", encoding="utf-8") console.print(" [dim]Created memory/HISTORY.md[/dim]") # Create skills directory for custom user skills diff --git a/nanobot/config/loader.py b/nanobot/config/loader.py index 560c1f5..c789efd 100644 --- a/nanobot/config/loader.py +++ b/nanobot/config/loader.py @@ -31,7 +31,7 @@ def load_config(config_path: Path | None = None) -> Config: if path.exists(): try: - with open(path) as f: + with open(path, encoding="utf-8") as f: data = json.load(f) data = _migrate_config(data) return Config.model_validate(data) @@ -55,8 +55,8 @@ def save_config(config: Config, config_path: Path | None = None) -> None: data = config.model_dump(by_alias=True) - with open(path, "w") as f: - json.dump(data, f, indent=2) + with open(path, "w", encoding="utf-8") as f: + json.dump(data, f, indent=2, ensure_ascii=False) def _migrate_config(data: dict) -> dict: diff --git a/nanobot/cron/service.py b/nanobot/cron/service.py index 4c14ef7..2128064 100644 --- a/nanobot/cron/service.py +++ b/nanobot/cron/service.py @@ -66,7 +66,7 @@ class CronService: if self.store_path.exists(): try: - data = json.loads(self.store_path.read_text()) + data = json.loads(self.store_path.read_text(encoding="utf-8")) jobs = [] for j in data.get("jobs", []): jobs.append(CronJob( @@ -148,7 +148,7 @@ class CronService: ] } - self.store_path.write_text(json.dumps(data, indent=2)) + self.store_path.write_text(json.dumps(data, indent=2, ensure_ascii=False), encoding="utf-8") async def start(self) -> None: """Start the cron service.""" diff --git a/nanobot/heartbeat/service.py b/nanobot/heartbeat/service.py index 8b33e3a..3c1a6aa 100644 --- a/nanobot/heartbeat/service.py +++ b/nanobot/heartbeat/service.py @@ -65,7 +65,7 @@ class HeartbeatService: """Read HEARTBEAT.md content.""" if self.heartbeat_file.exists(): try: - return self.heartbeat_file.read_text() + return self.heartbeat_file.read_text(encoding="utf-8") except Exception: return None return None diff --git a/nanobot/providers/openai_codex_provider.py b/nanobot/providers/openai_codex_provider.py index 2336e71..fa28593 100644 --- a/nanobot/providers/openai_codex_provider.py +++ b/nanobot/providers/openai_codex_provider.py @@ -176,7 +176,7 @@ def _convert_messages(messages: list[dict[str, Any]]) -> tuple[str, list[dict[st if role == "tool": call_id, _ = _split_tool_call_id(msg.get("tool_call_id")) - output_text = content if isinstance(content, str) else json.dumps(content) + output_text = content if isinstance(content, str) else json.dumps(content, ensure_ascii=False) input_items.append( { "type": "function_call_output", diff --git a/nanobot/session/manager.py b/nanobot/session/manager.py index 9c0c7de..9c1e427 100644 --- a/nanobot/session/manager.py +++ b/nanobot/session/manager.py @@ -121,7 +121,7 @@ class SessionManager: created_at = None last_consolidated = 0 - with open(path) as f: + with open(path, encoding="utf-8") as f: for line in f: line = line.strip() if not line: @@ -151,7 +151,7 @@ class SessionManager: """Save a session to disk.""" path = self._get_session_path(session.key) - with open(path, "w") as f: + with open(path, "w", encoding="utf-8") as f: metadata_line = { "_type": "metadata", "created_at": session.created_at.isoformat(), @@ -159,9 +159,9 @@ class SessionManager: "metadata": session.metadata, "last_consolidated": session.last_consolidated } - f.write(json.dumps(metadata_line) + "\n") + f.write(json.dumps(metadata_line, ensure_ascii=False) + "\n") for msg in session.messages: - f.write(json.dumps(msg) + "\n") + f.write(json.dumps(msg, ensure_ascii=False) + "\n") self._cache[session.key] = session @@ -181,7 +181,7 @@ class SessionManager: for path in self.sessions_dir.glob("*.jsonl"): try: # Read just the metadata line - with open(path) as f: + with open(path, encoding="utf-8") as f: first_line = f.readline().strip() if first_line: data = json.loads(first_line)