mirror of
https://github.com/cft0808/edict.git
synced 2026-09-09 19:01:13 +08:00
Features: - 9 specialized agents (中书省·门下省·尚书省 + 六部) - Real-time dashboard with 6 tabs (Overview/Kanban/History/Timeline/Models/Skills) - Model configuration with live-apply via local API server - One-click install script - Data sync pipeline (15s refresh loop) - Full audit trail via flow_log
96 lines
4.2 KiB
Python
96 lines
4.2 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
同步 openclaw.json 中的 agent 配置 → data/agent_config.json
|
|
支持自动发现 agent workspace 下的 Skills 目录
|
|
"""
|
|
import json, pathlib, datetime
|
|
|
|
# Auto-detect project root (parent of scripts/)
|
|
BASE = pathlib.Path(__file__).parent.parent
|
|
DATA = BASE / 'data'
|
|
OPENCLAW_CFG = pathlib.Path.home() / '.openclaw' / 'openclaw.json'
|
|
|
|
ID_LABEL = {
|
|
'zhongshu': {'label': '中书省', 'role': '中书令', 'duty': '起草任务令与优先级', 'emoji': '📜'},
|
|
'menxia': {'label': '门下省', 'role': '侍中', 'duty': '审议与退回机制', 'emoji': '🔍'},
|
|
'shangshu': {'label': '尚书省', 'role': '尚书令', 'duty': '派单与升级裁决', 'emoji': '📮'},
|
|
'libu': {'label': '礼部', 'role': '礼部尚书', 'duty': '文档/汇报/规范', 'emoji': '📝'},
|
|
'hubu': {'label': '户部', 'role': '户部尚书', 'duty': '资源/预算/成本', 'emoji': '💰'},
|
|
'bingbu': {'label': '兵部', 'role': '兵部尚书', 'duty': '应急与巡检', 'emoji': '⚔️'},
|
|
'xingbu': {'label': '刑部', 'role': '刑部尚书', 'duty': '合规/审计/红线', 'emoji': '⚖️'},
|
|
'gongbu': {'label': '工部', 'role': '工部尚书', 'duty': '工程交付与自动化', 'emoji': '🔧'},
|
|
}
|
|
|
|
KNOWN_MODELS = [
|
|
{'id': 'anthropic/claude-sonnet-4-6', 'label': 'Claude Sonnet 4.6', 'provider': 'Anthropic'},
|
|
{'id': 'anthropic/claude-opus-4-5', 'label': 'Claude Opus 4.5', 'provider': 'Anthropic'},
|
|
{'id': 'anthropic/claude-haiku-3-5', 'label': 'Claude Haiku 3.5', 'provider': 'Anthropic'},
|
|
{'id': 'openai/gpt-4o', 'label': 'GPT-4o', 'provider': 'OpenAI'},
|
|
{'id': 'openai/gpt-4o-mini', 'label': 'GPT-4o Mini', 'provider': 'OpenAI'},
|
|
{'id': 'openai-codex/gpt-5.3-codex', 'label': 'GPT-5.3 Codex', 'provider': 'OpenAI Codex'},
|
|
{'id': 'google/gemini-2.0-flash', 'label': 'Gemini 2.0 Flash', 'provider': 'Google'},
|
|
{'id': 'google/gemini-2.5-pro', 'label': 'Gemini 2.5 Pro', 'provider': 'Google'},
|
|
]
|
|
|
|
|
|
def get_skills(workspace: str):
|
|
skills_dir = pathlib.Path(workspace) / 'skills'
|
|
skills = []
|
|
if skills_dir.exists():
|
|
for d in sorted(skills_dir.iterdir()):
|
|
if d.is_dir():
|
|
md = d / 'SKILL.md'
|
|
desc = ''
|
|
if md.exists():
|
|
for line in md.read_text(encoding='utf-8', errors='ignore').splitlines():
|
|
line = line.strip()
|
|
if line and not line.startswith('#'):
|
|
desc = line[:100]
|
|
break
|
|
skills.append({'name': d.name, 'path': str(md), 'exists': md.exists(), 'description': desc})
|
|
return skills
|
|
|
|
|
|
def main():
|
|
cfg = {}
|
|
try:
|
|
cfg = json.loads(OPENCLAW_CFG.read_text())
|
|
except Exception as e:
|
|
print(f'[WARN] cannot read openclaw.json: {e}')
|
|
return
|
|
|
|
agents_cfg = cfg.get('agents', {})
|
|
default_model = agents_cfg.get('defaults', {}).get('model', {}).get('primary', 'unknown')
|
|
agents_list = agents_cfg.get('list', [])
|
|
|
|
result = []
|
|
for ag in agents_list:
|
|
ag_id = ag.get('id', '')
|
|
if ag_id not in ID_LABEL:
|
|
continue
|
|
meta = ID_LABEL[ag_id]
|
|
workspace = ag.get('workspace', str(pathlib.Path.home() / f'.openclaw/workspace-{ag_id}'))
|
|
result.append({
|
|
'id': ag_id,
|
|
'label': meta['label'], 'role': meta['role'], 'duty': meta['duty'], 'emoji': meta['emoji'],
|
|
'model': ag.get('model', default_model),
|
|
'defaultModel': default_model,
|
|
'workspace': workspace,
|
|
'skills': get_skills(workspace),
|
|
'allowAgents': ag.get('subagents', {}).get('allowAgents', []),
|
|
})
|
|
|
|
payload = {
|
|
'generatedAt': datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),
|
|
'defaultModel': default_model,
|
|
'knownModels': KNOWN_MODELS,
|
|
'agents': result,
|
|
}
|
|
DATA.mkdir(exist_ok=True)
|
|
(DATA / 'agent_config.json').write_text(json.dumps(payload, ensure_ascii=False, indent=2))
|
|
print(f'[sync_agent_config] {len(result)} agents synced')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|