From 6fe2e2bd3a1d78a6fba7cca03973ed8c0b9f1537 Mon Sep 17 00:00:00 2001 From: Tao Date: Sat, 28 Feb 2026 07:14:51 +1000 Subject: [PATCH] feat: release v0.7.1 telegram guidance + CLI model dropdown source --- DEVLOG.md | 32 ++++++++++++++- openclaw-manager.js | 96 +++++++++++++++++++++++++++++++++++++++------ start.bat | 4 +- start.sh | 4 +- 4 files changed, 120 insertions(+), 16 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index 0e8519e..a972f53 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,7 +1,37 @@ # OpenClaw Manager — 开发日志 > 最后更新:2026-02-27 -> 当前版本:v0.7.0 +> 当前版本:v0.7.1 + +--- + +## v0.7.1 更新日志(2026-02-27) + +### New Features + +**Dashboard Guidance Block (Telegram-first usage + safety)** +- Added a concise guidance card under Dashboard explaining OCM's intended Telegram workflow +- Clarifies prerequisites: users should already have basic OpenClaw CLI experience +- Clarifies purpose: OCM mainly helps visualize and update `openclaw.json` for easier main-agent/sub-agent management and multi-tree structures +- Added explicit Telegram security checklist: + - BotFather `Allow Groups = ON` + - BotFather `Group Privacy = OFF` + - Keep each group private (only you + agent/sub-agents), do not invite others + +### Improvements + +**Model Dropdown Source — only `openclaw models list`** +- Model dropdown options are now sourced from real CLI output (`openclaw models list`) instead of built-in/static lists or config-only additions +- Applies to all relevant selectors: agent/sub-agent creation forms, agent inline model switcher, primary model selector, and fallback picker +- Added warning message when model list loading/parsing fails + +### Technical Notes + +- Added model ID parser for CLI output and short-term cache for model list fetches +- `GET /api/models` now returns: + - `knownModels` from CLI + - `modelListError` for UI warning display +- Startup script banners updated to `v0.7.1` (`start.sh` / `start.bat`) --- diff --git a/openclaw-manager.js b/openclaw-manager.js index 9339c48..84e0448 100644 --- a/openclaw-manager.js +++ b/openclaw-manager.js @@ -1,6 +1,6 @@ #!/usr/bin/env node // ================================================================ -// OpenClaw Manager v0.7.0 +// OpenClaw Manager v0.7.1 // 跨平台本地管理工具 (Windows / macOS / Linux) // // 用法: @@ -24,7 +24,7 @@ const SCRIPT_DIR = __dirname; const MANAGER_CONFIG = path.join(SCRIPT_DIR, 'manager-config.json'); let PORT = 3333; let HOST = '0.0.0.0'; -const APP_VERSION = '0.7.0'; +const APP_VERSION = '0.7.1'; // --port 参数 const portIdx = process.argv.indexOf('--port'); if (portIdx !== -1 && process.argv[portIdx + 1]) PORT = parseInt(process.argv[portIdx + 1]) || 3333; @@ -197,6 +197,37 @@ function runOpenclawCmd(args) { }); } +function parseModelIdsFromCliOutput(raw) { + if (!raw) return []; + const set = new Set(); + const isModelId = (s) => /^[a-z0-9][a-z0-9_-]*(?:\/[A-Za-z0-9._:-]+)+$/.test(s); + for (const line of raw.split(/\r?\n/)) { + const trimmed = line.trim(); + if (!trimmed) continue; + const normalized = trimmed.replace(/^[-*]\s+/, ''); + const first = normalized.split(/\s+/)[0].replace(/^`|`$/g, ''); + if (isModelId(first)) set.add(first); + } + return Array.from(set).sort((a, b) => a.localeCompare(b)); +} + +let MODEL_LIST_CACHE = { ts: 0, knownModels: [], error: '' }; +async function getKnownModelsFromCli() { + const now = Date.now(); + if (now - MODEL_LIST_CACHE.ts < 30000) return MODEL_LIST_CACHE; + try { + const out = await runOpenclawCmd('models list'); + const ids = parseModelIdsFromCliOutput(out); + const knownModels = ids.map(id => ({ id, label: id, group: id.split('/')[0] || 'other' })); + const error = knownModels.length ? '' : 'No model IDs found in `openclaw models list` output'; + MODEL_LIST_CACHE = { ts: now, knownModels, error }; + return MODEL_LIST_CACHE; + } catch (e) { + MODEL_LIST_CACHE = { ts: now, knownModels: [], error: e.message || 'openclaw models list failed' }; + return MODEL_LIST_CACHE; + } +} + // 过滤 ANSI 终端控制码(光标移动、清行、颜色等) function stripAnsi(str) { return str @@ -628,13 +659,15 @@ async function handleApi(req, res, urlObj, body) { // GET /api/models if (method === 'GET' && pathname === '/api/models') { const cfg = await readConfig(); + const modelList = await getKnownModelsFromCli(); res.writeHead(200); res.end(JSON.stringify({ models: cfg.agents?.defaults?.models || {}, authProfiles: cfg.auth?.profiles || {}, primaryModel: cfg.agents?.defaults?.model?.primary || '', fallbacks: cfg.agents?.defaults?.model?.fallbacks || [], - knownModels: KNOWN_MODELS, + knownModels: modelList.knownModels || [], + modelListError:modelList.error || '', authProviders: AUTH_PROVIDERS, })); return; @@ -1627,6 +1660,10 @@ select option { background:var(--surface); } .dash-sections { display:grid; grid-template-columns:repeat(auto-fit,minmax(300px,1fr)); gap:16px; } .dash-card { padding:20px; } .dash-card h3 { font-size:14px; font-weight:600; margin-bottom:14px; color:var(--text); } +.dash-notice { margin-top:18px; padding:16px 18px; } +.dash-notice h3 { font-size:14px; margin-bottom:10px; } +.dash-note-list { margin-left:16px; color:var(--muted); font-size:12px; line-height:1.7; } +.dash-note-list li { margin-bottom:4px; } .dash-row { display:flex; justify-content:space-between; align-items:center; padding:6px 0; border-bottom:1px solid var(--border); font-size:12px; } .dash-row:last-child { border-bottom:none; } .dash-label { color:var(--muted); } @@ -1824,6 +1861,15 @@ const MAIN_HTML_BODY = String.raw`
Loading...
+
+

📌 Telegram 使用说明(重要)

+ +
⚠️ 强烈建议:每个 Agent 群组只保留你自己和该 Agent(或其 Sub-Agent)。不要邀请其他人,把每个组当作私聊空间。
+
@@ -1852,6 +1898,8 @@ const MAIN_HTML_BODY = String.raw`

模型管理

模型由 openclaw onboard 注册,此处管理主模型和 Fallback 链。

+

模型下拉仅显示 openclaw models list 返回的模型。

+
默认主模型primary
@@ -2199,6 +2247,14 @@ const I18N = { 'channels.title':'Channel 绑定','channels.add':'+ 添加绑定','channels.hint':'管理 Agent 与频道/群组的绑定关系。绑定顺序决定优先级。', 'models.title':'模型管理','models.primary':'默认主模型','models.fallback':'Fallback 链', 'models.hint':'模型由 openclaw onboard 注册,此处管理主模型和 Fallback 链。', + 'models.onlyCliHint':'模型下拉仅显示 openclaw models list 返回的模型。', + 'models.modelListErr':'读取 openclaw models list 失败:', + 'models.modelListEmpty':'未从 openclaw models list 解析到模型。请先运行 openclaw onboard 并确认模型可用。', + 'dash.noticeTitle':'📌 Telegram 使用说明(重要)', + 'dash.notice1':'OCM 主要面向 Telegram:通过群组绑定 Agent,让每个 Agent 拥有独立 Workspace / SOUL.md / MEMORY.md。', + 'dash.notice2':'请确保你已有基本 OpenClaw 操作经验。OCM 主要负责可视化更新 openclaw.json,方便增删主 Agent 与 Sub-Agent,并支持多条 Agent 树。', + 'dash.notice3':'BotFather 中请确认 Allow Groups = ON 且 Group Privacy = OFF,否则 Sub-Agent 可能无法入组或无法响应。', + 'dash.noticeWarn':'⚠️ 强烈建议:每个 Agent 群组只保留你自己和该 Agent(或其 Sub-Agent)。不要邀请其他人,把每个组当作私聊空间。', 'auth.title':'认证配置','auth.configured':'已配置认证', 'auth.guide':'点击 Provider 查看认证步骤。认证需在终端完成,或使用下方 CLI 终端。', 'auth.step1':'1. 获取 API Key','auth.step2':'2. 在终端运行以下命令','auth.step3':'3. 按提示粘贴 API Key 并回车', @@ -2310,6 +2366,14 @@ const I18N = { 'channels.title':'Channel Bindings','channels.add':'+ Add Binding','channels.hint':'Manage Agent to channel/group bindings. Order determines priority.', 'models.title':'Model Management','models.primary':'Default Primary Model','models.fallback':'Fallback Chain', 'models.hint':'Models are registered via openclaw onboard. Manage primary model and fallback chain here.', + 'models.onlyCliHint':'Model dropdowns only show IDs returned by openclaw models list.', + 'models.modelListErr':'Failed to load openclaw models list: ', + 'models.modelListEmpty':'No model IDs were parsed from openclaw models list. Run openclaw onboard first.', + 'dash.noticeTitle':'📌 Telegram Usage Notes (Important)', + 'dash.notice1':'OCM is primarily for Telegram workflows: bind agents to groups so each agent has isolated Workspace / SOUL.md / MEMORY.md.', + 'dash.notice2':'Basic OpenClaw CLI experience is required. OCM focuses on visual openclaw.json updates for easier main-agent/sub-agent management and multiple agent trees.', + 'dash.notice3':'In BotFather, keep Allow Groups = ON and Group Privacy = OFF; otherwise sub-agents may fail to join or respond.', + 'dash.noticeWarn':'⚠️ Strong recommendation: each agent group should include only you and that agent (or its sub-agents). Treat each group like a private chat.', 'auth.title':'Auth Config','auth.configured':'Configured Auth', 'auth.guide':'Click a Provider for setup instructions. Auth is done in terminal or via the CLI panel below.', 'auth.step1':'1. Get API Key','auth.step2':'2. Run the command below in terminal','auth.step3':'3. Paste your API Key when prompted', @@ -2472,7 +2536,7 @@ const LANDING_TEXT = { }, }; // ── 全局状态 ──────────────────────────────────────────────── -let S = { agents:[], channels:[], models:{}, authProfiles:{}, knownModels:[], authProviders:[], primaryModel:'', fallbacks:[] }; +let S = { agents:[], channels:[], models:{}, authProfiles:{}, knownModels:[], modelListError:'', authProviders:[], primaryModel:'', fallbacks:[] }; let wizCur = 1; let logTimer = null; let selectedAuthProv = null; @@ -2610,6 +2674,7 @@ async function loadModels(){ const r=await api('GET','/api/models'); S.models=r.models||{}; S.authProfiles=r.authProfiles||{}; S.knownModels=r.knownModels||[]; S.authProviders=r.authProviders||[]; + S.modelListError=r.modelListError||''; S.primaryModel=r.primaryModel||''; S.fallbacks=r.fallbacks||[]; renderModels(); renderAuth(); buildModelDropdowns(); }catch(e){ toast('加载模型失败: '+e.message,'error'); } @@ -2878,10 +2943,6 @@ function buildModelOpts(selected){ opts+=\`\`; }); if(lastGroup) opts+=\`\`; - Object.keys(S.models).forEach(id=>{ - if(!S.knownModels.find(k=>k.id===id)) - opts+=\`\`; - }); return opts; } @@ -2947,6 +3008,19 @@ async function deleteChannel(idx,label){ // ── 渲染模型 ───────────────────────────────────────────────── function renderModels(){ + const listWarn=document.getElementById('modelListWarn'); + if(listWarn){ + if(S.modelListError){ + listWarn.style.display=''; + listWarn.textContent=t('models.modelListErr')+S.modelListError; + }else if(!S.knownModels.length){ + listWarn.style.display=''; + listWarn.textContent=t('models.modelListEmpty'); + }else{ + listWarn.style.display='none'; + listWarn.textContent=''; + } + } // 检测 primary model 是否是 API Key(显示修复警告) const primWarn=document.getElementById('primaryModelWarn'); if(primWarn){ @@ -2963,7 +3037,7 @@ function renderModels(){ } } const pSel=document.getElementById('primaryModelSel'); - pSel.innerHTML=''; + pSel.innerHTML=''; S.knownModels.filter(m=>m.id!=='__default__').forEach(m=>{ pSel.innerHTML+=\`\`; }); @@ -3004,7 +3078,7 @@ async function savePrimaryModel(){ // 修复被错误设置为 API Key 的主模型 → 重置为第一个已注册模型或留空 async function fixBadPrimaryModel(){ // 尝试从已注册模型中取第一个可用 ID - const firstModel = Object.keys(S.models||{}).find(k => isValidModelId(k)); + const firstModel = (S.knownModels||[]).map(m=>m.id).find(k => isValidModelId(k)); const resetTo = firstModel || ''; if(!confirm(\`将主模型重置为"\${resetTo||'(清空,使用全局默认)'}"?\`)) return; try{ @@ -3158,7 +3232,7 @@ async function refreshAuthOnly(){ try{ const r=await api('GET','/api/models'); S.authProfiles=r.authProfiles||{}; - S.models=r.models||{}; S.knownModels=r.knownModels||[]; + S.models=r.models||{}; S.knownModels=r.knownModels||[]; S.modelListError=r.modelListError||''; renderAuth(); buildModelDropdowns(); }catch(e){ toast((lang==='en'?'Refresh failed: ':'刷新失败: ')+e.message,'error'); } } diff --git a/start.bat b/start.bat index 09f616e..18aa7d1 100644 --- a/start.bat +++ b/start.bat @@ -1,6 +1,6 @@ @echo off :: ================================================================ -:: OpenClaw Manager v0.6.0 — Start Script (Windows) +:: OpenClaw Manager v0.7.1 — Start Script (Windows) :: :: Features: :: * Auto-detect Node.js, show install instructions if missing @@ -19,7 +19,7 @@ set "MIN_NODE_MAJOR=18" echo. echo =================================== -echo OpenClaw Manager v0.6.0 +echo OpenClaw Manager v0.7.1 echo ----------------------------------- echo. diff --git a/start.sh b/start.sh index 23ced45..16963da 100755 --- a/start.sh +++ b/start.sh @@ -1,6 +1,6 @@ #!/usr/bin/env bash # ================================================================ -# OpenClaw Manager v0.6.0 — Start Script (macOS / Linux) +# OpenClaw Manager v0.7.1 — Start Script (macOS / Linux) # # Features: # - Auto-detect Node.js, show install instructions if missing @@ -22,7 +22,7 @@ CYAN='\033[0;36m'; BOLD='\033[1m'; DIM='\033[2m'; RESET='\033[0m' banner() { echo "" - echo -e "${CYAN}${BOLD} 🦀 OpenClaw Manager v0.6.0${RESET}" + echo -e "${CYAN}${BOLD} 🦀 OpenClaw Manager v0.7.1${RESET}" echo -e "${DIM} ─────────────────────────────${RESET}" echo "" }