mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-10-25 17:36:29 +08:00
Windows Notepad / PowerShell write UTF-8 with a BOM by default, and the JSON /
txt files CowAgent asks users to edit by hand are exactly the ones that get
re-saved that way. Plain `utf-8` turns the leading BOM into a `\ufeff`
character, and what happens next depends on the reader:
- `plugins/*/config.json` (godcmd's is created on first run for the operator
to fill in): `json.load` raises "Unexpected UTF-8 BOM", the plugin's
`__init__` fails, and `PluginManager.activate_plugins` answers a failed init
by disabling the plugin and **persisting** `enabled=false` -- so it stayed
off across restarts even after the file was put right.
- `banwords.txt`: the first line is read as `\ufeffword`, `str.strip()` does
not remove U+FEFF (it is not whitespace), so the word is registered with the
BOM glued to it and can never match. No error -- the first word of the list
is silently unfiltered.
- `skills_config.json`: the read helper swallows the error and answers `{}`,
and `cow skill list` rebuilds the file assuming `enabled: True` -- a skill
the user had turned off comes back on, silently.
- `config.json` in `cow backup`: same swallow, so every caller treats the
install as unconfigured and `cow backup` archives `~/cow` instead of the
configured `agent_workspace`. `restore` hits the same read on the copy inside
the archive, where it is not caught and aborts the restore.
- `mcp.json`: `_read_mcp_file` wraps it as `McpConfigError("mcp.json is not
valid JSON: ...")`, which names the JSON rather than the BOM, so the file
looks fine in an editor.
Apply the convention `agent/admin.py` already documents -- "utf-8-sig tolerates
a UTF-8 BOM (e.g. config.json edited with Windows Notepad / PowerShell)" -- at
every one of those read sites, replacing what were five separate PRs (#3419,
#3421, #3422, #3423) with one change.
`Plugin.load_config` also restores the `if os.path.exists(plugin_config_path)`
check inside the `if not plugin_conf` block that assigns `plugin_config_path`:
pulled out of it, the check ran on a name that was never assigned whenever
`pconf()` already returned a config, i.e. for every plugin with an entry in
`plugins/config.json`, and `load_config` raised `UnboundLocalError` -- the same
"plugin gets disabled" outcome the change was meant to avoid.
`tests/test_plugin_config_defaults.py` covers that case; it fails on the
previous revision with `UnboundLocalError: cannot access local variable
'plugin_config_path' where it is not associated with a value`.
50 lines
1.7 KiB
Python
50 lines
1.7 KiB
Python
import os
|
|
import json
|
|
from config import pconf, plugin_config, conf, write_plugin_config # noqa: F401 # re-exported via `from .plugin import *`
|
|
from common.atomic_write import write_json_atomic
|
|
from common.log import logger
|
|
|
|
|
|
class Plugin:
|
|
def __init__(self):
|
|
self.handlers = {}
|
|
|
|
def load_config(self) -> dict:
|
|
"""
|
|
加载当前插件配置
|
|
:return: 插件配置字典
|
|
"""
|
|
# 优先获取 plugins/config.json 中的全局配置
|
|
plugin_conf = pconf(self.name)
|
|
if not plugin_conf:
|
|
# 全局配置不存在,则获取插件目录下的配置
|
|
plugin_config_path = os.path.join(self.path, "config.json")
|
|
if os.path.exists(plugin_config_path):
|
|
with open(plugin_config_path, "r", encoding="utf-8-sig") as f:
|
|
plugin_conf = json.load(f)
|
|
|
|
# 写入全局配置内存
|
|
write_plugin_config({self.name: plugin_conf})
|
|
return plugin_conf
|
|
|
|
def save_config(self, config: dict):
|
|
try:
|
|
write_plugin_config({self.name: config})
|
|
# 写入全局配置
|
|
global_config_path = "./plugins/config.json"
|
|
if os.path.exists(global_config_path):
|
|
write_json_atomic(global_config_path, plugin_config)
|
|
# 写入插件配置
|
|
plugin_config_path = os.path.join(self.path, "config.json")
|
|
if os.path.exists(plugin_config_path):
|
|
write_json_atomic(plugin_config_path, config)
|
|
|
|
except Exception as e:
|
|
logger.warn("save plugin config failed: {}".format(e))
|
|
|
|
def get_help_text(self, **kwargs):
|
|
return "暂无帮助信息"
|
|
|
|
def reload(self):
|
|
pass
|