feat: openclaw-style secrets (env.vars + \) and per-task model routing
- Replace python-dotenv with config.json env.vars block + \ substitution - Add models section for per-task model routing (heartbeat, subagent, default) - Heartbeat/subagent tasks can use different models/providers than main chat - Remove python-dotenv from dependencies - Update all docs to reflect new config approach - Reorganize docs into project/ and research/ subdirectories
This commit is contained in:
60
config.py
60
config.py
@@ -583,6 +583,7 @@ def save_default_config() -> str:
|
|||||||
os.makedirs(CONFIG_DIR, exist_ok=True)
|
os.makedirs(CONFIG_DIR, exist_ok=True)
|
||||||
|
|
||||||
if os.path.isfile(CONFIG_PATH):
|
if os.path.isfile(CONFIG_PATH):
|
||||||
|
migrate_config()
|
||||||
return CONFIG_PATH
|
return CONFIG_PATH
|
||||||
|
|
||||||
default = {
|
default = {
|
||||||
@@ -689,6 +690,65 @@ def save_default_config() -> str:
|
|||||||
return CONFIG_PATH
|
return CONFIG_PATH
|
||||||
|
|
||||||
|
|
||||||
|
def migrate_config() -> bool:
|
||||||
|
"""Patch an existing config.json with new sections added in later versions.
|
||||||
|
|
||||||
|
Called on every startup (via ``save_default_config``). Only touches the
|
||||||
|
file when keys are actually missing — existing values are never
|
||||||
|
overwritten. Returns ``True`` if the file was modified.
|
||||||
|
"""
|
||||||
|
if not os.path.isfile(CONFIG_PATH):
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
with open(CONFIG_PATH, "r", encoding="utf-8") as f:
|
||||||
|
data = json.load(f)
|
||||||
|
except (json.JSONDecodeError, OSError):
|
||||||
|
return False
|
||||||
|
|
||||||
|
changed = False
|
||||||
|
|
||||||
|
# --- env.vars block (added when secrets moved from .env) ---------------
|
||||||
|
if "env" not in data:
|
||||||
|
data["env"] = {
|
||||||
|
"vars": {
|
||||||
|
"SLACK_BOT_TOKEN": "",
|
||||||
|
"SLACK_APP_TOKEN": "",
|
||||||
|
"TELEGRAM_BOT_TOKEN": "",
|
||||||
|
"DISCORD_BOT_TOKEN": "",
|
||||||
|
"ANTHROPIC_API_KEY": "",
|
||||||
|
"OPENCODE_SERVER_PASSWORD": "",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
changed = True
|
||||||
|
elif "vars" not in data.get("env", {}):
|
||||||
|
data["env"]["vars"] = {}
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# --- bot_token fields in adapter sections ------------------------------
|
||||||
|
for section, token_key, env_ref in [
|
||||||
|
("slack", "bot_token", "${SLACK_BOT_TOKEN}"),
|
||||||
|
("slack", "app_token", "${SLACK_APP_TOKEN}"),
|
||||||
|
("telegram", "bot_token", "${TELEGRAM_BOT_TOKEN}"),
|
||||||
|
("discord", "bot_token", "${DISCORD_BOT_TOKEN}"),
|
||||||
|
]:
|
||||||
|
if section in data and token_key not in data[section]:
|
||||||
|
data[section][token_key] = env_ref
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
# --- models section (per-task model routing) ---------------------------
|
||||||
|
if "models" not in data:
|
||||||
|
data["models"] = {"heartbeat": None, "subagent": None, "default": None}
|
||||||
|
changed = True
|
||||||
|
|
||||||
|
if changed:
|
||||||
|
with open(CONFIG_PATH, "w", encoding="utf-8") as f:
|
||||||
|
json.dump(data, f, indent=2)
|
||||||
|
logger.info(f"Config migrated with new sections: {CONFIG_PATH}")
|
||||||
|
|
||||||
|
return changed
|
||||||
|
|
||||||
|
|
||||||
def write_mcp_config(mcp_config: MCPConfig, workspace_dir: str, use_claude: bool) -> None:
|
def write_mcp_config(mcp_config: MCPConfig, workspace_dir: str, use_claude: bool) -> None:
|
||||||
"""Write MCP server config to the appropriate file for the runtime.
|
"""Write MCP server config to the appropriate file for the runtime.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user