From 69258d8428a2798e951b12f11d255f1703717319 Mon Sep 17 00:00:00 2001 From: dtzp555-max Date: Tue, 17 Mar 2026 20:17:01 +1000 Subject: [PATCH] feat: auto-load default Skills & Tools for new agents (#12) (#13) Add a Skills & Tools picker to the Agent/Sub-Agent creation forms so new agents automatically get useful defaults (memory-continuity, agent-workflow, execution-agent-dispatch, session-logs + fs/runtime/ memory/sessions_spawn/subagents tool groups). Users can expand the collapsible panel to customize, or leave it closed to accept defaults. - Frontend: checkbox grid with quick-action buttons (Default/All/None) - Backend: all 4 creation endpoints accept and persist skills/toolGroups - Bump version to v0.9.4; update DEVLOG, CHANGELOG, README, guides Co-authored-by: Tao Co-authored-by: Claude Opus 4.6 --- DEVLOG.md | 29 ++++++++- README.md | 3 +- docs/CHANGELOG.md | 5 ++ docs/USAGE_GUIDE.en.md | 4 +- docs/USAGE_GUIDE.zh-CN.md | 4 +- openclaw-manager.js | 127 +++++++++++++++++++++++++++++++++++--- 6 files changed, 158 insertions(+), 14 deletions(-) diff --git a/DEVLOG.md b/DEVLOG.md index 153d610..2eada5f 100644 --- a/DEVLOG.md +++ b/DEVLOG.md @@ -1,10 +1,35 @@ # OpenClaw Manager — 开发日志 -> 最后更新:2026-03-01 -> 当前版本:v0.9.0 +> 最后更新:2026-03-17 +> 当前版本:v0.9.4 --- +## v0.9.4 更新日志(2026-03-17) + +### New Features + +- **Skills & Tools 自动配置**:创建 Agent / Sub-Agent 时自动加载默认 Skills 和 Tool Groups + - 默认 Skills:`memory-continuity`, `agent-workflow`, `execution-agent-dispatch`, `session-logs` + - 默认 Tool Groups:`group:fs`, `group:runtime`, `group:memory`, `sessions_spawn`, `subagents` + - 创建表单新增可折叠的 Skills & Tools 选择面板,支持 checkbox 多选 + - 提供「仅默认」「全选」「清空」快捷按钮 + - 不展开 / 不修改则自动使用默认预设,开箱即用 +- 支持的可选 Skills:browser-use, github, gh-issues, coding-agent, execution-agent-planner, discord, weather, summarize, healthcheck +- 四个创建端点(Telegram Agent/Sub、Discord Agent/Sub)均支持 `skills` + `toolGroups` 字段 + +### Backend + +- 新增 `applySkillsTools()` 服务端辅助函数,统一处理 skills/tools 的默认值回退逻辑 +- `POST /api/agents/bot`、`POST /api/agents/discord`、`POST /api/agents/discord-sub`、`POST /api/agents` 均已适配 + +### UI + +- Skills & Tools Picker:深色风格 checkbox 网格,嵌入创建表单底部 +- 新增专用 CSS 样式(`.skills-picker`、`.sp-grid`、`.sp-item`、`.sp-actions`) +- 新增 i18n 字符串支持中英文标签 + +--- ## v0.9.0 更新日志(2026-03-01) diff --git a/README.md b/README.md index 7d8bbc1..403e345 100644 --- a/README.md +++ b/README.md @@ -156,7 +156,7 @@ That is why OCM should be thought of as a **control panel for an existing OpenCl ## Features by area -- **Agent Management** — Add main agents and sub-agents through a guided setup flow. View all agents in a tree structure with model selection, workspace browsing, and inline configuration. +- **Agent Management** — Add main agents and sub-agents through a guided setup flow. View all agents in a tree structure with model selection, workspace browsing, and inline configuration. New agents automatically get default Skills and Tool Groups, or you can customize them via the built-in Skills & Tools picker. - **Usage Statistics** — Real token usage data parsed directly from OpenClaw session files. Breakdown by model, agent, and day with a visual chart. - **Model & Auth** — Configure models from your registered provider list. Follow built-in guides for provider authentication setup. - **Built-in CLI** — Available from any page (top-right **Terminal** + bottom dock). Run OpenClaw commands with **real-time streaming output**, **Tab completion**, built-in presets, and your own saved favorites. @@ -166,6 +166,7 @@ That is why OCM should be thought of as a **control panel for an existing OpenCl ## What's New (v0.9.x) +- **Skills & Tools auto-config (v0.9.4)**: new agents/sub-agents automatically get default Skills (memory-continuity, agent-workflow, execution-agent-dispatch, session-logs) and Tool Groups (fs, runtime, memory, sessions_spawn, subagents). A collapsible Skills & Tools picker in the creation form lets you customize per agent. - **Discord support (v0.9.x)**: add agent/sub-agent flows for Discord (main agent binds channel; sub-agent binds thread). - **README screenshots gallery**: screenshots grouped into a collapsible section so the homepage stays shorter. - **Built-in CLI terminal**: run OpenClaw commands from any page with streaming output, presets, favorites, and Tab completion. diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 28bc2ff..6da0cc1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -2,6 +2,11 @@ This is a user-facing summary derived from `DEVLOG.md`. +## v0.9.4 +- **Skills & Tools auto-config**: New agents/sub-agents now automatically get default Skills (memory-continuity, agent-workflow, execution-agent-dispatch, session-logs) and Tool Groups (fs, runtime, memory, sessions_spawn, subagents). +- Agent creation forms include a collapsible Skills & Tools picker with checkbox multi-select and quick-action buttons (Default / All / None). +- All 4 creation endpoints (Telegram/Discord × Agent/Sub-Agent) support `skills` and `toolGroups` fields. + ## v0.9.0 - Agents: Telegram + Discord support in Add Agent / Add Sub-Agent wizard (main agent binds channel; sub-agent binds thread). - UI: top-center OCM version badge; improved Telegram/Discord setup guides. diff --git a/docs/USAGE_GUIDE.en.md b/docs/USAGE_GUIDE.en.md index ef0d8d8..cdb1039 100644 --- a/docs/USAGE_GUIDE.en.md +++ b/docs/USAGE_GUIDE.en.md @@ -138,8 +138,9 @@ Click `+ Add Agent` and fill: - workspace path (recommended: separate folder per agent) - model (dropdown is sourced from `openclaw models list`) - the BotFather token +- **Skills & Tools** (optional): expand the collapsible picker at the bottom to customize. By default, new agents get: `memory-continuity`, `agent-workflow`, `execution-agent-dispatch`, `session-logs` as Skills, and `group:fs`, `group:runtime`, `group:memory`, `sessions_spawn`, `subagents` as Tool Groups. Use the quick-action buttons (Default / All / None) to adjust. -OCM writes the agent + Telegram account/binding into `openclaw.json`. +OCM writes the agent + Telegram account/binding + skills + tool groups into `openclaw.json`. --- @@ -176,6 +177,7 @@ Send a message in the new group. In logs you should see something like: - paste the Group ID - set workspace (recommended: its own folder) - choose model +- **Skills & Tools** (optional): same picker as Add Agent — defaults are pre-selected, expand to customize **Step 4 — Allowlist (optional but recommended)** diff --git a/docs/USAGE_GUIDE.zh-CN.md b/docs/USAGE_GUIDE.zh-CN.md index b19588a..c1a024b 100644 --- a/docs/USAGE_GUIDE.zh-CN.md +++ b/docs/USAGE_GUIDE.zh-CN.md @@ -123,8 +123,9 @@ bash start.sh - workspace 路径(建议独立目录) - 选择模型(下拉来自 `openclaw models list`,更不容易选错) - 填入刚拿到的 bot token +- **Skills & Tools**(可选):展开表单底部的折叠面板可自定义。默认预设:Skills 包含 `memory-continuity`、`agent-workflow`、`execution-agent-dispatch`、`session-logs`;Tool Groups 包含 `group:fs`、`group:runtime`、`group:memory`、`sessions_spawn`、`subagents`。提供「仅默认 / 全选 / 清空」快捷按钮。 -保存后,OCM 会把这颗主 Agent 写进 `openclaw.json`,并把 bot token 绑定到对应 `accountId`。 +保存后,OCM 会把主 Agent + skills + tools 写进 `openclaw.json`,并把 bot token 绑定到对应 `accountId`。 --- @@ -159,6 +160,7 @@ openclaw gateway logs --follow - 填 Group ID - workspace(建议单独目录) - 选择模型 +- **Skills & Tools**(可选):和 Add Agent 相同的选择器,默认预设已勾选,展开可自定义 #### Step 4:写 allowlist(推荐) 如果你启用了 Telegram 的 allowlist(例如 `channels.telegram.allowFrom`), diff --git a/openclaw-manager.js b/openclaw-manager.js index d17ceef..457f240 100644 --- a/openclaw-manager.js +++ b/openclaw-manager.js @@ -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.9.3'; +const APP_VERSION = '0.9.4'; // --port 参数 const portIdx = process.argv.indexOf('--port'); if (portIdx !== -1 && process.argv[portIdx + 1]) PORT = parseInt(process.argv[portIdx + 1]) || 3333; @@ -63,6 +63,17 @@ function refreshPaths() { CONFIG_PATH = path.join(OPENCLAW_DIR, 'openclaw.json'); } +// ── Default Skills & Tool Groups for new agents ───────────── +const DEFAULT_SKILLS = ['memory-continuity', 'agent-workflow', 'execution-agent-dispatch', 'session-logs']; +const DEFAULT_TOOL_GROUPS = ['group:fs', 'group:runtime', 'group:memory', 'sessions_spawn', 'subagents']; + +function applySkillsTools(agentEntry, skills, toolGroups) { + const s = (Array.isArray(skills) && skills.length > 0) ? skills : DEFAULT_SKILLS; + const tg = (Array.isArray(toolGroups) && toolGroups.length > 0) ? toolGroups : DEFAULT_TOOL_GROUPS; + agentEntry.skills = s; + agentEntry.tools = { alsoAllow: tg }; +} + // ── 已知模型列表 ────────────────────────────────────────────── const KNOWN_MODELS = [ { id: '__default__', label: '使用全局默认模型' }, @@ -461,7 +472,7 @@ async function handleApi(req, res, urlObj, body) { // POST /api/agents/bot — create agent with its own bot token if (method === 'POST' && pathname === '/api/agents/bot') { - const { botToken, agentId, name, model, workspace, purpose, personality } = body; + const { botToken, agentId, name, model, workspace, purpose, personality, skills, toolGroups } = body; if (!botToken || !botToken.trim()) { res.writeHead(400); res.end(JSON.stringify({ error: 'Bot Token is required' })); return; } @@ -507,6 +518,7 @@ async function handleApi(req, res, urlObj, body) { const wsAlias = `~/.openclaw/workspaces/${workspace}`; const agentEntry = { id: agentId, name: name || agentId, workspace: wsAlias }; if (model && model !== '__default__') agentEntry.model = { primary: model }; + applySkillsTools(agentEntry, skills, toolGroups); cfg.agents.list.push(agentEntry); // Add binding if (!cfg.bindings) cfg.bindings = []; @@ -540,7 +552,7 @@ async function handleApi(req, res, urlObj, body) { // POST /api/agents/discord — create top-level Discord agent (single bot, channel binding) if (method === 'POST' && pathname === '/api/agents/discord') { - const { agentId, name, workspaceFolder, model, purpose, personality, guildId, channelId } = body; + const { agentId, name, workspaceFolder, model, purpose, personality, guildId, channelId, skills, toolGroups } = body; if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) { res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return; } @@ -566,6 +578,7 @@ async function handleApi(req, res, urlObj, body) { const wsPath = path.join(OPENCLAW_DIR, 'workspaces', folder); const agentEntry = { id: agentId, name: name || agentId, workspace: wsAlias }; if (model && model !== '__default__') agentEntry.model = { primary: model }; + applySkillsTools(agentEntry, skills, toolGroups); cfg.agents.list.push(agentEntry); if (!cfg.bindings) cfg.bindings = []; @@ -609,7 +622,7 @@ async function handleApi(req, res, urlObj, body) { // POST /api/agents/discord-sub — create Discord sub-agent (thread-only binding, grouping under parentAgentId) if (method === 'POST' && pathname === '/api/agents/discord-sub') { - const { agentId, displayName, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, guildId, threadId } = body; + const { agentId, displayName, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, guildId, threadId, skills, toolGroups } = body; if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) { res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return; } @@ -635,6 +648,7 @@ async function handleApi(req, res, urlObj, body) { const wsPath = path.join(OPENCLAW_DIR, 'workspaces', folder); const agentEntry = { id: agentId, name: displayName || agentId, workspace: wsAlias, parentAgentId: String(parentAgentId).trim() }; if (model && model !== '__default__') agentEntry.model = { primary: model }; + applySkillsTools(agentEntry, skills, toolGroups); cfg.agents.list.push(agentEntry); if (!cfg.bindings) cfg.bindings = []; @@ -679,7 +693,7 @@ async function handleApi(req, res, urlObj, body) { // POST /api/agents — create sub-agent (shares parent bot) if (method === 'POST' && pathname === '/api/agents') { - const { agentId, displayName, groupId, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, telegramUserId } = body; + const { agentId, displayName, groupId, workspaceFolder, model, purpose, personality, initialMemory, parentAgentId, telegramUserId, skills, toolGroups } = body; if (!agentId || !/^[a-zA-Z0-9_-]+$/.test(agentId)) { res.writeHead(400); res.end(JSON.stringify({ error: 'Agent ID must contain only alphanumeric characters, underscores, or dashes' })); return; } @@ -708,6 +722,7 @@ async function handleApi(req, res, urlObj, body) { const wsAlias= `~/.openclaw/workspaces/${folder}`; const agentEntry = { id: agentId, name: displayName || agentId, workspace: wsAlias, parentAgentId: String(parentAgentId).trim() }; if (model && model !== '__default__') agentEntry.model = { primary: model }; + applySkillsTools(agentEntry, skills, toolGroups); cfg.agents.list.push(agentEntry); // Binding uses parent's accountId const parentAccountId = parentBinding.match.accountId; @@ -1726,6 +1741,19 @@ header { background:var(--surface); border-bottom:1px solid var(--border); paddi .lang-toggle { background:var(--border); border:none; color:var(--muted); border-radius:6px; padding:5px 10px; font-size:12px; cursor:pointer; } .lang-toggle:hover { background:#3a3f5c; color:var(--text); } +/* skills/tools picker */ +.skills-picker { background:var(--bg); border:1px solid var(--border); border-radius:10px; padding:12px 14px; margin-top:4px; } +.skills-picker summary { font-size:13px; font-weight:600; cursor:pointer; color:var(--text); user-select:none; } +.skills-picker summary:hover { color:var(--accent); } +.skills-picker .sp-grid { display:grid; grid-template-columns:1fr 1fr; gap:4px 16px; margin-top:8px; } +.skills-picker label.sp-item { display:flex; align-items:center; gap:6px; font-size:12.5px; color:var(--muted); cursor:pointer; padding:3px 0; } +.skills-picker label.sp-item:hover { color:var(--text); } +.skills-picker label.sp-item input[type=checkbox] { accent-color:var(--accent); width:14px; height:14px; } +.skills-picker .sp-actions { display:flex; gap:8px; margin-top:8px; padding-top:8px; border-top:1px solid var(--border); } +.skills-picker .sp-actions button { font-size:11px; padding:3px 10px; border-radius:6px; border:1px solid var(--border); background:var(--surface); color:var(--muted); cursor:pointer; } +.skills-picker .sp-actions button:hover { color:var(--text); border-color:var(--accent); } +.sp-section-title { font-size:11px; font-weight:700; color:var(--accent); text-transform:uppercase; letter-spacing:.5px; margin:8px 0 4px; grid-column:1/-1; } + /* centered version badge */ .top-version{ position:absolute; left:50%; transform:translateX(-50%); font-size:12px; font-weight:800; letter-spacing:.3px; color:var(--text); background:rgba(108,99,255,.18); border:1px solid rgba(108,99,255,.45); padding:4px 12px; border-radius:999px; box-shadow:0 6px 18px rgba(0,0,0,.25); } .ver-old{ display:none; } @@ -2564,6 +2592,9 @@ const I18N = { 'wiz.soul':'性格关键词','wiz.soulHint':'(逗号分隔,选填)','wiz.soulPh':'幽默、直接、有条理...', 'wiz.soulTip':'留空则使用默认成长型提示词(推荐)', 'wiz.memory':'初始记忆','wiz.memoryHint':'(MEMORY.md,选填)','wiz.memoryPh':'例如:群组主要用中文交流。用户偏好简洁回复。', + 'wiz.skillsTools':'Skills & Tools 配置','wiz.skillsHint':'不选择则使用默认预设(推荐)', + 'wiz.skillsSection':'Skills(能力)','wiz.toolsSection':'Tool Groups(权限)', + 'wiz.spSelectDefault':'仅默认','wiz.spSelectAll':'全选','wiz.spSelectNone':'清空', 'wiz.preview':'即将创建:','wiz.group':'群组','wiz.modelLabel':'模型','wiz.globalDefault':'全局默认', 'wiz.soulYes':'含性格关键词','wiz.soulDefault':'默认成长型提示词(推荐)', 'wiz.autoBackup':'自动备份当前 openclaw.json ✓', @@ -2984,6 +3015,78 @@ function showAddForm(type) { applyLang(); } +// ── Skills / Tools picker helper ────────────────────────── +const AVAILABLE_SKILLS = [ + { id:'memory-continuity', label:'Memory Continuity', isDefault:true }, + { id:'agent-workflow', label:'Agent Workflow', isDefault:true }, + { id:'execution-agent-dispatch', label:'Execution Dispatch', isDefault:true }, + { id:'session-logs', label:'Session Logs', isDefault:true }, + { id:'browser-use', label:'Browser Use', isDefault:false }, + { id:'github', label:'GitHub', isDefault:false }, + { id:'gh-issues', label:'GitHub Issues', isDefault:false }, + { id:'coding-agent', label:'Coding Agent', isDefault:false }, + { id:'execution-agent-planner', label:'Execution Planner', isDefault:false }, + { id:'discord', label:'Discord', isDefault:false }, + { id:'weather', label:'Weather', isDefault:false }, + { id:'summarize', label:'Summarize', isDefault:false }, + { id:'healthcheck', label:'Healthcheck', isDefault:false }, +]; +const AVAILABLE_TOOL_GROUPS = [ + { id:'group:fs', label:'Filesystem (group:fs)', isDefault:true }, + { id:'group:runtime', label:'Runtime (group:runtime)', isDefault:true }, + { id:'group:memory', label:'Memory (group:memory)', isDefault:true }, + { id:'sessions_spawn', label:'Sessions Spawn', isDefault:true }, + { id:'subagents', label:'Subagents', isDefault:true }, +]; + +function buildSkillsToolsPicker(prefix) { + let html = '
' + t('wiz.skillsTools') + ' ' + t('wiz.skillsHint') + ''; + html += '
' + t('wiz.skillsSection') + '
'; + html += '
'; + AVAILABLE_SKILLS.forEach(s => { + html += ''; + }); + html += '
'; + html += '
' + t('wiz.toolsSection') + '
'; + html += '
'; + AVAILABLE_TOOL_GROUPS.forEach(g => { + html += ''; + }); + html += '
'; + html += '
'; + html += ''; + html += ''; + html += ''; + html += '
'; + return html; +} + +function spAction(prefix, action) { + const wrap = document.getElementById(prefix+'-sp-wrap'); + if (!wrap) return; + const boxes = wrap.querySelectorAll('input[type=checkbox]'); + if (action==='none') { boxes.forEach(b=>b.checked=false); return; } + if (action==='all') { boxes.forEach(b=>b.checked=true); return; } + // default + const defSkills = AVAILABLE_SKILLS.filter(s=>s.isDefault).map(s=>s.id); + const defTools = AVAILABLE_TOOL_GROUPS.filter(g=>g.isDefault).map(g=>g.id); + boxes.forEach(b=>{ + if (b.dataset.spType==='skill') b.checked = defSkills.includes(b.value); + else b.checked = defTools.includes(b.value); + }); +} + +function collectSkillsTools(prefix) { + const wrap = document.getElementById(prefix+'-sp-wrap'); + if (!wrap) return { skills:[], toolGroups:[] }; + const skills = []; const toolGroups = []; + wrap.querySelectorAll('input[type=checkbox]:checked').forEach(b=>{ + if (b.dataset.spType==='skill') skills.push(b.value); + else toolGroups.push(b.value); + }); + return { skills, toolGroups }; +} + function buildAddAgentForm() { const modelOpts = buildModelOpts('__default__'); return '
' + @@ -3041,6 +3144,7 @@ function buildAddAgentForm() { '
' + '' + '' + t('wiz.soulTip') + '
' + + '
' + buildSkillsToolsPicker('fa') + '
' + '
' + '' + '' + @@ -3136,6 +3240,7 @@ function buildAddSubForm() { '
' + '
' + '
' + + '
' + buildSkillsToolsPicker('fs') + '
' + '
' + '' + @@ -3169,18 +3274,20 @@ async function submitAddAgent() { if (!name) { toast(t('agents.errName'), 'err'); return; } if (!workspace) { toast('Workspace folder is required', 'err'); return; } + const { skills, toolGroups } = collectSkillsTools('fa'); + try { if (channel === 'telegram') { const token = document.getElementById('fa-token').value.trim(); if (!token) { toast(t('agents.errToken'), 'err'); return; } - const payload = { botToken: token, agentId, name, workspace, model: model === '__default__' ? '' : model, purpose, personality }; + const payload = { botToken: token, agentId, name, workspace, model: model === '__default__' ? '' : model, purpose, personality, skills, toolGroups }; await api('POST', '/api/agents/bot', payload); } else { const link = (document.getElementById('fa-discord-link')?.value||'').trim(); const parsed = parseDiscordLinkOrId(link); if (!parsed) { toast('Discord channel link/ID is required', 'err'); return; } const payload = { agentId, name, workspaceFolder: workspace, model: model === '__default__' ? '' : model, purpose, personality, - guildId: parsed.guildId, channelId: parsed.channelId }; + guildId: parsed.guildId, channelId: parsed.channelId, skills, toolGroups }; await api('POST', '/api/agents/discord', payload); } toast('Agent created successfully', 'ok'); @@ -3209,6 +3316,8 @@ async function submitAddSub() { if (!/^[a-zA-Z0-9_-]+$/.test(agentId)) { toast(t('wiz.errIdFormat'), 'err'); return; } if (!workspaceFolder) { toast('Workspace folder is required', 'err'); return; } + const { skills, toolGroups } = collectSkillsTools('fs'); + try { if (channel === 'telegram') { const groupId = document.getElementById('fs-gid').value.trim(); @@ -3216,14 +3325,14 @@ async function submitAddSub() { if (!groupId) { toast(t('wiz.errGroupId'), 'err'); return; } if (telegramUserId && !/^\d+$/.test(telegramUserId)) { toast('Telegram User ID must be a number', 'err'); return; } await api('POST', '/api/agents', { parentAgentId, agentId, displayName: displayName || agentId, groupId, - workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory, telegramUserId }); + workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory, telegramUserId, skills, toolGroups }); } else { const link = (document.getElementById('fs-discord-link')?.value||'').trim(); const parsed = parseDiscordLinkOrId(link); if (!parsed) { toast('Discord thread link/ID is required', 'err'); return; } await api('POST', '/api/agents/discord-sub', { parentAgentId, agentId, displayName: displayName || agentId, workspaceFolder, model: model === '__default__' ? '' : model, purpose, personality, initialMemory, - guildId: parsed.guildId, threadId: parsed.channelId }); + guildId: parsed.guildId, threadId: parsed.channelId, skills, toolGroups }); } toast(t('wiz.created'), 'ok'); clearAddForm();