1仕組みと動作原理
統合エントリポイントは純粋な argparse 転送レイヤーです。cli.py は SUBCOMMANDS 辞書を用いて audit/compare/diff/copy/add-model/list/switch を各サブモジュールの main() にマッピングしており、`diff` は単なる `compare` のエイリアスで独自のビジネスロジックは持ちません。
SUBCOMMANDS = { "audit": audit, "compare": compare, "diff": compare, "copy": copy_provider,
設定ファイルの特定は「明示的パス → ニックネームレジストリ → 3つのデフォルトパス」の3段階フォールバックであり、デフォルトパスの優先順位は DEFAULT_CONFIG_PATHS にハードコードされています。
DEFAULT_CONFIG_PATHS = [ Path.home() / "workspace" / ".force" / "openclaw" / "openclaw.json", Path.home() / ".kimi_openclaw" / "openclaw.json", Path.home() / ".openclaw" / "openclaw.json", ]
複数インスタンスの抽象化は lobsters.json ニックネームレジストリに基づいています。まず Path で引数が実在するファイルかを判定し、ファイルでない場合にレジストリ内で nickname→path のマッピングを検索します。
registry = load_lobster_registry() if str(name_or_path) in registry: return Path(registry[str(name_or_path)])
すべての書き込み操作には自動バックアップが付属し、保持上限が存在します。<設定ディレクトリ>/config-backups/<stem>-<UTCタイムスタンプ>.json に書き込み、その後 mtime 順で20件を超える古いバックアップを削除します。
ts = datetime.now(timezone.utc).strftime("%Y%m%d-%H%M%S") backup_path = backup_dir / f"{p.stem}-{ts}{p.suffix}" shutil.copy2(p, backup_path)
デフォルトモデルの変更時は既存の構造を保持します。agents.defaults.model がもともとオブジェクトであれば primary のみを変更し、文字列であれば文字列として書き込みます。元の記述形式を強制的に正規化することはありません。
if isinstance(current, dict): defaults["model"] = {**current, "primary": ref} else: defaults["model"] = ref
変更系サブコマンドはデフォルトで「変更前診断 + エラー発生時の中止 + 変更後再診断」を実施し、`--no-audit` が指定された場合のみスキップします。これが自動監査(auto-audit)ガードレールです。
if any(i["level"] == "error" for i in issues): print("Aborting due to audit errors. Use --no-audit to skip.", file=sys.stderr) return 2
copy は「上書き」ではなく「マージ」です。baseUrl/api および同名モデルの定義のみを同期し、コピー先固有のモデルは削除されません。
for key in ("baseUrl", "api"):
ゲートウェイの再起動には3段階のフォールバックを使用します。まず `openclaw gateway restart` を試行し、失敗した場合は `systemctl --user restart`、さらにシステムレベルの `systemctl restart` へとフォールバックします。3段階すべて失敗しても警告を出力するのみで例外は送出されません。
commands = [ ["openclaw", "gateway", "restart"], ["systemctl", "--user", "restart", "openclaw-gateway"], ["systemctl", "restart", "openclaw-gateway"], ]
現場でのトラブルシューティング経験を実行可能なアサーションとして定着化:audit には DeepSeek アンチパターンのヒューリスティックが含まれ、provider 名が deepseek で始まる場合に警告を出し、正しいパス(gateway provider + deepseek-v4-pro)を案内します。
if pname.lower().startswith("deepseek"):
2コア機能
3外部依存関係
| Kind | Dependency / Endpoint |
|---|---|
| cli | openclaw CLI(gateway restart) |
| cli | systemctl(用户级/系统级服务重启回退) |
| package | Python 3 标准库 only(json/os/shutil/subprocess/argparse/pathlib;无第三方依赖) |
| network | OpenClaw provider baseUrl(仅写在配置里被审计/搬运,skill 自身从不发请求) |
4推奨統制措置と判定根拠
- scripts/: ローカルスクリプトまたはコマンドの実行: python3 scripts/cli.py <audit|diff|copy|add-model|list|switch>(または対応するサブスクリプトを直接実行)
- 环境变量 / API Key: ベンダーサービスAPIキーの読み取り: openclaw.json 内の models.providers.*.apiKey: 設定ファイル全体とともにメモリに読み込まれ書き戻される(スクリプトがこのフィールドを明示的に抽出することはありません); copy/
- 本地工作目录: ファイルの読み書きおよび成果物の出力: <設定ディレクトリ>/config-backups/<stem>-<UTCタイムスタンプ>.json(書き込み操作の直前に毎回バックアップ)
5二重目の独立検証
- [ok] SKILL.md 声明「Backups: every write operation copies the config to config-backups/<stem>-<utc-timestamp>.json」是否与代码一致 — 三条写路径 copy_provider.py / add_model.py / switch_model.py 都在 save_config 之前调用 backup_config(config_path),且 openclaw_config.backup_config 用 datetime.now(timezone.utc) 生成时间戳、文件名格式恰为 f"{p.stem}-{ts}{p.suffix}";SKILL.md 关于「keeps the 20 most recent backups」也对得上 max_backups: int = 20 默认值与 existing[:-max_backups] 裁剪。
- [ok] SKILL.md 声明「switch 是唯一改 agents.defaults.model 的子命令」 — grep set_default_model 的调用点只有 switch_model.py 一处;add_model.py 只写 agents.defaults.models(别名映射),copy_provider.py 只写 models.providers 与别名,compare/list/audit 纯只读,声明成立。
- [ok] SKILL.md「Restart: write subcommands support --restart to restart the OpenClaw gateway after saving」 — 三个写脚本都定义了 --restart 并调用 restart_gateway();且未给 --restart 时会打印「NOTE: You must restart the OpenClaw gateway...」提示,行为与文档一致。副产物确认:restart_gateway 的第三档是系统级 systemctl(无 --user),文档未提及这一越权风险,已写进 verdict.risks。
- [ok] external_deps 里是否漏掉了网络调用(有没有隐藏的 endpoint probe 或遥测) — 反向扫描 scripts/ 全部 8 文件:无 import urllib/requests/socket/httpx,无 curl/wget 字符串,无任何 URL 常量。openclaw 与 openclaw-model-switch 的关键差异(后者文档要求 curl 探针)在此得到对照确认。network 依赖项仅保留「配置里存在 baseUrl 字段」这一事实,并显式标注 skill 自身不请求。
- [ok] 是否读 env 里的 API key / 密钥 — scripts/ 内 os.environ、getenv 零命中;openclaw_config.py 里 import os 的唯一用途是 os.path.getmtime 做备份排序。凭证面仅为「随配置整份读写的 apiKey 字段」与「copy/add-model 把 provider 块连同 apiKey 深拷贝到目标配置」,已在 security.credential_reads 如实登记而非留空。
- [ok] 「copy 不删目标独有模型」是否为真(找反例:有没有静默覆盖整个 provider 的分支) — 反例检查通过:当目标已有该 provider 时走 merge_provider(只按 id 增补/更新、且注释明确 does not remove target-only models),不整块替换;唯一整体替换发生在「目标根本没有该 provider」时(copy.deepcopy(source_provider)),此时不存在目标独有模型可丢。--alias 分支只做 target_aliases[ref] = deepcopy(detail) 的增补,也不删。
- [discrepancy] 备份保留逻辑会不会误删非备份文件 — 发现第一遍未记的边界:裁剪用 backup_dir.glob(f"{p.stem}-*{p.suffix}") 匹配,若用户在 config-backups/ 目录里放了同名模式的其它文件(例如手工备份 openclaw-手工.json,而 stem 恰为 openclaw),mtime 最旧的会被 unlink。属真实但低概率的删除面,已补进 verdict.risks。
- [ok] 功能声明 vs 实际能力夸大检查(SKILL.md / README 有没有说到做不到的事) — SKILL.md 的六子命令、昵称注册表、三级默认路径、备份与 20 份上限、dry-run、restart、自动 audit 全部可在代码中一一落到实处,未发现夸大。唯一「文档比代码多说一句」的是 references/deepseek_patch_sop.md 的示例命令 `add-model gateway-provider references/deepseek_model.json`(相对路径需从 skill 根执行),属示例语境问题而非能力夸大。
6結論
c792eb95fa4dde39…