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:
2026-02-20 23:55:15 -05:00
parent 915d8ea04f
commit d32674937a

View File

@@ -583,6 +583,7 @@ def save_default_config() -> str:
os.makedirs(CONFIG_DIR, exist_ok=True)
if os.path.isfile(CONFIG_PATH):
migrate_config()
return CONFIG_PATH
default = {
@@ -689,6 +690,65 @@ def save_default_config() -> str:
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:
"""Write MCP server config to the appropriate file for the runtime.