Author SHA1 Message Date
15798e007d fix: orphan agents now grouped under main in agent tree
Agents without parentAgentId, own bot, or binding-based inference
(e.g. dynamically created sub-agents like codex-worker) were
incorrectly displayed as top-level roots. They now default to
main's subtree.

Bump version to 0.9.4.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-19 21:28:13 +10:00
d3fdbcef6a fix: improve agent tree ordering and parent inference (#15)
Root agents with their own bot (hasOwnBot) now sort to the top of the
agent tree alongside main. Agents listed in a parent's
subagents.allowAgents are automatically inferred as children even
without an explicit parentAgentId or Telegram binding.

Co-authored-by: Tao <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:33:26 +10:00
16a080e0b2 fix: add English i18n keys for Skills & Tools picker (#14)
The Skills & Tools picker added in v0.9.4 only had Chinese i18n
strings, so the panel showed Chinese text even in English mode.
Add English translations for wiz.skillsTools, wiz.skillsHint,
wiz.skillsSection, wiz.toolsSection, and the quick-action buttons.

Co-authored-by: Tao <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:21:24 +10:00
69258d8428 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 <dtzp555@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-17 20:17:01 +10:00
a371ba6f7c docs: add related projects links (#12)
Co-authored-by: dtzp555 <dtzp555@gmail.com>
2026-03-08 16:09:47 +10:00
8ae5fdb20c bootstrap: create CURRENT_STATE for new agents (#11)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-08 11:30:36 +10:00
c3ed4c9c8f ui: add CLI copy helper + bump version to 0.9.2 (#10)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-08 10:20:34 +10:00
0a5f963b14 ui: add Dashboard first-run checklist card (#9)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-08 07:52:03 +10:00
9e3ec7b616 docs: switch README screenshots to hero layout A (#8)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-07 20:32:31 +10:00
55bfdcdbc8 ui: make CLI completion messages English (#7)
Co-authored-by: Tao <dtzp555@gmail.com>
2026-03-07 20:29:28 +10:00
7 changed files with 266 additions and 32 deletions
+27 -2
View File
@@ -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 多选
- 提供「仅默认」「全选」「清空」快捷按钮
- 不展开 / 不修改则自动使用默认预设,开箱即用
- 支持的可选 Skillsbrowser-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
+15 -5
View File
@@ -56,10 +56,13 @@ OCM gives you a **local control panel** for the things that become painful first
These are the highest-value surfaces for first-time users:
<p>
<img src="docs/redacted-screenshots-refresh/dashboard-2026-03-07.jpg" width="240" />
<img src="docs/redacted-screenshots-refresh/agents-2026-03-07.jpg" width="240" />
<img src="docs/redacted-screenshots/actions.jpg" width="240" />
<img src="docs/redacted-screenshots-refresh/cli-2026-03-07.jpg" width="240" />
<img src="docs/redacted-screenshots-refresh/dashboard-2026-03-07.jpg" width="860" />
</p>
<p>
<img src="docs/redacted-screenshots-refresh/agents-2026-03-07.jpg" width="280" />
<img src="docs/redacted-screenshots-refresh/cli-2026-03-07.jpg" width="280" />
<img src="docs/redacted-screenshots/actions.jpg" width="180" />
</p>
More screenshots: see the [full usage guide](docs/USAGE_GUIDE.en.md) or the gallery below.
@@ -153,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.
@@ -163,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.
@@ -276,6 +280,12 @@ ocm/
- **Frontend**: Vanilla HTML/CSS/JS embedded in a template literal — no build step, no framework
- **Architecture**: Single-file, zero-dependency, runs anywhere Node.js runs
## Related Projects
- [clawkeeper](https://github.com/dtzp555-max/clawkeeper) — OpenClaw memory operations kit for doctor / provider switch / backup / restore workflows.
- [execution-agent-planner](https://github.com/dtzp555-max/execution-agent-planner) — skill for deciding when work should stay with one execution agent or split across specialized workers.
- [gh-pr-release-flow](https://github.com/dtzp555-max/gh-pr-release-flow) — skill for PR-first GitHub repos where release work should happen after merge.
## License
MIT
+5
View File
@@ -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.
+3 -1
View File
@@ -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)**
+3 -1
View File
@@ -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`),
+212 -22
View File
@@ -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.0';
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: '使用全局默认模型' },
@@ -315,6 +326,28 @@ ${hasMemory ? initialMemory : '> 暂无初始记录。记忆将通过日常对
`;
}
function generateCurrentStateMd() {
return `# CURRENT_STATE
_Last updated: TBD Australia/Brisbane_
## In Flight
- none
## Blocked / Waiting
- none
## Recently Finished
- none
## Next
- none
## Reset Summary
- No active summary yet.
`;
}
// ── 请求解析 ──────────────────────────────────────────────────
function parseBody(req) {
return new Promise((resolve, reject) => {
@@ -395,6 +428,12 @@ async function handleApi(req, res, urlObj, body) {
);
// For main agent without explicit binding, infer its accountId from first unclaimed telegram account
const mainInferredAccountId = telegramAccounts.find(a => !claimedAccounts.has(a)) || telegramAccounts[0] || null;
// Build map: agentId -> parentAgentId inferred from subagents.allowAgents
const allowAgentsParentMap = {};
list.forEach(parent => {
const allowed = parent.subagents?.allowAgents || [];
allowed.forEach(childId => { if (!allowAgentsParentMap[childId]) allowAgentsParentMap[childId] = parent.id; });
});
const enriched = list.map(a => {
const binding = bindings.find(b => b.agentId === a.id && b.match?.peer?.kind === 'group');
const groupId = binding?.match?.peer?.id || null;
@@ -424,7 +463,9 @@ async function handleApi(req, res, urlObj, body) {
// Workspace: explicit per-agent, or defaults.workspace for main
const workspace = a.workspace || (isMain ? defaultWorkspace : null);
return { ...a, workspace, groupId, requireMention: groupId ? (groups[groupId]?.requireMention ?? true) : null,
effectiveModel: modelVal || defaults.model?.primary || '默认', hasOwnBot, accountId, parentAccountId, parentAgentId: a.parentAgentId || null, bindings: (bindings||[]).filter(b=>b.agentId===a.id).map(b=>({
effectiveModel: modelVal || defaults.model?.primary || '默认', hasOwnBot, accountId, parentAccountId,
parentAgentId: a.parentAgentId || (!hasOwnBot ? allowAgentsParentMap[a.id] : null) || null,
bindings: (bindings||[]).filter(b=>b.agentId===a.id).map(b=>({
idx: bindings.indexOf(b),
channel: b.match?.channel || '',
accountId: b.match?.accountId || '',
@@ -439,7 +480,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;
}
@@ -485,6 +526,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 = [];
@@ -497,6 +539,7 @@ async function handleApi(req, res, urlObj, body) {
const agentName = name || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(agentName, purpose || '', personality || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(agentName, ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
// Create agents/<id>/ runtime directory (required by OpenClaw gateway)
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -506,7 +549,7 @@ async function handleApi(req, res, urlObj, body) {
ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [
'Agent created with its own bot token',
'Workspace directory created with SOUL.md and MEMORY.md',
'Workspace directory created with SOUL.md, MEMORY.md, and memory/CURRENT_STATE.md',
'Runtime directory created at agents/' + agentId + '/',
'Configuration updated and backed up',
'Restart gateway to load new bot: openclaw gateway restart'
@@ -517,7 +560,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;
}
@@ -543,6 +586,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 = [];
@@ -565,6 +609,7 @@ async function handleApi(req, res, urlObj, body) {
await fsp.mkdir(path.join(wsPath, 'memory'), { recursive: true });
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name || agentId, purpose || '', personality || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name || agentId, ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -575,7 +620,7 @@ async function handleApi(req, res, urlObj, body) {
notes: [
'Discord agent created (single Discord bot)',
'Channel binding added',
'Workspace + runtime directories created',
'Workspace + runtime directories created (including memory/CURRENT_STATE.md)',
'Configuration updated and backed up',
'Restart gateway to apply: openclaw gateway restart'
]
@@ -585,7 +630,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;
}
@@ -611,6 +656,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 = [];
@@ -634,6 +680,7 @@ async function handleApi(req, res, urlObj, body) {
const name = displayName || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose || '', personality || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory || ''), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -643,7 +690,7 @@ async function handleApi(req, res, urlObj, body) {
res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [
'Discord sub-agent created (thread-only binding)',
'Workspace + runtime directories created',
'Workspace + runtime directories created (including memory/CURRENT_STATE.md)',
'Configuration updated and backed up',
'Restart gateway to apply: openclaw gateway restart'
]
@@ -654,7 +701,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;
}
@@ -683,6 +730,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;
@@ -707,6 +755,7 @@ async function handleApi(req, res, urlObj, body) {
const name = displayName || agentId;
await fsp.writeFile(path.join(wsPath, 'SOUL.md'), generateSoulMd(name, purpose, personality), 'utf8');
await fsp.writeFile(path.join(wsPath, 'MEMORY.md'), generateMemoryMd(name, initialMemory), 'utf8');
await fsp.writeFile(path.join(wsPath, 'memory', 'CURRENT_STATE.md'), generateCurrentStateMd(), 'utf8');
// Create agents/<id>/ runtime directory (required by OpenClaw gateway)
const agentRuntimeDir = path.join(OPENCLAW_DIR, 'agents', agentId);
await fsp.mkdir(agentRuntimeDir, { recursive: true });
@@ -715,7 +764,7 @@ async function handleApi(req, res, urlObj, body) {
res.end(JSON.stringify({ ok: true, agentId, workspacePath: wsPath, configBackup: bakPath,
notes: [
'Sub-agent created and shares parent bot',
'Workspace and runtime directories created',
'Workspace and runtime directories created (including memory/CURRENT_STATE.md)',
'Configuration updated and backed up',
'Restart gateway to apply: openclaw gateway restart'
],
@@ -1700,6 +1749,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; }
@@ -2030,6 +2092,16 @@ const MAIN_HTML_BODY = String.raw`
</div>
</div>
<div class="dash-gauges" id="dashGauges"><div class="empty">Loading...</div></div>
<div class="card dash-notice">
<h3 data-i18n="dash.firstRunTitle">🚀 First-run checklist</h3>
<ul class="dash-note-list">
<li data-i18n="dash.firstRun1">Confirm OCM is pointed at the correct OpenClaw directory.</li>
<li data-i18n="dash.firstRun2">Check this Dashboard first: make sure Gateway is running and the system looks healthy.</li>
<li data-i18n="dash.firstRun3">Open Agents / Routing and confirm the bindings you expect are actually there.</li>
<li data-i18n="dash.firstRun4">Use the built-in Terminal to run <code>openclaw doctor</code> once so you catch environment/auth issues early.</li>
<li data-i18n="dash.firstRun5">Before bigger edits, make a backup or check rollback so recovery is nearby if something goes wrong.</li>
</ul>
</div>
<div class="dash-sections">
<div class="card dash-card" id="dashSystem">
<h3>🖥 System Info</h3>
@@ -2175,6 +2247,7 @@ const MAIN_HTML_BODY = String.raw`
<select id="cliPreset" onchange="onCliPresetSelect()" style="font-size:11px;padding:3px 6px;max-width:180px;background:var(--bg);border:1px solid var(--border);border-radius:6px;color:var(--text)">
<option value="" data-i18n="cli.presets"> 常用命令 </option>
</select>
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="copyCliCommand()" data-i18n="cli.copy">复制命令</button>
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="clearCliOutput()" data-i18n="cli.clear">清空</button>
<button class="btn-secondary" style="font-size:11px;padding:3px 10px" onclick="toggleCliPanel()" data-i18n="cli.collapse"> 收起</button>
</div>
@@ -2439,6 +2512,12 @@ const I18N = {
'models.onlyCliHint':'模型下拉仅显示 openclaw models list 返回的模型。',
'models.modelListErr':'读取 openclaw models list 失败:',
'models.modelListEmpty':'未从 openclaw models list 解析到模型。请先运行 openclaw onboard 并确认模型可用。',
'dash.firstRunTitle':'🚀 首次使用检查清单',
'dash.firstRun1':'先确认 OCM 指向的是正确的 OpenClaw 数据目录。',
'dash.firstRun2':'先看 Dashboard:确认 Gateway 正在运行,系统状态看起来正常。',
'dash.firstRun3':'打开 Agents / Routing,确认你期望的绑定确实已经存在。',
'dash.firstRun4':'用内置终端先运行一次 <code>openclaw doctor</code>,尽早发现环境或认证问题。',
'dash.firstRun5':'在做较大改动前,先做一次备份或确认回滚入口,出问题时更容易恢复。',
'dash.noticeTitle':'📌 Telegram 使用说明(重要)',
'dash.notice1':'OCM 主要面向 Telegram:通过群组绑定 Agent,让每个 Agent 拥有独立 Workspace / SOUL.md / MEMORY.md。',
'dash.notice2':'请确保你已有基本 OpenClaw 操作经验。OCM 主要负责可视化更新 openclaw.json,方便增删主 Agent 与 Sub-Agent,并支持多条 Agent 树。',
@@ -2521,6 +2600,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 ✓',
@@ -2534,7 +2616,7 @@ const I18N = {
'ch.peerId':'Peer ID','ch.peerIdHint':'(群组 ID 或用户 ID','ch.peerIdTip':'留空则匹配所有对应类型的 Peer',
'ch.submit':'添加绑定',
'l.sub':'选择运行模式',
'cli.open':'⌨️ 终端','cli.title':'⌨️ CLI 终端','cli.clear':'清空','cli.collapse':'▼ 收起',
'cli.open':'⌨️ 终端','cli.title':'⌨️ CLI 终端','cli.copy':'复制命令','cli.clear':'清空','cli.collapse':'▼ 收起',
'cli.ready':'── 终端就绪,等待命令 ──','cli.cleared':'── 已清空 ──',
'cli.presets':'── 常用命令 ──','cli.builtins':'内置命令','cli.favs':'我的收藏',
'cli.run':'▶ 执行','cli.stop':'■ 停止','cli.star':'⭐','cli.manage':'管理',
@@ -2573,6 +2655,12 @@ const I18N = {
'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.firstRunTitle':'🚀 First-run checklist',
'dash.firstRun1':'Confirm OCM is pointed at the correct OpenClaw directory.',
'dash.firstRun2':'Check Dashboard first: make sure Gateway is running and the system looks healthy.',
'dash.firstRun3':'Open Agents / Routing and confirm the bindings you expect are actually there.',
'dash.firstRun4':'Use the built-in Terminal to run <code>openclaw doctor</code> once so you catch environment or auth issues early.',
'dash.firstRun5':'Before bigger edits, make a backup or check rollback so recovery is nearby if something goes wrong.',
'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.',
@@ -2656,6 +2744,9 @@ const I18N = {
'wiz.soul':'Personality Keywords','wiz.soulHint':'(comma-separated, optional)','wiz.soulPh':'humorous, direct, organized...',
'wiz.soulTip':'Leave blank for default growth prompt (recommended)',
'wiz.memory':'Initial Memory','wiz.memoryHint':'(MEMORY.md, optional)','wiz.memoryPh':'e.g. Group mainly uses English. Users prefer concise replies.',
'wiz.skillsTools':'Skills & Tools','wiz.skillsHint':'Leave as-is for defaults (recommended)',
'wiz.skillsSection':'Skills','wiz.toolsSection':'Tool Groups',
'wiz.spSelectDefault':'Default','wiz.spSelectAll':'All','wiz.spSelectNone':'None',
'wiz.preview':'About to create:','wiz.group':'Group','wiz.modelLabel':'Model','wiz.globalDefault':'Global Default',
'wiz.soulYes':'With personality keywords','wiz.soulDefault':'Default growth prompt (recommended)',
'wiz.autoBackup':'Auto-backup current openclaw.json ✓',
@@ -2669,7 +2760,7 @@ const I18N = {
'ch.peerId':'Peer ID','ch.peerIdHint':'(Group ID or User ID)','ch.peerIdTip':'Leave blank to match all peers of this type',
'ch.submit':'Add Binding',
'l.sub':'Select Mode',
'cli.open':'⌨️ Terminal','cli.title':'⌨️ CLI Terminal','cli.clear':'Clear','cli.collapse':'▼ Collapse',
'cli.open':'⌨️ Terminal','cli.title':'⌨️ CLI Terminal','cli.copy':'Copy cmd','cli.clear':'Clear','cli.collapse':'▼ Collapse',
'cli.ready':'── Terminal Ready ──','cli.cleared':'── Cleared ──',
'cli.presets':'── Presets ──','cli.builtins':'Built-in','cli.favs':'My Favorites',
'cli.run':'▶ Run','cli.stop':'■ Stop','cli.star':'⭐','cli.manage':'Manage',
@@ -2935,6 +3026,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 = '<details class="skills-picker" id="'+prefix+'-sp-wrap"><summary>' + t('wiz.skillsTools') + ' <span style="color:var(--muted);font-weight:400;font-size:12px">' + t('wiz.skillsHint') + '</span></summary>';
html += '<div class="sp-section-title">' + t('wiz.skillsSection') + '</div>';
html += '<div class="sp-grid">';
AVAILABLE_SKILLS.forEach(s => {
html += '<label class="sp-item"><input type="checkbox" data-sp-type="skill" value="'+s.id+'" '+(s.isDefault?'checked':'')+'>'+s.label+'</label>';
});
html += '</div>';
html += '<div class="sp-section-title">' + t('wiz.toolsSection') + '</div>';
html += '<div class="sp-grid">';
AVAILABLE_TOOL_GROUPS.forEach(g => {
html += '<label class="sp-item"><input type="checkbox" data-sp-type="tool" value="'+g.id+'" '+(g.isDefault?'checked':'')+'>'+g.label+'</label>';
});
html += '</div>';
html += '<div class="sp-actions">';
html += '<button type="button" onclick="spAction(\\x27'+prefix+'\\x27,\\x27default\\x27)">' + t('wiz.spSelectDefault') + '</button>';
html += '<button type="button" onclick="spAction(\\x27'+prefix+'\\x27,\\x27all\\x27)">' + t('wiz.spSelectAll') + '</button>';
html += '<button type="button" onclick="spAction(\\x27'+prefix+'\\x27,\\x27none\\x27)">' + t('wiz.spSelectNone') + '</button>';
html += '</div></details>';
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 '<div class="add-form">' +
@@ -2992,6 +3155,7 @@ function buildAddAgentForm() {
'<div class="form-group"><label>' + t('wiz.soul') + ' <span style="color:var(--muted);font-weight:400">' + t('wiz.soulHint') + '</span></label>' +
'<input id="fa-soul" placeholder="' + t('wiz.soulPh') + '">' +
'<span class="hint-text">' + t('wiz.soulTip') + '</span></div>' +
'<div class="form-group">' + buildSkillsToolsPicker('fa') + '</div>' +
'<div style="display:flex;gap:8px;margin-top:14px">' +
'<button class="btn-primary" onclick="submitAddAgent()">' + t('agents.addAgentSubmit') + '</button>' +
'<button class="btn-ghost" onclick="clearAddForm()">' + t('btn.cancel') + '</button>' +
@@ -3087,6 +3251,7 @@ function buildAddSubForm() {
'<input id="fs-soul" placeholder="' + t('wiz.soulPh') + '"></div>' +
'<div class="form-group"><label>' + t('wiz.memory') + ' <span style="color:var(--muted);font-weight:400">' + t('wiz.memoryHint') + '</span></label>' +
'<textarea id="fs-mem" placeholder="' + t('wiz.memoryPh') + '" rows="2"></textarea></div>' +
'<div class="form-group">' + buildSkillsToolsPicker('fs') + '</div>' +
'<div style="display:flex;gap:8px;margin-top:14px">' +
'<button class="btn-primary" onclick="submitAddSub()">' + t('agents.addSubSubmit') + '</button>' +
@@ -3120,18 +3285,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');
@@ -3160,6 +3327,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();
@@ -3167,14 +3336,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();
@@ -3191,16 +3360,24 @@ function renderAgents() {
// Build grouping: prefer explicit parentAgentId; fallback to telegram bot-root grouping.
const byId = {}; (S.agents||[]).forEach(a=>{ byId[a.id]=a; });
// infer parentAgentId for legacy telegram sub-agents if missing
// infer parentAgentId for sub-agents if missing
const accountToRootId = {};
(S.agents||[]).forEach(a=>{ if(a.hasOwnBot && a.accountId) accountToRootId[a.accountId]=a.id; });
(S.agents||[]).forEach(a=>{
if(!a.parentAgentId && !a.hasOwnBot && a.parentAccountId && accountToRootId[a.parentAccountId]){
if(a.parentAgentId || a.hasOwnBot || a.id === 'main') return; // already resolved or is a root
// Case 1: legacy telegram sub-agent with parentAccountId
if(a.parentAccountId && accountToRootId[a.parentAccountId]){
a._inferParentAgentId = accountToRootId[a.parentAccountId];
return;
}
// Case 2: orphan agent (no own bot, no parentAgentId, no binding-based inference)
// These are likely dynamically created sub-agents — default to 'main'
if(byId['main']){
a._inferParentAgentId = 'main';
}
});
const roots=[]; const childrenByRoot={}; const orphans=[];
const roots=[]; const childrenByRoot={};
(S.agents||[]).forEach(a=>{
const pid = a.parentAgentId || a._inferParentAgentId || '';
if(pid && byId[pid]){
@@ -3209,6 +3386,12 @@ function renderAgents() {
roots.push(a);
}
});
// Sort roots: agents with own bot first (main always first among those), then orphans
roots.sort((a,b)=>{
if(a.id==='main') return -1; if(b.id==='main') return 1;
if(a.hasOwnBot && !b.hasOwnBot) return -1; if(!a.hasOwnBot && b.hasOwnBot) return 1;
return (a.name||a.id).localeCompare(b.name||b.id);
});
function fmtBinding(b){
const ch = (b.channel||'').toLowerCase();
@@ -3668,7 +3851,14 @@ async function deleteAuth(key){
}
function copyText(txt){
navigator.clipboard.writeText(txt).then(()=>toast('已复制','success')).catch(()=>toast('复制失败','error'));
navigator.clipboard.writeText(txt).then(()=>toast(lang==='en'?'Copied':'已复制','success')).catch(()=>toast(lang==='en'?'Copy failed':'复制失败','error'));
}
function copyCliCommand(){
const inp=document.getElementById('cliInput');
const cmd=(inp&&inp.value?inp.value:'').trim();
if(!cmd){ toast(lang==='en'?'Nothing to copy (CLI input is empty)':'没有可复制的命令(输入框为空)','error'); return; }
copyText(cmd);
}
@@ -3908,8 +4098,8 @@ function runCli(){
es.addEventListener('done',e=>{
try{
const d=JSON.parse(e.data);
if(d.code===0) cliAppend('\\n✅ 完成 (exit 0)\\n','cli-done-ok');
else cliAppend('\\n❌ 退出码 '+d.code+'\\n','cli-done-err');
if(d.code===0) cliAppend('\\n✅ Done (exit 0)\\n','cli-done-ok');
else cliAppend('\\n❌ Exit code '+d.code+'\\n','cli-done-err');
}catch(_){}
es.close(); cliEvt=null; setCliRunning(false);
});
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "openclaw-manager",
"version": "0.9.1",
"version": "0.9.4",
"description": "A local web UI for managing OpenClaw AI agents \u2014 no npm install required",
"main": "openclaw-manager.js",
"scripts": {