mirror of
https://github.com/cft0808/edict.git
synced 2026-09-06 08:58:42 +08:00
P0: - 圣旨模板下旨真正创建任务: 新增 POST /api/create-task 前端 executeTemplate 改为 API 调用(降级仍可剪贴板复制) P1: - morning-config POST 字段校验: 检查 categories/keywords/feishu_webhook 类型 - 早报幂等锁支持 --force 强制采集: 看板手动刷新默认 force=true - sync_agent_config 补全 Copilot 模型列表(6个) P2: - utils.py 公共函数抽取: read_json/now_iso/validate_url/safe_name - refresh_live_data.py 改用 utils.read_json 消除重复定义 - apply_model_changes 回滚标记: 失败时 rolledBack=true 写入日志+前端展示 - 早报日期 API 兼容 YYYY-MM-DD 自动转换 + 格式校验 - Request logging: log_message 改为只记录 4xx/5xx 错误请求 - 飞书 Webhook URL 校验: 限制 https + open.feishu.cn 域名 P3: - 御批模式基础实现: Review/Menxia 状态显示准奏/封驳按钮 新增 POST /api/review-action(approve推进/reject退回中书省+轮次+1) 前端 reviewAction() + 变更日志回滚标记显示
112 lines
3.8 KiB
Python
112 lines
3.8 KiB
Python
#!/usr/bin/env python3
|
||
"""应用 data/pending_model_changes.json → openclaw.json,并重启 Gateway"""
|
||
import json, pathlib, subprocess, datetime, shutil, logging, glob
|
||
from file_lock import atomic_json_write, atomic_json_read
|
||
|
||
log = logging.getLogger('model_change')
|
||
logging.basicConfig(level=logging.INFO, format='%(asctime)s [%(name)s] %(message)s', datefmt='%H:%M:%S')
|
||
|
||
BASE = pathlib.Path(__file__).parent.parent
|
||
DATA = BASE / 'data'
|
||
OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json'
|
||
PENDING = DATA / 'pending_model_changes.json'
|
||
CHANGE_LOG = DATA / 'model_change_log.json'
|
||
MAX_BACKUPS = 10
|
||
|
||
|
||
def rj(path, default):
|
||
try:
|
||
return json.loads(path.read_text())
|
||
except Exception:
|
||
return default
|
||
|
||
|
||
def cleanup_backups():
|
||
"""只保留最近 MAX_BACKUPS 个备份"""
|
||
pattern = str(OPENCLAW_CFG.parent / 'openclaw.json.bak.model-*')
|
||
baks = sorted(glob.glob(pattern))
|
||
for old in baks[:-MAX_BACKUPS]:
|
||
try:
|
||
pathlib.Path(old).unlink()
|
||
except OSError:
|
||
pass
|
||
|
||
|
||
def main():
|
||
if not PENDING.exists():
|
||
return
|
||
pending = rj(PENDING, [])
|
||
if not pending:
|
||
return
|
||
|
||
cfg = rj(OPENCLAW_CFG, {})
|
||
agents_list = cfg.get('agents', {}).get('list', [])
|
||
default_model = cfg.get('agents', {}).get('defaults', {}).get('model', {}).get('primary', '')
|
||
|
||
applied, errors = [], []
|
||
for change in pending:
|
||
ag_id = change.get('agentId', '').strip()
|
||
new_model = change.get('model', '').strip()
|
||
if not ag_id or not new_model:
|
||
errors.append({'change': change, 'error': 'missing fields'})
|
||
continue
|
||
found = False
|
||
for ag in agents_list:
|
||
if ag.get('id') == ag_id:
|
||
old = ag.get('model', default_model)
|
||
if new_model == default_model:
|
||
ag.pop('model', None)
|
||
else:
|
||
ag['model'] = new_model
|
||
applied.append({'at': datetime.datetime.now().isoformat(), 'agentId': ag_id, 'oldModel': old, 'newModel': new_model})
|
||
found = True
|
||
break
|
||
if not found:
|
||
errors.append({'change': change, 'error': f'agent {ag_id} not found'})
|
||
|
||
if applied:
|
||
bak = OPENCLAW_CFG.parent / f'openclaw.json.bak.model-{datetime.datetime.now().strftime("%Y%m%d-%H%M%S")}'
|
||
shutil.copy2(OPENCLAW_CFG, bak)
|
||
cleanup_backups()
|
||
cfg['agents']['list'] = agents_list
|
||
atomic_json_write(OPENCLAW_CFG, cfg)
|
||
|
||
log_data = rj(CHANGE_LOG, [])
|
||
log_data.extend(applied)
|
||
if len(log_data) > 200:
|
||
log_data = log_data[-200:]
|
||
atomic_json_write(CHANGE_LOG, log_data)
|
||
|
||
for e in applied:
|
||
log.info(f'{e["agentId"]}: {e["oldModel"]} → {e["newModel"]}')
|
||
|
||
restart_ok = False
|
||
rollback = False
|
||
try:
|
||
r = subprocess.run(['openclaw', 'gateway', 'restart'], capture_output=True, text=True, timeout=30)
|
||
restart_ok = r.returncode == 0
|
||
log.info(f'gateway restart rc={r.returncode}')
|
||
except Exception as e:
|
||
log.error(f'gateway restart failed: {e}')
|
||
# 回滚配置
|
||
if bak.exists():
|
||
shutil.copy2(bak, OPENCLAW_CFG)
|
||
log.warning('rolled back openclaw.json from backup')
|
||
rollback = True
|
||
for a in applied:
|
||
a['rolledBack'] = True
|
||
|
||
atomic_json_write(PENDING, [])
|
||
atomic_json_write(DATA / 'last_model_change_result.json', {
|
||
'at': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
||
'applied': applied, 'errors': errors,
|
||
'gatewayRestarted': restart_ok, 'rolledBack': rollback,
|
||
})
|
||
elif errors:
|
||
log.warning(f'{len(errors)} changes failed, 0 applied')
|
||
atomic_json_write(PENDING, [])
|
||
|
||
|
||
if __name__ == '__main__':
|
||
main()
|